How to solve: setState in pageBuilder doesn't work in Flutter

Question

Asked by Zoltan on December 02, 2021 (source).

I'm using Hero widget to pop up a selector page where you can select your preferred color to use as background. If I tap on "Change color!" then the main page container background changes perfectly, but on the selector page it remains in the build "state". After closing and reopening my selector page, then - of course - the background will filled with the good color...

Any idea?

import 'package:flutter/cupertino.dart';
import 'dart:ui';

class TestPage extends StatefulWidget {
  @override
  _TestPageState createState() => _TestPageState();
}

class CustomRectTween extends RectTween {
  final Rect? begin;
  final Rect? end;

  CustomRectTween({
    required this.begin,
    required this.end,
  }) : super(begin: begin, end: end);

  @override
  Rect lerp(double t) {
    final elasticCurveValue = Curves.easeOut.transform(t);
    double? xLeft = begin!.left;
    double yLeft = end!.left;
    double xRight = begin!.right;
    double yRight = end!.right;
    double xTop = begin!.top;
    double yTop = end!.top;
    double xBottom = begin!.bottom;
    double yBottom = end!.bottom;
    return Rect.fromLTRB(
      lerpDouble(xLeft, yLeft, elasticCurveValue)!,
      lerpDouble(xTop, yTop, elasticCurveValue)!,
      lerpDouble(xRight, yRight, elasticCurveValue)!,
      lerpDouble(xBottom, yBottom, elasticCurveValue)!,
    );
  }
}

class _TestPageState extends State<TestPage> {
  int actNum = 1;

  @override
  void initState() {
    // initState vagyis itt kerülnek beállításra az alapértékek
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
          body: Stack(
        children: [
          Container(
            color: actNum == 1 ? Colors.greenAccent : Colors.white,
            child: Hero(
              tag: "akcio",
              transitionOnUserGestures: true,
              createRectTween: (begin, end) {
                return CustomRectTween(begin: begin, end: end);
              },
              child: GestureDetector(
                  onTap: () {
                    Navigator.of(context).push(new PageRouteBuilder(
                        opaque: false,
                        maintainState: false,
                        barrierDismissible: false,
                        transitionDuration: Duration(milliseconds: 450),
                        pageBuilder: (BuildContext context, _, __) {
                          return Container(
                            height: MediaQuery.of(context).size.height,
                            width: MediaQuery.of(context).size.width,
                            child: Stack(
                              children: [
                                Positioned(
                                  bottom: 100.0,
                                  child: Hero(
                                    tag: "akcio",
                                    child: GestureDetector(
                                      onDoubleTap: () {
                                        Navigator.of(context).pop();
                                      },
                                      onTap: () {
                                        if (actNum == 1) {
                                          actNum = 0;
                                        } else {
                                          actNum = 1;
                                        }
                                        setState(() {});
                                      },
                                      child: Container(
                                        height: 100.0,
                                        width:
                                            MediaQuery.of(context).size.width,
                                        color: actNum == 1
                                            ? Colors.greenAccent
                                            : Colors.white,
                                        child: Container(
                                            child: Text('Change color!')),
                                      ),
                                    ),
                                  ),
                                )
                              ],
                            ),
                          );
                        }));
                  },
                  child: Icon(
                    Icons.play_circle_fill,
                    color: Colors.black,
                    size: 75.0,
                  )),
            ),
          )
        ],
      )),
    );
  }
}

Answer

Question answered by Yeasin S (source).

Wrap your pageBuilder's Container with Scaffold widget, it will solve the issue. It is just overlaying with main widget.

  transitionDuration: Duration(milliseconds: 450),
                        pageBuilder: (BuildContext context, _, __) {
                          return Scaffold(
                            body: Container(
                              height: MediaQuery.of(context).size.height,
                              width: MediaQuery.of(context).size.width,
                              child: Stack(

Also, another solution is MaterialPageRoute

  GestureDetector(
                  onTap: () async {
                    final result =
                        await Navigator.of(context).push(MaterialPageRoute(
                      builder: (context) => NewWidget(),
                    ));

                    print("result isactNum0 $result");

                    setState(() {
                      actNum = result ? 1 : 0;
                    });
                  },

NewWidget is the nextPage


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

  @override
  State<NewWidget> createState() => _NewWidgetState();
}

class _NewWidgetState extends State<NewWidget> {
  bool isactNum0 = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.transparent,
      body: Container(
        height: MediaQuery.of(context).size.height,
        width: MediaQuery.of(context).size.width,
        child: Stack(
          children: [
            Positioned(
              bottom: 100.0,
              child: Hero(
                tag: "akcio",
                child: GestureDetector(
                  onDoubleTap: () {
                    Navigator.of(context).pop(isactNum0); // pass current state isactNum0
                  },
                  onTap: () {
                    setState(() {
                      isactNum0 = !isactNum0;
                    });
                  },
                  child: Container(
                    height: 100.0,
                    width: MediaQuery.of(context).size.width,
                    color: isactNum0 ? Colors.greenAccent : Colors.white,
                    child: Container(child: Text('Change color!')),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

push returns future and we can wait for result. While it will pop, we will get current state from NewWidget and updating UI based on it.

Main page wasn't updating because it is from different route.

Check question comments to learn more about Difference between MaterialPageRoute and PageRouteBuilder

FLUTTER HERO NAVIGATOR SETSTATE
SHARE: