Why does a change in state of a child bottom sheet trigger a rebuild of parent widget?

2,119

when you call showModalBottomSheet , it actually use Navigator inside

return Navigator.of(context, rootNavigator: useRootNavigator).push(_ModalBottomSheetRoute<T>(
builder: builder,

source code of showModalBottomSheet https://github.com/flutter/flutter/blob/17079f26b54c8517678699a0cefe5f7bfec67b3f/packages/flutter/lib/src/material/bottom_sheet.dart#L635

Flutter teams' reply of issue Pages on Navigator stack rebuild when a new page is pushed https://github.com/flutter/flutter/issues/11655#issuecomment-348287396

This is working as intended. In general, you should assume that all widgets can rebuild at any time, that they don't is mostly just an optimisation.

In particular, routes will rebuild because their navigator state has changed so they might need to update how they draw back buttons and the like.

Share:
2,119
Hassan Hammad
Author by

Hassan Hammad

Updated on November 21, 2022

Comments

  • Hassan Hammad
    Hassan Hammad over 1 year

    I have a Scaffold screen (ListsScreen). Which has a Button(AddNewListButton) that opens up a Modal Bottom Sheet (ListScreenBottomSheetWidget). Bottom Sheet has TextField (ListTitleInputTextFieldWidget).

    When i tap the TextField to open the keyboard, the parent screen rebuilds itself, due to which ofcourse all its child widgets are rebuilt as well.

    Why is this happening? I was under the impression that state changes only rebuild themselves or their children, not their parents. And i also added const constructors almost everywhere to avoid rebuilds but this is still happening.

    The Parent ListsScreen:

    class ListsScreen extends StatelessWidget {
      const ListsScreen();
      static const routeName = '/lists-screen';
    
      @override
      Widget build(BuildContext context) {
        final user = Provider.of<AuthProvider>(context, listen: false).getUser;
        print('stateless rebuilding');
        return Scaffold(
          appBar: AppBar(
            centerTitle: false,
            title: Text(
              '${user['name']}\'s Lists',
              style: TextStyle(
                color: Theme.of(context).primaryColorLight,
              ),
            ),
            actions: <Widget>[
              const SignOutButton(),
            ],
          ),
          body: Center(
            child: Padding(
              padding: const EdgeInsets.all(20.0),
              child: Column(
                children: <Widget>[
                  SizeConfig.smallDevice
                      ? const SizedBox(
                          height: 30,
                        )
                      : const SizedBox(
                          height: 40,
                        ),
                  SizeConfig.smallDevice
                      ? Text(
                          'Welcome to TODOS',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            fontSize: 20,
                            color: Colors.grey[700],
                          ),
                        )
                      : Text(
                          'Welcome to TODOS',
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            fontSize: 25,
                            color: Colors.grey[700],
                          ),
                        ),
                  SizeConfig.smallDevice
                      ? const SizedBox(
                          height: 30,
                        )
                      : const SizedBox(
                          height: 40,
                        ),
                  const AddNewListButton(),
                  SizeConfig.smallDevice
                      ? const SizedBox(
                          height: 30,
                        )
                      : const SizedBox(
                          height: 40,
                        ),
                  const UserListsListViewWidget(),
                ],
              ),
            ),
          ),
        );
      }
    }
    
    class SignOutButton extends StatelessWidget {
      const SignOutButton();
    
      Future<void> _submitRequest(BuildContext context) async {
        _showLoadingAlert(context);
    
        try {
          await Provider.of<AuthProvider>(context, listen: false)
              .submitLogOutRequest();
          Navigator.of(context).pop();
          Navigator.of(context).pushReplacementNamed(LoginScreen.routeName);
        } on HttpExceptions catch (error) {
          Navigator.of(context).pop();
          _showErrorDialogue(error.getErrorList, context);
        }
      }
    
      void _showErrorDialogue(List<dynamic> errorMessages, BuildContext context) {
        showDialog(
          context: context,
          builder: (ctx) => Dialog(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(20.0),
            ),
            child: ErrorListWidget(errorMessages: errorMessages),
          ),
        );
      }
    
      void _showLoadingAlert(BuildContext context) {
        showDialog(
          context: context,
          builder: (ctx) => const LoadingWidget(),
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return FlatButton(
          onPressed: () => _submitRequest(context),
          child: Row(
            children: <Widget>[
              Text(
                'Sign Out',
                style: TextStyle(
                  color: Theme.of(context).primaryColorLight,
                ),
              ),
              Icon(
                Icons.exit_to_app,
                color: Theme.of(context).primaryColorLight,
              ),
            ],
          ),
        );
      }
    }
    
    class AddNewListButton extends StatelessWidget {
      const AddNewListButton();
    
      void _modalBottomSheetMenu(BuildContext context) {
        showModalBottomSheet(
          context: context,
          backgroundColor: Colors.transparent,
          isScrollControlled: true,
          builder: (builder) {
            return const ListScreenBottomSheetWidget();
          },
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return RaisedButton(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(5),
          ),
          elevation: 10,
          color: Theme.of(context).primaryColor,
          onPressed: () => _modalBottomSheetMenu(context),
          child: Text(
            '+ Add List',
            style: TextStyle(
              color: Colors.white,
              fontSize: SizeConfig.smallDevice ? 10 : 15,
            ),
          ),
        );
      }
    }
    

    The Modal Bottom Sheet:

    import 'package:flutter/material.dart';
    import 'package:todo_spicotech/helpers/size_config.dart';
    
    class ListScreenBottomSheetWidget extends StatelessWidget {
      const ListScreenBottomSheetWidget();
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onTap: () {
            FocusScopeNode currentFocus = FocusScope.of(context);
            if (!currentFocus.hasPrimaryFocus) {
              currentFocus.unfocus();
            }
            currentFocus.unfocus();
          },
          child: Container(
            margin: const EdgeInsets.all(20.0),
            padding: EdgeInsets.only(
              bottom: MediaQuery.of(context).viewInsets.bottom,
            ),
            child: Material(
              borderRadius: BorderRadius.all(Radius.circular(15)),
              elevation: 10,
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
    
                    SizeConfig.smallDevice
                        ? Text(
                            'Create a new List',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 20,
                              color: Colors.grey[700],
                            ),
                          )
                        : Text(
                            'Create a new List',
                            textAlign: TextAlign.center,
                            style: TextStyle(
                              fontSize: 25,
                              color: Colors.grey[700],
                            ),
                          ),
                    SizeConfig.smallDevice
                        ? const SizedBox(
                      height: 20,
                    )
                        : const SizedBox(
                      height: 30,
                    ),
                    const ListTitleInputTextFieldWidget(),
                    SizeConfig.smallDevice
                        ? const SizedBox(
                      height: 20,
                    )
                        : const SizedBox(
                      height: 30,
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: <Widget>[
                        InkWell(
                          borderRadius: BorderRadius.circular(5),
                          onTap: () {
                            Navigator.of(context).pop();
                          },
                          child: Ink(
                            padding: EdgeInsets.all(10),
                            child: const Text('CANCEL'),
                          ),
                        ),
                        RaisedButton(
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(5),
                          ),
                          elevation: 10,
                          color: Theme.of(context).primaryColor,
                          onPressed: () {},
                          child: Text(
                            'Create',
                            style: TextStyle(
                              color: Colors.white,
                              fontSize: SizeConfig.smallDevice ? 10 : 15,
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ),
        );
      }
    }
    
    class ListTitleInputTextFieldWidget extends StatefulWidget {
      const ListTitleInputTextFieldWidget();
      @override
      _ListTitleInputTextFieldWidgetState createState() => _ListTitleInputTextFieldWidgetState();
    }
    
    class _ListTitleInputTextFieldWidgetState extends State<ListTitleInputTextFieldWidget> {
      @override
      Widget build(BuildContext context) {
        return TextFormField(
          decoration: const InputDecoration(
            enabledBorder: OutlineInputBorder(
              borderSide: BorderSide(
                color: Colors.lightBlue,
              ),
            ),
            focusedBorder: OutlineInputBorder(
              borderSide: BorderSide(
                color: Colors.lightBlue,
              ),
            ),
            labelText: 'List Title',
            contentPadding: EdgeInsets.all(10),
          ),
        );
      }
    }