Flutter MaterialApp with navigatorKey and key is not restarting

Viewed 1680

When testing, press the increment button a few times to view if the app restarts.

I was trying to "restart" a toy app in Flutter. I was using the old counter example and after modifying it the behavior is not the expected.

For example, with this code:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
  UniqueKey _materialKey;
  UniqueKey _homeKey;

  @override
  void initState() {
    super.initState();
    _awaitAndRun();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      key: _materialKey,
      navigatorKey: _navigatorKey,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(
        key: _homeKey,
        title: 'Flutter Demo Home Page',
      ),
    );
  }

  Future<void> _awaitAndRun() async {
    print('Starting delay... Please press the button before the time ends.');
    await Future<dynamic>.delayed(Duration(seconds: 5));
    setState(() {
      _materialKey = UniqueKey();
      _homeKey = UniqueKey();
    });
    print('Has been the screen reloaded?');
  }
}

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>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

The awaitAndRun function should restart the App because I am setting the _materialKey and _homeKey as new instances. As part of it, the MyHomePage widget also should be rebuilt (because the key value has changed) but it didn't. I can understand it's because the _navigatorKey is "saving" the state of the MaterialApp but the MyHomePage should be rebuild because its key has changed!

It's even more strange because if I remove the _materialKey (as shown in the code below), the MyHomePage widget gets rebuilt.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
  UniqueKey _materialKey;
  UniqueKey _homeKey;

  @override
  void initState() {
    super.initState();
    _awaitAndRun();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //key: _materialKey,
      navigatorKey: _navigatorKey,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(
        key: _homeKey,
        title: 'Flutter Demo Home Page',
      ),
    );
  }

  Future<void> _awaitAndRun() async {
    print('Starting delay... Please press the button before the time ends.');
    await Future<dynamic>.delayed(Duration(seconds: 5));
    setState(() {
      _materialKey = UniqueKey();
      _homeKey = UniqueKey();
    });
    print('Has been the screen reloaded?');
  }
}

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>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

So it doesn't have any sense for me, why does this happen? Why when I set the _materialKey the MyHomePage widget doesn't get rebuilt?

1 Answers

It's pretty normal. Flutter always target safe and clean state and doesn't keep any item usually time. You can use a lot of methods, so I think should provider

You need to game states keep to app state, so I created ToysGame(sample) and added count property, you can use and change this property so unaffected state changes.

Firstly wrap single provider instance to your app page.

  home: Provider(
    create: (context) => ToysGame(),
    child: MyHomePage(
      key: _homeKey,
      title: 'Flutter Demo Home Page',
    ),
  ),

After doesn't keep count on in page state, you should use toys state with provider.

  Text(
          Provider.of<ToysGame>(context).count.toString(),
          style: Theme.of(context).textTheme.headline4,
        ),

And you want to change value

 void _incrementCounter() {
  Provider.of<ToysGame>(context, listen: false).count++;
   setState(() {});
  }

Finally, it's done. I added on my stackhelpover repo with restart app lib name. (You read provider package and maybe other feature help for you [ change notifier or multi-provider ]) StackHelpOver Result State

Related