44

In Emily Fortuna's article (and video) she mentions:

GlobalKeys have two uses: they allow widgets to change parents anywhere in your app without losing state, or they can be used to access information about another widget in a completely different part of the widget tree. An example of the first scenario might if you wanted to show the same widget on two different screens, but holding all the same state, you’d want to use a GlobalKey.

Her article includes a gif demo of an app called "Using GlobalKey to ReuseWidget" but does not provide source code (probably because it's too trivial). You can also see a quick video demo here, starting at 8:30 mark: https://youtu.be/kn0EOS-ZiIc?t=510

How do I implement her demo? Where do I define the GlobalKey variable and how/where do I use it? Basically for example, I want to display a counter that counts up every second, and have it on many different screens. Is that something GlobalKey can help me with?

WSBT
  • 33,033
  • 18
  • 128
  • 133

3 Answers3

43

The most common use-case of using GlobalKey to move a widget around the tree is when conditionally wrapping a "child" into another widget like so:

Widget build(context) {
  if (foo) {
    return Foo(child: child);
  }
  return child;
}

With such code, you'll quickly notice that if child is stateful, toggling foo will make child lose its state, which is usually unexpected.

To solve this, we'd make our widget stateful, create a GlobalKey, and wrap child into a KeyedSubtree.

Here's an example:

class Example extends StatefulWidget {
  const Example({Key key, this.foo, this.child}) : super(key: key);

  final Widget child;
  final bool foo;

  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    final child = KeyedSubtree(key: key, child: widget.child);

    if (widget.foo) {
      return Foo(child: child);
    }
    return child;
  }
}
Delgan
  • 18,571
  • 11
  • 90
  • 141
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • Thanks for pointing out the `KeyedSubtree` widget. I will use it next time! – WSBT Jul 09 '19 at 18:36
  • 2
    Why is Example a StatefulWidget. If the 'key' member is set as final, can it not be a stateless widget? – Scorb Jul 14 '21 at 15:32
34

I would not recommend using GlobalKey for this task.

You should pass the data around, not the widget, not the widget state. For example, if you want a Switch and a Slider like in the demo, you are better off just pass the actual boolean and double behind those two widgets. For more complex data, you should look into Provider, InheritedWidget or alike.

Things have changed since that video was released. Saed's answer (which I rewarded 50 bounty points) might be how it was done in the video, but it no longer works in recent Flutter versions. Basically right now there is no good way to easily implement the demo using GlobalKey.

But...

If you can guarantee that, the two widgets will never be on the screen at the same time, or more precisely, they will never be simultaneously inserted into the widget tree on the same frame, then you could try to use GlobalKey to have the same widget on different parts of the layout.

Note this is a very strict limitation. For example, when swiping to another screen, there is usually a transition animation where both screens are rendered at the same time. That is not okay. So for this demo, I inserted a "blank page" to prevent that when swiping.

Global key demo

How to:

So, if you want the same widget, appearing on very different screens (that hopefully are far from each other), you can use a GlobalKey to do that, with basically 3 lines of code.

First, declare a variable that you can access from both screens:

final _key = GlobalKey();

Then, in your widget, have a constructor that takes in a key and pass it to the parent class:

Foo(key) : super(key: key);

Lastly, whenever you use the widget, pass the same key variable to it:

return Container(
  color: Colors.green[100],
  child: Foo(_key),
);

Full Source:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatelessWidget {
  final _key = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Global Key Demo")),
      body: PageView.builder(
        itemCount: 3,
        itemBuilder: (context, index) {
          switch (index) {
            case 0:
              return Container(
                color: Colors.green[100],
                child: Foo(_key),
              );
              break;
            case 1:
              return Container(
                color: Colors.blue[100],
                child: Text("Blank Page"),
              );
              break;
            case 2:
              return Container(
                color: Colors.red[100],
                child: Foo(_key),
              );
              break;
            default:
              throw "404";
          }
        },
      ),
    );
  }
}

class Foo extends StatefulWidget {
  @override
  _FooState createState() => _FooState();

  Foo(key) : super(key: key);
}

class _FooState extends State<Foo> {
  bool _switchValue = false;
  double _sliderValue = 0.5;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Switch(
          value: _switchValue,
          onChanged: (v) {
            setState(() => _switchValue = v);
          },
        ),
        Slider(
          value: _sliderValue,
          onChanged: (v) {
            setState(() => _sliderValue = v);
          },
        )
      ],
    );
  }
}
WSBT
  • 33,033
  • 18
  • 128
  • 133
  • Hello user1032613, I just want to clarify that this was not my approach it was Emily Fortuna's. I was just try to explain how it can be implemented rather than that I completely agree with you – Saed Nabil Jun 08 '20 at 12:59
  • @SaedNabil Thanks for the clarification. I believe that might be the case, since her video clearly shows two widgets appearing on the same frame. However, do you have any supporting evidence that Emily used the exact method you mentioned, perhaps her blog or GitHub etc? – WSBT Jun 08 '20 at 15:58
  • Hi, I would like to differentiate between concept/approach & implementation ,I am not aware of her implementation, but clearly it is my implementation for her approach I do not think that Emily was recommending this approach either as she is a very knowledgeable about flutter ,I believe she was mentioning a possibility/capability of using Global keys to access/modify state of a widget in the context of explaining Keys in general , Back then the state management was not clear and more debatable topic than now beside I am not aware of any different way that Globalkey could possibly had been used – Saed Nabil Jun 08 '20 at 16:27
  • 1
    @SaedNabil Thanks, I have edited my answer accordingly. Also, I'd like to point out that, this answer uses a different approach, in case you missed it from a quick glance. Instead of using `currentState` to access another widget's state (thus differentiating the "main widget" and "the clone"), this answer literally let Flutter link different widget instances to a single state. – WSBT Jun 08 '20 at 22:10
  • thanks , I knew the concept of moving widget across the widget tree but never tried implementing it before, because in real life applications ,state should be moved away to a centralized class so business logic can manipulate and then update the ui accordingly , it may be useful when you have animation widget which still need to hold its state inside , so nice to see a proof of concept so what you did is rediscover a piece of the "ancient puzzle" of GlobalKeys which can be used with caution and limitations you yourself mentioned in your answer :) – Saed Nabil Jun 09 '20 at 12:44
  • Any pointers to use the same widget in routes (push & Pop) ? I know its not best practice but we have a web-view to be shown across the app and its heavy. Loading the bundle every time when we push the route is extremely bad UX. Please help – john7ric Jul 11 '21 at 02:09
22

Update: this was an old approach to tackle the state management and not recommended anymore,please see my comments on this answer and also check user1032613's answer below

Global keys can be used to access the state of a statefull widget from anywhere in the widget tree

enter image description here

import 'package:flutter/material.dart';

main() {
  runApp(MaterialApp(
    theme: ThemeData(
      primarySwatch: Colors.indigo,
    ),
    home: App(),
  ));
}

class App extends StatefulWidget {
  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  GlobalKey<_CounterState> _counterState;

  @override
  void initState() {
    super.initState();
    _counterState = GlobalKey();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
          child: Column(
        children: <Widget>[
          Counter(
            key: _counterState,
          ),
        ],
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.navigate_next),
        onPressed: () {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (context) {
              return Page1(_counterState);
            }),
          );
        },
      ),
    );
  }
}

class Counter extends StatefulWidget {
  const Counter({
    Key key,
  }) : super(key: key);

  @override
  _CounterState createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count;

  @override
  void initState() {
    super.initState();
    count = 0;
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            setState(() {
              count++;
            });
          },
        ),
        Text(count.toString()),
      ],
    );
  }
}
class Page1 extends StatefulWidget {
  final GlobalKey<_CounterState> counterKey;
  Page1( this.counterKey);
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Row(
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.add),
              onPressed: () {
                setState(() {
                  widget.counterKey.currentState.count++;
                  print(widget.counterKey.currentState.count);
                });
              },
            ),
            Text(
              widget.counterKey.currentState.count.toString(),
              style: TextStyle(fontSize: 50),
            ),
          ],
        ),
      ),
    );
  }
}
Saed Nabil
  • 6,705
  • 1
  • 14
  • 36
  • How would you set _CounterState as the type when creating the global key _counterState when _CounterState has been imported from a separate package? – Brandon Pillay Apr 13 '20 at 10:05
  • 1
    @BrandonPillay You can use final counterKey; instead of final GlobalKey<_CounterState> counterKey; so it will be inferred , if you insist to use type you need to convert _CounterState from private to global CounterState so you can import it from another package. – Saed Nabil Apr 13 '20 at 19:56
  • Hey @SaedNabil, I want to know when does the `build()` for `Counter` get triggered. We only do `widget.counterKey.currentState.count++;`. When we click back, the `build()` of `Counter` should not be triggered and the value should've been not updated in the UI, only as a state variable `count`. So, when does the `build()` get triggered for `Counter` Widget? – Harsh Jun 02 '20 at 07:14
  • @Harsh Hey, it is so sad that flutter team may have changed the behavior of the navigator and now it is not rebuild automatically , but it is understandable as the state management is a developing active matter in flutter, I have to admit to you that this way is not recommended anymore to manage your app state ,if you are advanced developer use Inherited widget to force rebuild if not use the much more human approach to it which is Provider package to manage your state https://pub.dev/packages/provider – Saed Nabil Jun 02 '20 at 12:55