Using Callback and ValueNotifier in Flutter
Hi everyone. Today’s topic is listening the changes when close dialog, bottomsheet, datepicker … We can also listen changes with popular state management packages like provider, bloc,riverpod but I don’t think using package is necessary for listening dialog changes for trigger our page. Without package we can use callback and valuenotifier methods.We are going to use callback method to trigger our page after close the dialog.
But in ValueNotifier we don’t have to close our dialog. ValueNotifier also trigger our page even the dialog still open. So let’s see our example and learn how it works.
Callback Method
Before coding lets think a scenario that we want to change the background color of the app with using flutter_color_picker package in dialog. The first step is open color picker dialog then change the color value. Now we have new color value on color picker dialog and we need to pass this color value to our page with navigator.pop() method.
You can see on video that we can change our dialog background color at the same time. But to change page background color we need to close the dialog and apply our callback method.
- Page Code
import 'package:color_notifier/color_picker_second_dialog.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Color scaffolColor = Colors.white;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
backgroundColor: scaffolColor,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
await openColorPickerDialog();
},
child: const Text("Change Color"))
],
),
),
);
}
Future<void> openColorPickerDialog() async {
await showDialog(
context: context,
builder: (context) {
return ColorPickerSecondDialog(
scaffolColor: scaffolColor,
);
},
).then((value) {
if (value != null) {
setState(() {
scaffolColor = value;
});
}
});
}
}
You see that we are catching value on showDialog function and set this value to our scaffold color. The value comes from our dialog. Let’s also see dialog codes.
2. Dialog Code
// ignore_for_file: must_be_immutable
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorPickerSecondDialog extends StatefulWidget {
ColorPickerSecondDialog({super.key, required this.scaffolColor});
Color scaffolColor;
@override
State<ColorPickerSecondDialog> createState() =>
_ColorPickerSecondDialogState();
}
class _ColorPickerSecondDialogState extends State<ColorPickerSecondDialog> {
@override
Widget build(BuildContext context) {
return AlertDialog(
backgroundColor: widget.scaffolColor,
title: const Text('Pick a color!'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: widget.scaffolColor,
onColorChanged: (value) {
setState(() {
widget.scaffolColor = value;
});
},
)),
actions: <Widget>[
ElevatedButton(
child: const Text('Got it'),
onPressed: () {
Navigator.pop(context, widget.scaffolColor);
},
),
],
);
}
}
In dialog, all we need to do is send back new color value to page.
widget.scaffold color is our new value and we are just passing this to pop method.
Navigator.pop(context, widget.scaffolColor);
That’s it for using callback method. I guess you all know already this using.
I think you have noticed that callback method trigger our pages after close our modal. Is there any way to change app background color at the same time with color picker dialog color? In this point I am going to talk about valuenotifier.
ValueNotifier
Holds a Mutable Value: A
ValueNotifier
holds a single mutable value of any data type. You can change this value over time, and any changes will trigger notifications to its listeners. Even from different pages or dialogs.
- Notifies Listeners:
ValueNotifier
implements theValueListenable
interface, which means you can listen to changes in its value using widgets likeValueListenableBuilder
or by manually adding listener functions to it.
So let’s see what is the difference from using callback.
You can see that we don’t have to close dialog for trigger background color.
So let’s see how it works.
- Page Code
import 'package:color_notifier/color_picker_dialog.dart';
import 'package:flutter/material.dart';
class ColorNotifierPage extends StatefulWidget {
const ColorNotifierPage({super.key});
@override
State<ColorNotifierPage> createState() => _ColorNotifierPageState();
}
class _ColorNotifierPageState extends State<ColorNotifierPage> {
ValueNotifier<Color> scaffolColor = ValueNotifier(Colors.white);
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: scaffolColor,
builder: (context, value, child) {
return Scaffold(
appBar: AppBar(),
backgroundColor: scaffolColor.value,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
await openColorPickerDialog();
},
child: const Text("Change Color"))
],
),
),
);
},
);
}
Future<void> openColorPickerDialog() async {
await showDialog(
context: context,
builder: (context) {
return ColorPickerDialog(
scaffolColor: scaffolColor,
);
},
);
}
}
All we need to do is creating color notifier variable and wrap this variable with ValueListenableBuilder and give notifier color value to scaffold background color. The last step is send our notifier value the our dialog
2. Dialog Code
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
class ColorPickerDialog extends StatefulWidget {
const ColorPickerDialog({super.key, required this.scaffolColor});
final ValueNotifier<Color> scaffolColor;
@override
State<ColorPickerDialog> createState() => _ColorPickerDialogState();
}
class _ColorPickerDialogState extends State<ColorPickerDialog> {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: widget.scaffolColor,
builder: (context, value, child) {
return AlertDialog(
backgroundColor: widget.scaffolColor.value,
title: const Text('Pick a color!'),
content: SingleChildScrollView(
child: ColorPicker(
pickerColor: widget.scaffolColor.value,
onColorChanged: (value) {
widget.scaffolColor.value = value;
},
)),
actions: <Widget>[
ElevatedButton(
child: const Text('Got it'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
In dialog the process is same. We are using ValueListenableBuilder and listening the changes our value inside onColorChanged method.
When color change in color picker, our ValueListenableBuilder widget catch the changes and trigger our dialog and page colors at the same time.
I now that there are a lot of method or way to do that. I just want to show you that these filtering, refreshing, passing parameter processes can be done without using package. If you have better package-less solution please share us and let community know :) Thank you for reading.