State Management for Child Widget
Learn about parent-child widget state management in Flutter.
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;});}@overrideWidget 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);}@overrideWidget 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), ), ), ); } }