What is void callback in flutter

VoidCallback is a function which takes no parameters and returns no parameters. In Flutter it is also true. Sometimes we call it simply callback.

Just like any other data type in Dart, we can use the Function data type to assign a value to a variable. Even we can pass that Function variable through Class Constructor.

Why we do so?

We do so because we want to break our spaghetti code into more modules so that it is easy to read. Easy to use and reuse.

Before we jump into a Flutter example, we will try to understand this concept in Dart console.

Consider this code snippet. You can try it in Dart Pad.

void main() {
  
  int resultOfAddition = calculate(20, 10, add); 
  
  print(resultOfAddition); // 30
  
  int resultOfSubtraction = calculate(20, 10, subtract); 
  
  print(resultOfSubtraction);   // 10
  
}

int add(int num1, int num2) {
  int result = num1 + num2;
  return result;
}

int subtract(int num1, int num2) {
  int result = num1 - num2;
  return result;
}

int calculate(int x, int y, Function performCalculation) {
  return performCalculation(x, y);
}

In the above code, we have used function as a first class citizen, or first class object. In other words, we have passed a function just like any variable.

In Dart, function is treated like any other variable. As a result, we can call the Dart programming language has First-class functions.

It’s a different concept, that we need to understand in depth. So in the next section we will discuss it great detail.

However, at present, let us just remember that, we can pass a function as a parameter. Therefore, we can pass it as a class constructor as well.

Now, try to understand the above code in the light of the code below.

void main() {
  
  calculate(20, 10, add); // 30    
  calculate(20, 10, subtract); // 10

}

void add(int num1, int num2) {
  int result = num1 + num2;
  print(result);
}

void subtract(int num1, int num2) {
  int result = num1 - num2;
  print(result);
}

void calculate(int x, int y, performCalculation) {
  performCalculation(x, y);
}

In the first code sample, we have used functions that returns value. Rather, to be specific, they return a certain data type – integer.

Right?

But when we rewrite the code, we pass a function just as a variable. So it makes more sense.

Therefore we can say this.

VoidCallback = void Function()

That means, it is a signature of callbacks that have no arguments and return no data. As regards to our above code it is closer to void callback. But not exactly the void callback.

Because our callbacks have arguments, although they return no data in particular.

However, we move closer to the concept of void callback.

What is callback function and how it works?

Consequently, we can say that a callback function is a function passed into another function as an argument.

After that, when it is invoked inside the outer function, it completes some kind of routine or action.

In Flutter exactly that happens.

What are callbacks in flutter?

Now, we have learned that callback is basically a function or a method that we pass as a variable into another function, or method to perform an action.

Simply put, we send data using the callback or VoidCallback.

What is Void Callback flutter?

In Flutter the case is slightly different. Because we are handling Widgets, or classes.

Therefore, the VoidCallback is useful for callback events with no expected value. But it will perform some tasks.

Consider the code below.

import 'package:flutter/material.dart';
import 'package:rflutter_alert/rflutter_alert.dart';

import '../model/questions_list.dart';
import '../model/quiz_theme.dart';
import 'answer.dart';
import 'question.dart';

class QuizPage extends StatefulWidget {
  const QuizPage({Key? key}) : super(key: key);

  @override
  State<QuizPage> createState() => _QuizPageState();
}

class _QuizPageState extends State<QuizPage> {
  int _currentIndex = 0;
  QuizMaster quiz = QuizMaster();
  String _correctAnswer = 'Choose your correct answer!';
  int _index = 0; 

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'Playxis - Play + Lexis',
          style: QuizTheme.appbarStyle,
        ),
        backgroundColor: QuizTheme.shrinePink300,
      ),
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Question(questions: quiz.questionList, index: _index),
            ...(quiz.questionList[_index]['answer'] as List<String>)
                .map((answer) {
              return Container(
                width: double.infinity,
                margin: const EdgeInsets.all(5.0),
                child: ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _index = _index + 1;
                    });
                    if (_index == quiz.questionList.length + 1) {
                      _index = 0;
                    }
                    if (_index == 0) {
                      _correctAnswer = 'Choose your correct answer!';
                    } else if (_index == 1) {
                      _correctAnswer = 'Synonym of Mendacity was: Falsehood';
                    } else if (_index == 2) {
                      _correctAnswer = 'Synonym of Culpable was: Guilty';
                    } else if (_index == 3) {
                      _correctAnswer = 'Synonym of Rapacious was: Greedy';
                    } else {
                      _index = 0;
                      _correctAnswer = 'Choose your correct answer!';
                      Alert(
                        context: context,
                        title: 'Quiz Completed.',
                        desc:
                            'We\'ve reached the end. Thanks for taking part. Meet you again.',
                      ).show();
                    }
                  },
                  style: ElevatedButton.styleFrom(
                    primary: QuizTheme.shrineBrown900,
                  ),
                  child: Text(
                    answer,
                    style: QuizTheme.answerStyle,
                  ),
                ),
              );
            }).toList(),
            Container(
              padding: const EdgeInsets.all(5.0),
              width: 250.0,
              child: const Divider(
                thickness: 5.0,
                color: QuizTheme.shrinePink400,
              ),
            ),
            Container(
              width: double.infinity,
              alignment: Alignment.topCenter,
              margin: const EdgeInsets.only(
                left: 10.0,
                right: 10.0,
              ),
              child: Text(
                _correctAnswer,
                style: QuizTheme.questionStyle,
              ),
            ),
          ],
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        backgroundColor: colorScheme.surface,
        selectedItemColor: colorScheme.onSurface,
        unselectedItemColor: colorScheme.onSurface.withOpacity(.60),
        selectedLabelStyle: textTheme.caption,
        unselectedLabelStyle: textTheme.caption,
        onTap: (value) {
          // Respond to item press.
          setState(
            () => _currentIndex = value,
          );
        },
        items: const [
          BottomNavigationBarItem(
            label: 'Favorites',
            icon: Icon(Icons.favorite),
          ),
          BottomNavigationBarItem(
            label: 'Music',
            icon: Icon(Icons.music_note),
          ),
          BottomNavigationBarItem(
            label: 'Places',
            icon: Icon(Icons.location_on),
          ),
          BottomNavigationBarItem(
            label: 'News',
            icon: Icon(Icons.library_books),
          ),
        ],
      ),
    );
  }
}

If we run the above code, our “Play with Lexis Flutter App” works fine.

Although this is not our intention to make our code look like a spaghetti code. We want to understand why we need a VoidCallback or callback event.

Take a look at this part of code snippet.

return Container(
                width: double.infinity,
                margin: const EdgeInsets.all(5.0),                
                child: ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _index = _index + 1;
                    });
                    if (_index == quiz.questionList.length + 1) {
                      _index = 0;
                    }
                    if (_index == 0) {
                      _correctAnswer = 'Choose your correct answer!';
                    } else if (_index == 1) {
                      _correctAnswer = 'Synonym of Mendacity was: Falsehood';
                    } else if (_index == 2) {
                      _correctAnswer = 'Synonym of Culpable was: Guilty';
                    } else if (_index == 3) {
                      _correctAnswer = 'Synonym of Rapacious was: Greedy';
                    } else {
                      _index = 0;
                      _correctAnswer = 'Choose your correct answer!';
                      Alert(
                        context: context,
                        title: 'Quiz Completed.',
                        desc:
                            'We\'ve reached the end. Thanks for taking part. Meet you again.',
                      ).show();
                    }
                  },
                  style: ElevatedButton.styleFrom(
                    primary: QuizTheme.shrineBrown900,
                  ),
                  child: Text(
                    answer,
                    style: QuizTheme.answerStyle,
                  ),
                ),
              );
            ...

In the above code, what do we see?

The Container Widget has a child ElevatedButton Widget which again has a child Text Widget.

Through this two widgets, we wanted to pass two data type. The first one is a void Function. And the second one is a String value.

Now we can minimise our code a little bit in the following way.

The onPressed property of the ElevatedButton returns an anonymous void function. Right?

Therefore, we can create a void method inside the parent Widget firstly.

class _QuizPageState extends State<QuizPage> {
  int _currentIndex = 0;
  QuizMaster quiz = QuizMaster();
  String _correctAnswer = 'Choose your correct answer!';
  int _index = 0;
  void increment() {
    setState(() {
      _index = _index + 1;
    });
    if (_index == quiz.questionList.length + 1) {
      _index = 0;
    }
    if (_index == 0) {
      _correctAnswer = 'Choose your correct answer!';
    } else if (_index == 1) {
      _correctAnswer = 'Synonym of Mendacity was: Falsehood';
    } else if (_index == 2) {
      _correctAnswer = 'Synonym of Culpable was: Guilty';
    } else if (_index == 3) {
      _correctAnswer = 'Synonym of Rapacious was: Greedy';
    } else {
      _index = 0;
      _correctAnswer = 'Choose your correct answer!';
      Alert(
        context: context,
        title: 'Quiz Completed.',
        desc: 'We\'ve reached the end. Thanks for taking part. Meet you again.',
      ).show();
    }
  }

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    final textTheme = Theme.of(context).textTheme;
    return Scaffold(
      appBar: AppBar(
...
// code is incomplete for brevity

Secondly, we can call this function to the onPressed property of the ElevatedButton.

child: ElevatedButton(
     onPressed: () => increment(),
....

However, at the same time, it makes us aware of the possibility to organise our code in a more succinct way.

Since Dart has First-class functions and treat function like any other variable, we can pass it through a Widget. Yes, we want to pass the function as a variable.

In fact, it will drastically reduce our code size. Not only that, it will make our code more readable and reusable.

Therefore we will create a separate custom Widget that will return a Container->ElevatedButton->Text as the Widget tree.

And at the same time, it will pass one String data type and a ViodCallback function through its class constructor.

So that, we can reflect the String value in our Text Widget, and the onPressed property of the ElevatedButton Widget will refer to the VoidCallback function.

As a result, we build this Custom Widget.

import 'package:flutter/material.dart';
import '../model/quiz_theme.dart';

class Answer extends StatelessWidget {
  const Answer({
    Key? key,
    required this.answer,
    required this.pointToOnPress,
  }) : super(key: key);

  final String answer;
  final VoidCallback pointToOnPress;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.all(5.0),
      child: ElevatedButton(
        onPressed: pointToOnPress, // passing 
        style: ElevatedButton.styleFrom(
          primary: QuizTheme.shrineBrown900,
        ),
        child: Text(
          answer,
          style: QuizTheme.answerStyle,
        ),
      ),
    );
  }
}

This time, we treat the function as a variable, or a first-class object, and pass it through the class Constructor.

Moreover, now we can change the code in this direction.

class _QuizPageState extends State<QuizPage> {
  int _currentIndex = 0;
  QuizMaster quiz = QuizMaster();
  String _correctAnswer = 'Choose your correct answer!';
  int _index = 0;
  void increment() {
    setState(() {
      _index = _index + 1;
    });
    if (_index == quiz.questionList.length + 1) {
      _index = 0;
    }
    if (_index == 0) {
      _correctAnswer = 'Choose your correct answer!';
    } else if (_index == 1) {
      _correctAnswer = 'Synonym of Mendacity was: Falsehood';
    } else if (_index == 2) {
      _correctAnswer = 'Synonym of Culpable was: Guilty';
    } else {
      _index = 0;
      _correctAnswer = 'Choose your correct answer!';
      Alert(
        context: context,
        title: 'Quiz Completed.',
        desc: 'We\'ve reached the end. Thanks for taking part. Meet you again.',
      ).show();
    }
  }
// code is continuing below
...
body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: [
            Question(questions: quiz.questionList, index: _index),
            ...(quiz.questionList[_index]['answer'] as List<String>)
                .map((answer) {
              return Answer(answer: answer, pointToOnPress: increment);
            }).toList(),
            Container(
              padding: const EdgeInsets.all(5.0),
              width: 250.0,
              child: const Divider(
                thickness: 5.0,
                color: QuizTheme.shrinePink400,
              ),
            ),
            Container(
              width: double.infinity,
              alignment: Alignment.topCenter,
              margin: const EdgeInsets.only(
                left: 10.0,
                right: 10.0,
              ),
              child: Text(
                _correctAnswer,
                style: QuizTheme.questionStyle,
              ),
            ),
          ],
        ),
      ),

In the above code, now we can return custom Answer Widget that we have built. And pass the String data and the VoidCallback function through its constructor.

Understanding this concept is very important in Flutter. So in the next section, we will try to dig deeper into this conception.

If you want to see the full code snippet and clone the flow of logic please visit this branch of GitHub Repository.

What Next?

Books at Leanpub

Books in Apress

My books at Amazon

Courses at Educative

GitHub repository

Technical blog

Twitter

Comments

One response to “What is void callback in flutter”

  1. […] previous section we have discussed VoidCallback in Flutter. That might give you more […]

Leave a Reply