Tutorial

How To Communicate Between Widgets with Flutter using VoidCallback and Function(x)

Flutter

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:

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():

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:

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:

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:

Screenshot of the Flutter application displaying a count of zero

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:

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:

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:

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:

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:

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:

Screenshot of the Flutter application display an Add button, a Remove button, and a count value of 4

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.

Creative Commons License