While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

In this article we’re going to investigate how we can use callback-style events to communicate between widgets with Flutter.

Why is this important? It allows us to separate our widgets into small, testable units that can be adaptable to their context.

Creating a New Flutter Project

As always, we’ll start off by setting up a new project:

# New Flutter project $ flutter create widget_communication # Open this up inside of VS Code $ cd widget_communication && code .

We can now open this up in the iOS or Android simulator from within VS Code.

Count This!

The first method we’re going to use is simply passing data down to the child as a property. Let’s update main.dart to contain a reference to our CounterPage that we’ll create in a second:

import 'package:flutter/material.dart'; import 'package:widget_communication/counter_page.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Widget Communication', home: CounterPage(), ); } }

The CounterPage widget is a simple StatefulWidget :

// counter_page.dart import 'package:flutter/material.dart'; import 'package:widget_communication/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), ), ); } }

Inside of this widget we’re establishing a count equal to 0 and passing this into a widget named Count as a property. Let’s create the Count widget:

// 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"); } }

This gives us the following expected count of 0 :

VoidCallback

For example’s sake, let’s turn our count into a Button and say that any time we click the button we want to notify the parent CounterPage .

As we don’t want to return a value here, we’ll need to register a VoidCallback . We’ll also add braces to the items within our Count constructor to make them named parameters.

// 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(), ); } }

We’ll then need to update our CounterPage to listen to the onCountSelected callback:

// counter_page.dart Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text("Widget Communication")), body: Center( child: Count( count: count, onCountSelected: () { print("Count was selected."); }, ), ), ); }

If we select the value of our counter now, we should see Count was selected. inside of the debug console!

Function(x)

Whilst the use of VoidCallback is great for identifying callback events with no expected value, what do we do when we want to return a value back to the parent?

Enter, Function(x) :

// counter_page.dart import 'package:flutter/material.dart'; class Count extends StatelessWidget { final int count; final VoidCallback onCountSelected; final Function(int) onCountChange; Count({ @required this.count, @required this.onCountChange, this.onCountSelected, }); @override Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ IconButton( icon: Icon(Icons.add), onPressed: () { onCountChange(1); }, ), FlatButton( child: Text("$count"), onPressed: () => onCountSelected(), ), IconButton( icon: Icon(Icons.remove), onPressed: () { onCountChange(-1); }, ), ], ); } }

Here we’ve added a couple of buttons and a new Function(int) named onCountChange that we’re calling with the value that we want to pass back to the parent.

Inside of the parent we’re able to listen to this and change the value of count accordingly:

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."); }, onCountChange: (int val) { setState(() => count += val); }, ), ), ); } }

Here’s the result of our work: