State Management for Child Widget

This lesson will explore how a stateful parent widget manages the state of a stateless child widget.

Child widget state management

Any Flutter widget can manage its state if it’s a stateful widget. However, when it’s a stateless child widget, the parent manages its state.

It’s an exciting feature of Flutter, especially when we manage the state in a small application. However, this process is unsuitable for a large-scale application where we have to pass the state object to many screens or pages. In such cases, we’ll use Provider or Riverpod packages. We can even use the BLOC architecture.

The Provider package is one of the best options, and we will stick with it for this chapter.

Still, to understand the Flutter state management, we should know how it works at the root level.

Exporting a child widget’s state

How can a child widget export its state to its parent? Without a callback, we cannot.

So, we will consider the callback first. Since the parent is importing the state of the child widget, we will not make the child widget stateful anymore. It can be stateless.

However, the parent widget should be stateful. Moreover, a consistent mechanism will help the child widget to export its state safely.

Let’s see the code of the parent widget class.

class ParentWidget extends StatefulWidget {
@override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
// Manages the _inActive state for ChildWidget.
bool _inActive = true;
// Implements _manageStateForChildWidget(), the method called when the box is tapped.
void _manageStateForChildWidget(bool newValue) {
setState(() {
_inActive = newValue;
});
}
@override
Widget build(BuildContext context) {
return ChildWidget(
inActive: _inActive,
notifyParent: _manageStateForChildWidget,
);
}
}

The code is very straightforward. By default, the state object should be inactive. So we’ve set it to true. It also manages the state of the child widget. As we tap a box, it will no longer remain inactive. It becomes false from true and makes the state active. Now, we need the setState() method, which will implement a method that passes a boolean parameter whose value is false.

In the app, the red Container widget inside the child widget becomes green. This change is visible in the illustration below:

Observe this part of the above code:

// Manages the _inActive state for ChildWidget.
//
bool _inActive = true;
// Implements _manageStateForChildWidget(), the method called when the box is tapped.
//
void _manageStateForChildWidget(bool newValue) {
setState(() {
_inActive = newValue;
});
}

Now, we need a callback. For that, we will use a special feature of Flutter:

typedef ValueChanged<T> = void Function(T value);

Implementing the ValueChanged typedef

How does the child widget implement this unique property? Let us see the code of the child widget. That will explain the rest.

// Extends StatelessWidget because all state is handled by its parent, ParentWidget
//
class ChildWidget extends StatelessWidget {
ChildWidget({Key? key, this.inActive = true, required this.notifyParent})
: super(key: key);
final bool inActive;
// When a tap is detected, it notifies the parent.
//
final ValueChanged<bool> notifyParent;
void manageState() {
notifyParent(!inActive);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: manageState,
child: Container(
child: Center(
child: Text(
inActive ? 'Inactive' : 'Active',
style: TextStyle(
fontSize: 25.0,
color: Colors.white,
),
),
),
width: 250.0,
height: 250.0,
decoration: BoxDecoration(color: inActive ? Colors.red : Colors.green),
),
);
}
}

Notice this part of the above code:

// When a tap is detected, it notifies the parent.
final ValueChanged<bool> notifyParent;
void manageState() {
notifyParent(!inActive);
}

As we have said earlier, by using this unique feature of Flutter, we have a method that passes a boolean value that exports the state to the parent widget.

Exporting and importing Flutter states

This section will address two questions. Firstly, how does the child widget export the state? Secondly, how does the parent widget import the state?

We can find the answer in the child widget constructor, where two named parameters point to a piece of data and a method that, through its parameter, changes the state of that data.

In the parent widget:

return ChildWidget( inActive: _inActive, notifyParent: _manageStateForChildWidget, );

In the child widget, this line is essential.

final ValueChanged<bool> notifyParent;

Here, notifyParent is a method that passes a specific type of data, where we indicate the data type which we would pass:

ValueChanged<bool>

Here’s the complete working application, which uses the parent widget to manage the state of a child widget:

import 'package:flutter/material.dart';

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

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  // Manages the _inActive state for ChildWidget.
  bool _inActive = true;

  // Implements _manageStateForChildWidget(), the method called when the box is tapped.
  void _manageStateForChildWidget(bool newValue) {
    setState(() {
      _inActive = newValue;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ChildWidget(
      inActive: _inActive,
      notifyParent: _manageStateForChildWidget,
    );
  }
}

// Extends StatelessWidget because all state is handled by its parent, ParentWidget
class ChildWidget extends StatelessWidget {
  ChildWidget({
    Key? key,
    this.inActive = true,
    required this.notifyParent,
  }) : super(key: key);
  final bool inActive;

  // When a tap is detected, it notifies the parent.
  final ValueChanged<bool> notifyParent;

  void manageState() {
    notifyParent(!inActive);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Our App',
      debugShowCheckedModeBanner: false,
      home: GestureDetector(
        onTap: manageState,
        child: Container(
          child: Center(
            child: Text(
              inActive ? 'Inactive' : 'Active',
              style: TextStyle(
                decoration: TextDecoration.none,
                fontSize: 30.0,
                fontWeight: FontWeight.bold,
                color: Colors.black,
              ),
            ),
          ),
          decoration:
              BoxDecoration(color: inActive ? Colors.red : Colors.green),
        ),
      ),
    );
  }
}
State management example