Use widgets with GlobalKeys in navigation

2,714

When you are using pushNamed new route is added on top of existing one.

Instead of pushNamed you can use pushReplacementNamed, so existing widget will be removed from navigation stack and replaced by new.

Share:
2,714
Author by

kikap

By day: do the operations engineering and manage the devops team. AWS EC2/RDS/ElasticSearch/CloudFormation, Packer, Docker, CI/CD, Python and other nice tools. By night: working on the next generation infrastructure as a code tooling in Purescript. Maintain physical infrastructure visualization service at https://www.rackmaze.com (CoffeeScript/Purescript/Python/NoSQL)

Updated on December 12, 2022

Comments

  • kikap 14 minutes

    My "main" widget has to have the Global key. If I navigate to it with a pushNamed or equivalent it generates the exception about duplicate key in the widget tree. I can only pop to this widget, but that severely reduces my navigation options and reusability of widgets.

    I've included the source of a small repro case, run the app, click Login button on the main page, enter 3 chars of login and 3 chars of password and then Login.

    Any thoughts? Redesigning without the GlobalKey is a major undertaking.

    flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    flutter: The following assertion was thrown while finalizing the widget tree:
    flutter: Duplicate GlobalKey detected in widget tree.
    flutter: The following GlobalKey was specified multiple times in the widget tree. This will lead to parts of
    flutter: the widget tree being truncated unexpectedly, because the second time a key is seen, the previous
    flutter: instance is moved to the new location. The key was:
    flutter: - [GlobalKey#1c91e navKey]
    flutter: This was determined by noticing that after the widget with the above global key was moved out of its
    flutter: previous parent, that previous parent never updated during this frame, meaning that it either did
    flutter: not update at all or updated before the widget was moved, in either case implying that it still
    flutter: thinks that it should have a child with that global key.
    flutter: The specific parent that did not update after having one or more children forcibly removed due to
    flutter: GlobalKey reparenting is:
    flutter: - Semantics(container: false, properties: SemanticsProperties, label: null, value: null, hint: null,
    flutter: hintOverrides: null, renderObject: RenderSemanticsAnnotations#147f5 NEEDS-PAINT)
    flutter: A GlobalKey can only be specified on one widget at a time in the widget tree.
    flutter:
    flutter: When the exception was thrown, this was the stack:
    flutter: #0      BuildOwner.finalizeTree.<anonymous closure> 
    flutter: #1      BuildOwner.finalizeTree 
    flutter: #2      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame 
    flutter: #3      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback 
    flutter: #4      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback 
    flutter: #5      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame 
    flutter: #6      _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame 
    flutter: #10     _invoke  (dart:ui/hooks.dart:236:10)
    flutter: #11     _drawFrame  (dart:ui/hooks.dart:194:3)
    flutter: (elided 3 frames from package dart:async)
    flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════
    flutter: Another exception was thrown: Multiple widgets used the same GlobalKey.
    
    import 'package:flutter/material.dart';
    class MyKeys {
      static final GlobalKey navKey = GlobalKey<NavigatorState>(debugLabel: 'navKey');
    }
    void main() => runApp(MyApp());
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          routes: {
            "/": (_) => MyHomePage(key: MyKeys.navKey, title: 'Home Page'),
            '/auth': (_) => MyAuth(),
          },
        );
      }
    }
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
      final String title;
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
      void _incrementCounter() {
        setState(() {
          _counter++;
        });
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                FlatButton(
                  child: Text('Login'),
                  onPressed: () => Navigator.of(context).pushNamed('/auth'),
                ),
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '$_counter',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      }
    }
    class MyAuth extends StatefulWidget {
      @override
      _MyAuthState createState() => _MyAuthState();
    }
    class _MyAuthState extends State<MyAuth> {
      String login;
      String password;
      final _formKey = GlobalKey<FormState>();
      void _doLogin() {
        final form = _formKey.currentState;
        if(form.validate()) {
          form.save();
          Navigator.of(context).pushNamed('/');
        }
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('Login')),
          body: Padding(
            padding: const EdgeInsets.all(24.0),
            child: Form(
              key: _formKey,
              child: Column(
                children: <Widget>[
                  TextFormField(
                    decoration: InputDecoration(
                      labelText: 'Enter login',
                    ),
                    autocorrect: false,
                    autofocus: true,
                    validator: (v) => v.trim().length < 3 ? 'Enter more than 3 chars': null,
                    onSaved: (v) => login = v.trim(),
                  ),
                  TextFormField(
                    decoration: InputDecoration(
                      labelText: 'Enter password',
                    ),
                    obscureText: true,
                    autocorrect: false,
                    validator: (v) => v.trim().length < 3 ? 'Password should be no less than 3 chars': null,
                    onSaved: (v) => password = v.trim(),
                  ),
                  FlatButton(
                    child: Text('Login'),
                    onPressed: () => _doLogin(),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }