Table of Contents
Introduction
A good paradigm for your Flutter projects is separating your widgets into small, testable units that can be adaptable to their context.
Flutter offers VoidCallback and Function(x) (where x can be a different type) for callback-style events between child and parent widgets.
In this article, you will use callback-style events to communicate between widgets with Flutter.
Prerequisites
To follow along with this article, you will need:
- To download and install Flutter.
- To download and install Android Studio _or_ Visual Studio Code.
- It is recommended to install plugins for your code editor:
Flutterextension installed for Visual Studio Code.
This article was verified with Flutter v1.22.2, Android SDK v30.0.2, and Android Studio v4.1.
Step 1 — Setting Up the Project
Once you have your environment set up for Flutter, you can run the following to create a new application:
flutter create <^>flutter_widget_communication<^>
Navigate to the new project directory:
cd <^>flutter_widget_communication<^>
Using flutter create will produce a demo application that will display the number of times a button is clicked.
You will build upon the code generated to experiment with callback-style events.
Step 2 — Passing Data from a Parent Widget to a Child Widget
You will create a parent widget (CounterPage) and a child widget (Count). The count value from the parent will display in the child.
Open main.dart in your code editor and modify it to use CounterPage():
[label lib/main.dart]
import 'package:flutter/material.dart';
<^>import 'counter_page.dart';<^>
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: <^>'Widget Communication'<^>,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: <^>CounterPage()<^>,
);
}
}
This code will display the CounterPage.
Create a new counter_page.dart file and add the following lines of code:
[label lib/counter_page.dart]
import 'package:flutter/material.dart';
import 'count.dart';
class CounterPage extends StatefulWidget {
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Widget Communication")),
body: Center(
child: Count(count),
),
);
}
}
This code will pass the count to a child widget.
Create a new count.dart file and add the following lines of code:
[label lib/count.dart]
import 'package:flutter/material.dart';
class Count extends StatelessWidget {
final int count;
Count(this.count);
@override
Widget build(BuildContext context) {
return Text("$count");
}
}
Compile your code and have it run in an emulator:
This will display the count (currently set to the number zero) on the screen.
Next, you will add a VoidCallback.
Step 3 — Using VoidCallback
For the purpose of this tutorial, you will want to create a Button that will register clicks and notify the parent CounterPage.
As you don't want to return a value here, you will need to register a _VoidCallback_. You will also add braces to the items within the Count constructor to make them _named parameters_.
Revisit count.dart and add onCountSelected:
[label lib/count.dart]
class Count extends StatelessWidget {
final int count;
<^>final VoidCallback onCountSelected;<^>
Count(<^>{<^>
<^>@required this.count,<^>
<^>this.onCountSelected,<^>
<^>}<^>);
@override
Widget build(BuildContext context) {
return <^>FlatButton(<^>
<^>child:<^> Text("$count"),
<^>onPressed: () => onCountSelected(),<^>
<^>);<^>
}
}
Then, revisit counter_page.dart and listen for the onCountSelected callback:
[label lib/counter_page.dart]
import 'package:flutter/material.dart';
import 'count.dart';
class CounterPage extends StatefulWidget {
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Widget Communication")),
body: Center(
child: Count(
<^>count: count,<^>
<^>onCountSelected: () {<^>
<^>print("Count was selected.");<^>
<^>},<^>
)
),
);
}
}
Compile your code and have it run in an emulator. Interact with the button and observe the output in your console:
[secondary_label Output]
Count was selected.
At this point, however, the count value will remain zero.
Next, you will add a Function(x).
Step 4 — Using Function(x)
VoidCallback is useful for callback events with no expected value. For scenarios where you want to return a value back to the parent, you will want to use Function(x).
Revisit count.dart and add Function(int) onCountChanged:
[label lib/count.dart]
import 'package:flutter/material.dart';
class Count extends StatelessWidget {
final int count;
final VoidCallback onCountSelected;
<^>final Function(int) onCountChanged;<^>
Count({
@required this.count,
<^>@required this.onCountChanged,<^>
this.onCountSelected,
});
@override
Widget build(BuildContext context) {
return <^>Row(<^>
<^>mainAxisAlignment: MainAxisAlignment.center,<^>
<^>children: <Widget>[<^>
<^>IconButton(<^>
<^>icon: Icon(Icons.add),<^>
<^>onPressed: () {<^>
<^>onCountChanged(1);<^>
},<^><^>
<^>),<^>
FlatButton(
child: Text("$count"),
onPressed: () => onCountSelected(),
),
<^>IconButton(<^>
<^>icon: Icon(Icons.remove),<^>
<^>onPressed: () {<^>
<^>onCountChanged(-1);<^>
<^>},<^>
<^>),<^>
<^>],<^>
<^>);<^>
}
}
Then, revisit counter_page.dart and listen for the onCountChange callback:
[label lib/counter_page.dart]
import 'package:flutter/material.dart';
import 'count.dart';
class CounterPage extends StatefulWidget {
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int count = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Widget Communication")),
body: Center(
child: Count(
count: count,
onCountSelected: () {
print("Count was selected.");
},
<^>onCountChanged: (int val) {<^>
<^>setState(() => count += val);<^>
<^>},<^>
)
),
);
}
}
When a button is clicked, the change value is passed from the Count child widget to the CounterPage parent widget. Then, the addition between the val and count is performed and count is updated.
Compile your code and have it run in an emulator:
Interact with the Add (+) and Remove (-) buttons, the count value should increment and decrement respectively.
Conclusion
In this article, you learned how to use VoidCallback and Function(x) to use callback-style events to communicate between widgets with Flutter.
If you'd like to learn more about Flutter, check out our Flutter topic page for exercises and programming projects.