Saturday, June 8, 2019

Pass child state to parent state in Flutter

08th June 2019,

In my recent flutter project I got a requirement to pass children states to parent state and move to the next screen based on children states.




As above image user can be select or deselect a row by clicking it. So I made a stateful widget which can be used multiple times.

 class RowContent extends StatefulWidget {  
  final String question;  
  RowContent(this.question);  
  @override  
  _RowContentState createState() => _RowContentState(question);  
 }  
 class _RowContentState extends State<RowContent> {  
  String passedQuestion;  
  var selectedImage;  
  var unSelectedImage;  
  var currentImage;  
  _RowContentState(this.passedQuestion);  
  @override  
  void initState() {  
   super.initState();  
   selectedImage = AssetImage("assets/images/ic_check.png");  
   unSelectedImage = AssetImage("assets/images/ic_uncheck.png");  
   currentImage = unSelectedImage;  
  }  
  @override  
  Widget build(BuildContext context) {  
   return InkWell(  
    onTap: () {  
     setState(() {  
      currentImage =  
        currentImage == selectedImage ? unSelectedImage : selectedImage;  
     });  
    },  
    child: Row(  
     children: <Widget>[  
      Padding(  
       padding: const EdgeInsets.only(  
        left: 32.0,  
        right: 26.0,  
       ),  
       child: Container(  
        height: 48,  
        width: 48,  
        decoration: new BoxDecoration(  
         image: new DecorationImage(  
           image: currentImage, fit: BoxFit.scaleDown),  
        ),  
       ),  
      ),  
      Expanded(  
       child: Padding(  
        padding: const EdgeInsets.only(right: 32.0),  
        child: Text(  
         passedQuestion  
        ),  
       ),  
      ),  
     ],  
    ),  
   );  
  }  
 }  

Now I want to go to the next screen only if user has selected all the 3 rows. This is where I wonder how can I pass the children states to the parent state so that parent can decide when to move to the next screen. for achive this I used callback methods.

Here is the code of parent component with callbacks.

 class OpenReport extends StatefulWidget {  
  @override  
  _OpenReportState createState() => _OpenReportState();  
 }  
 class _OpenReportState extends State<OpenReport> {  
  int totalCount = 0;  
  callback(int value){  
   setState(() {  
    totalCount = totalCount + value;  
    debugPrint(totalCount.toString());  
   });  
  }  
  @override  
  Widget build(BuildContext context) {  
   return Scaffold(  
    appBar: buildAppBar(context),  
    body: buildOpenReportContent(context),  
   );  
  }  
  AppBar buildAppBar(BuildContext context) {  
   return AppBar(  
    elevation: 0.0,  
    actions: <Widget>[  
     IconButton(  
       icon: Icon(  
        Icons.close,  
       ),  
       onPressed: () {  
        if (Navigator.canPop(context)) {  
         Navigator.pop(context, true);  
        }  
       },  
      ),  
    ],  
    centerTitle: true,  
   );  
  }  
  Widget buildOpenReportContent(BuildContext context) {  
   return Container(  
    child: Column(  
     children: <Widget>[  
      Container(  
       child: Padding(  
        padding: const EdgeInsets.only(  
         left: 80.0,  
         top: 28.0,  
         right: 80.0,  
        ),  
        child: Text(  
         "",  
         textAlign: TextAlign.center,  
        ),  
       ),  
      ),  
      Expanded(  
       child: Column(  
        children: <Widget>[  
         Padding(  
          padding: const EdgeInsets.only(top: 64.0, bottom: 32.0),  
          child: RowContent("Row 1 click to enable", callback),  
         ),  
         RowContent("Row 2 click to enable", callback),  
         Padding(  
          padding: const EdgeInsets.only(top: 32.0),  
          child: RowContent("Row 3 click to enable", callback),  
         ),  
        ],  
       ),  
      ),  
      Container(  
       child: Padding(  
        padding: const EdgeInsets.all(24.0),  
        child: Container(  
         height: 48.0,  
         child: FlatButton(  
          onPressed: () {  
           if (totalCount == 3){  
            debugPrint("implement go to next screen");  
           }  
          },  
          child: Row(  
           mainAxisAlignment: MainAxisAlignment.center,  
           children: <Widget>[  
            Text(  
             "go to next screen",  
            ),  
           ],  
          ),  
         ),  
        ),  
       ),  
      ),  
     ],  
    ),  
   );  
  }  
 }  

Here's how the child component changed.

 class RowContent extends StatefulWidget {  
  final String question;  
  final Function(int) callback; // getting the parent function  
  RowContent(this.question, this.callback);  
  @override  
  _RowContentState createState() => _RowContentState(question);  
 }  
 class _RowContentState extends State<RowContent> {  
  String passedQuestion;  
  var selectedImage;  
  var unSelectedImage;  
  var currentImage;  
  _RowContentState(this.passedQuestion);  
  @override  
  void initState() {  
   super.initState();  
   selectedImage = AssetImage("assets/images/ic_check.png");  
   unSelectedImage = AssetImage("assets/images/ic_uncheck.png");  
   currentImage = unSelectedImage;  
  }  
  @override  
  Widget build(BuildContext context) {  
   return InkWell(  
    onTap: () {  
     setState(() {  
      currentImage =  
        currentImage == selectedImage ? unSelectedImage : selectedImage;  
      widget.callback(currentImage == selectedImage ? 1 : -1); // calling parent function  
     });  
    },  
    child: Row(  
     children: <Widget>[  
      Padding(  
       padding: const EdgeInsets.only(  
        left: 32.0,  
        right: 26.0,  
       ),  
       child: Container(  
        height: 48,  
        width: 48,  
        decoration: new BoxDecoration(  
         image: new DecorationImage(  
           image: currentImage, fit: BoxFit.scaleDown),  
        ),  
       ),  
      ),  
      Expanded(  
       child: Padding(  
        padding: const EdgeInsets.only(right: 32.0),  
        child: Text(  
         passedQuestion,  
        ),  
       ),  
      ),  
     ],  
    ),  
   );  
  }  
 }  

This is how you can pass child state to parent state. There is another way to do this using global state obeject. Anyway hope someone find this useful.
Happy coding!!!