Custom events in a Flutter widget: how to pass function parameters in Dart

* Note: the complete Flutter source code is at the end of this article

Goals

  • Create a function parameter in Dart
  • Add custom events to any Flutter widget

Custom events in a Flutter widget: how to pass function parameters in Dart

When creating Widgets we often need to allow the execution of custom code when our widget changes state, for example when a user taps on the Widget. In order to allow this behaviour we can define our custom events on our widget (i.e. onTap to react to the user touching our widget) so that developers can run their own code when these events are triggered.

Let's see an example

We will create a simple widget that shows a message. We will also have an 'onTouched' custom event raised whenever the user touches the widget.

The code that uses our widget will react on each 'onTouched' event by showing the number of times we have touched the widget.

Let's get to the code.

Our widget features a GestureDetector than contains the box with the caption "Touch me". When the user taps the widget, the 'onTap' event of the GestureDetector is fired: we check if the 'onTouched' function has been defined and, if it has, we will call it.

class TouchMeWidget extends StatefulWidget {
  TouchMeWidget({this.onTouched});
  final Function onTouched;

  @override
  createState() => _TouchMeWidgetState();
}

class _TouchMeWidgetState extends State<TouchMeWidget> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () { 
        if (null != widget.onTouched) {
          // Raise the event
          widget.onTouched();
        }
      },
      child: Container(
        width: 300,
        height: 150,
        color: Colors.blue,
        child: Center(
          child: Text("Touch me"), 
        ),
      ),
    );
  }
}

Now let's write a widget that uses our TouchMeWidget and, whenever the user taps the box with the caption "Touch me", we will update the message to the user:

class SomeWidget extends StatefulWidget {
  @override
  createState() => _SomeWidgetState();
}

class _SomeWidgetState extends State<SomeWidget> {
  // This is the message we will be updating every 
  // time the user touches the box.
  var _caption = "Touch the box"; 

  // This is a counter to remember how many times 
  // the user has touched the widget.
  int _timesTouched = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Column(
          children: <Widget> [
            Center(
              // Show the message
              child: Text(_caption), 
            ),
            Center(
              // Show the box
              child: TouchMeWidget(),
            ),
          ],
        ),
      ),
    );
  }
}

In the constructor of TouchMeWidget, let's pass the 'onTouched' function, as folows:

// Show the box
child: TouchMeWidget(
    onTouched: () {
      // TODO: code to execute when the user taps the box.                          
    },
),

We can now define the behaviour that follows each tap on the box by the user, that is we want to update our message:

// Show the box
child: TouchMeWidget(
    onTouched: () {
      // setState will cause a redraw of our widget tree.
      setState(() { 
        // Update the message to the user.
        _caption = "you touched the box ${++_timesTouched} times";
      });                           
    },
),

That's it, we're ready to run the code on dartpad.dartlang.org:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SomeWidget(),
      ),
    );
  }
}

class SomeWidget extends StatefulWidget {
  @override
  createState() => _SomeWidgetState();
}

class _SomeWidgetState extends State<SomeWidget> {
  var _caption = "Touch the box";
  int _timesTouched = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Column(
          children: <Widget> [
            Center(
              child: Text(_caption),
            ),
            Center(
              child: TouchMeWidget(
                onTouched: () {
                  setState(() {
                    _caption = "you touched the box ${++_timesTouched} times";
                  });                           
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class TouchMeWidget extends StatefulWidget {
  TouchMeWidget({this.onTouched});
  final Function onTouched;

  @override
  createState() => _TouchMeWidgetState();
}

class _TouchMeWidgetState extends State<TouchMeWidget> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () { 
        if (null != widget.onTouched) {
          // Raise the event
          widget.onTouched();
        }
      },
      child: Container(
        width: 300,
        height: 150,
        color: Colors.blue,
        child: Center(
          child: Text("Touch me"), 
        ),
      ),
    );
  }
}

Adding parameters to a custom event function

If we want to pass parameters to the event function that is passed to our widget, the best way is to define the correct function signature to ensure that the compiler will catch errors when, for instance, a developer passes the wrong number (or type) of parameters.

To see how we can do this, let's change our code so that our widget will remember the number of times it has been touched by the user, and pass this information to the 'onTouched' event function.

Firt of all let's define the custom function that takes an 'int' parameter:

typedef OnTouchedCallback = void Function(int timesTouched);

Now we can use 'OnTouchedCallback' type in our TouchMeWidget, to ensure handlers are declared correctly:

class TouchMeWidget extends StatefulWidget {
  TouchMeWidget({this.onTouched});

  final OnTouchedCallback onTouched;

  @override
  createState() => _TouchMeWidgetState();
}

Let's edit the usage of 'onTouch' to receive the new parameter 'timesTouched' and display the message to the user:

TouchMeWidget(
    onTouched: (int timesTouched) {
      setState(() {
        _caption = "you touched the box ${timesTouched} times";
      });                           
    },
),

Putting it all together, Here is the full source code (you can try it out on dartpad.dartlang.org.

Happy Coding! ;)

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: SomeWidget(),
      ),
    );
  }
}

class SomeWidget extends StatefulWidget {
  @override
  createState() => _SomeWidgetState();
}

class _SomeWidgetState extends State<SomeWidget> {
  var _caption = "Touch the box";


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Column(
          children: <Widget> [
            Center(
              child: Text(_caption),
            ),
            Center(
              child: TouchMeWidget(
                onTouched: (int timesTouched) {
                  setState(() {
                    _caption = "you touched the box $timesTouched times";
                  });                           
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

typedef OnTouchedCallback = void Function(int timesTouched);

class TouchMeWidget extends StatefulWidget {
  TouchMeWidget({this.onTouched});
  final OnTouchedCallback onTouched;

  @override
  createState() => _TouchMeWidgetState();
}

class _TouchMeWidgetState extends State<TouchMeWidget> {
  int _timesTouched = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () { 
        if (null != widget.onTouched) {
          // Raise the event
          print(_timesTouched);
          widget.onTouched(++_timesTouched);
        }
      },
      child: Container(
        width: 300,
        height: 150,
        color: Colors.blue,
        child: Center(
          child: Text("Touch me"), 
        ),
      ),
    );
  }
}
comments powered by Disqus