110

Is there a way to force Flutter to redraw all widgets (e.g. after locale change)?

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
DogeLion
  • 3,371
  • 5
  • 16
  • 24

11 Answers11

47

Your Widget should have a setState() method, everytime this method is called, the widget is redrawn.

Documentation : Widget setState()

Piyush
  • 693
  • 1
  • 7
  • 12
Alexi Coard
  • 7,106
  • 12
  • 38
  • 58
  • You're saying I should replace the state of the top-level widget (root)? What if I don't have access to that widget. I want to tell Flutter to restart essentially. – DogeLion May 04 '17 at 12:56
  • You want to reload the whole app ? – Alexi Coard May 04 '17 at 13:06
  • 3
    I want to force the app (incl. views deeper in the view stack) to be redrawn (as the title says). And from the place I'll be triggering this I don't have access to the root widget(s). – DogeLion May 04 '17 at 16:16
  • do you mean that if I had 2 setState's then the widget will rebuild twice? – Samer Alkhatib Jun 13 '21 at 06:53
  • @SamerAlkhatib, I would answer yes, if placed in series... – emanuel sanga Oct 31 '21 at 08:12
  • This is an incorrect answer, calling setState on the root widget is not enough to cause a rebuild on all descendants. Proper way is to use a key: https://stackoverflow.com/a/73129922/463029 – shawnblais Nov 01 '22 at 15:42
40

Old question, but here is the solution:

In your build method, call the rebuildAllChildren function and pass it the context:

@override
Widget build(BuildContext context) { 
  rebuildAllChildren(context);
  return ...
}

void rebuildAllChildren(BuildContext context) {
  void rebuild(Element el) {
    el.markNeedsBuild();
    el.visitChildren(rebuild);
  }
  (context as Element).visitChildren(rebuild);
}

This will visit all children and mark them as needing to rebuild. If you put this code in the topmost widget in your widgets tree, it will rebuild everything.

Also note you must order that specific widget to rebuild. Also you could have some boolean so that the rebuild of that widget only rebuilds all of its children when you really need it (it's an expensive operation, of course).


IMPORTANT: This is a hack, and you should only do this if you know what you are doing, and have strong reason to do so. One example where this is necessary is in my internationalization package: i18_extension. As Collin Jackson explained in his answer, you are really not supposed to do this in general.

Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76
Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • 1
    This is still working for me to force rebuild when I use table_calendar widget in a bottomsheet. Somehow setState is not powerful enough to do it – Dika May 19 '21 at 16:58
  • 2
    @Dika you are probably calling setState in the widget that opened the bottomsheet. The bottomsheet is in the Overlay, not inside the widget that opened it. So it can't rebuild. It will probably work if you call setState in the table_calendar state or in the bottomsheet state. – Marcelo Glasberg May 19 '21 at 20:00
  • For a less hacky way of doing this, just generate a new UniqueKey() each time you want to rebuild: https://stackoverflow.com/a/73129922/463029 – shawnblais Nov 01 '22 at 15:43
29

This type of use case, where you have data that children can read but you don't want to explicitly pass the data to the constructor arguments of all your children, usually calls for an InheritedWidget. Flutter will automatically track which widgets depend on the data and rebuild the parts of your tree that have changed. There is a LocaleQuery widget that is designed to handle locale changes, and you can see how it's used in the Stocks example app.

Briefly, here's what Stocks is doing:

  • Put a callback on root widget (in this case, StocksApp) for handling locale changes. This callback does some work and then returns a customized instance of LocaleQueryData
  • Register this callback as the onLocaleChanged argument to the MaterialApp constructor
  • Child widgets that need locale information use LocaleQuery.of(context).
  • When the locale changes, Flutter only redraws widgets that have dependencies on the locale data.

If you want to track something other than locale changes, you can make your own class that extends InheritedWidget, and include it in the hierarchy near the root of your app. Its parent should be a StatefulWidget with key set to a GlobalKey that accessible to the children. The State of the StatefulWidget should own the data you want to distribute and expose methods for changing it that call setState. If child widgets want change the State's data, they can use the global key to get a pointer to the State (key.currentState) and call methods on it. If they want to read the data, they can call the static of(context) method of your subclass of InheritedWidget and that will tell Flutter that these widgets need to rebuilt whenever your State calls setState.

Collin Jackson
  • 110,240
  • 31
  • 221
  • 152
  • 3
    Woo, I read three times and then looked at Shrine sample code in Flutter Gallery three times and still only had a vague understanding. Trying out this. – Zhenlei Cai Jan 07 '18 at 01:35
  • Is that example still in the Stock example? – Ride Sun Feb 07 '18 at 19:20
  • 2
    Hi Collin, since the localeQuery widget has been removed, you may want to update your answer. Details see https://github.com/flutter/flutter/issues/1269 – Musen Jul 04 '18 at 04:45
  • 94
    Why not just have `Flutter.redrawAllWidgetsBecauseISaidSo();`? – TimSim Jul 25 '19 at 19:14
  • This doesn't address the question at all, OP is just looking for a way to trigger all descendant widgets to rebuild. Simplest way is to inject a key somewhere in the tree, it can be a `UniqueKey` if you want to repeatedly refresh, or `ValueKey(Locale.name)` if you want to rebuild when specific values actually change. https://stackoverflow.com/a/73129922/463029 – shawnblais Nov 01 '22 at 15:44
15

Simply Use:

Navigator.popAndPushNamed(context,'/screenname');

Whenever you need to refresh :)

Toshik Langade
  • 758
  • 7
  • 13
15

Just use a Key on one of your high-level widgets, everything below this will lose state:

Key _refreshKey = UniqueKey();

void _handleLocaleChanged() => setState((){ 
  _refreshKey = UniqueKey() 
});

Widget build(BuildContext context){
  return MaterialApp(
    key: _refreshKey ,
    ...

  )
}

You could also use a value key like:

return MaterialApp(
  key: ValueKey(locale.name)
  ...
);
shawnblais
  • 1,038
  • 11
  • 23
  • Suspect you have a feed where items each have an individual key. Would those items get called `dispose()` upon? Is using this method bad practice for widget state lifetime? – Paul May 17 '23 at 10:58
14

Refreshing the whole widget tree might be expensive and when you do it in front of the users eyes that wouldn't seem sweet.

so for this purpose flutter has ValueListenableBuilder<T> class. It allows you to rebuild only some of the widgets necessary for your purpose and skip the expensive widgets.

you can see the documents here ValueListenableBuilder flutter docs
or just the sample code below:

  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:'),
        ValueListenableBuilder(
          builder: (BuildContext context, int value, Widget child) {
            // This builder will only get called when the _counter
            // is updated.
            return Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Text('$value'),
                child,
              ],
            );
          },
          valueListenable: _counter,
          // The child parameter is most helpful if the child is
          // expensive to build and does not depend on the value from
          // the notifier.
          child: goodJob,
        )
      ],
    ),
  ),
  floatingActionButton: FloatingActionButton(
    child: Icon(Icons.plus_one),
    onPressed: () => _counter.value += 1,
  ),
);

And also never forget the power of setState(() {});

Taba
  • 3,850
  • 4
  • 36
  • 51
  • 4
    Where's _counter declared? – Marvin Aug 03 '20 at 16:38
  • Check [this example](https://api.flutter.dev/flutter/widgets/ValueListenableBuilder-class.html). – MD. Oct 19 '21 at 07:17
  • 1
    Not answering the question, after something like Locale change you need to rebuild the entire tree one time from the top down. To do this with ValueListenable you'd need to insert this into every single Widget in your app and bind it to the current locale (or whatever). Instead use a Key to force all descendants to rebuild: https://stackoverflow.com/a/73129922/463029 – shawnblais Nov 01 '22 at 15:46
13

I explain how to create a custom 'AppBuilder' widget in this post.

https://hillelcoren.com/2018/08/15/flutter-how-to-rebuild-the-entire-app-to-change-the-theme-or-locale/

You can use the widget by wrapping your MaterialApp with it, for example:

Widget build(BuildContext context) {
  return AppBuilder(builder: (context) {
    return MaterialApp(
      ...
    );
  });
}

You can tell the app to rebuild using:

AppBuilder.of(context).rebuild();
Hillel Coren
  • 429
  • 2
  • 10
  • 28
7

What might work for your use case is using the Navigator to reload the page. I do this when switching between "real" and "demo" mode in my app. Here's an example :

Navigator.of(context).push(
    new MaterialPageRoute(
        builder: (BuildContext context){
          return new SplashPage();
        }
    )
);

You can replace "new SplashPage()" in the above code with whatever main widget (or screen) you would like to reload. This code can be called from anywhere you have access to a BuildContext (which is most places in the UI).

AdaJane
  • 97
  • 3
  • 8
1

Why not just have Flutter.redrawAllWidgetsBecauseISaidSo();? – TimSim

There kinda is: Change to key to redraw statefull child widgets.

Jelena Lecic explained it good enough for me on medium.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  var _forceRedraw; // generate the key from this

  void _incrementCounter() {
    setState(() {
      _counter++;
      _forceRedraw = Object();
    });
  }

  @override
  void initState() {
    _forceRedraw = Object();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            MyStatefullTextWidget(
              key: ValueKey(_forceRedraw),
              counter: _counter,
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

class MyStatefullTextWidget extends StatefulWidget {
  final int counter;
  const MyStatefullTextWidget({
    required this.counter,
    Key? key,
  }) : super(key: key);

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

class _MyStatefullTextWidgetState extends State<MyStatefullTextWidget> {
  @override
  Widget build(BuildContext context) {
    return Text(
      'You have pushed the button this many times:${widget.counter}',
    );
  }
}


  • That article is behind a paywall. Please explain your code. – Ian Rajkumar Sep 26 '21 at 20:16
  • @IanRajkumar try to delete all cookies in your browser for the medium page. I feel your pain. – ǝlpoodooɟƃuooʞ Sep 27 '21 at 10:18
  • 1
    Thank you, I had problem when calling function passed down from parent screen. When I called it from child screen, the child screen doesn't get rebuilt (child screen is stateless widget). I use your solution and it works, the child screen get rebuilt. – PercyPham Mar 21 '22 at 15:15
-1

I my case it was enough to reconstruct the item.

Changed:

          return child;
        }).toList(),

To:

          return SetupItemTypeButton(
            type: child.type,
            icon: child.icon,
            active: _selected[i] == true,
            onTap: ...,
          );
        }).toList(),
class SetupItemTypeButton extends StatelessWidget {
  final dynamic type;
  final String icon;
  estureTapCallback onTap;

  SetupItemTypeButton({Key? key, required this.type, required this.icon, required this.onTap}) : super(key: key);

  @override
  Widget build(BuildContext context) {
   return Container();
  }
}

class SetupItemsGroup extends StatefulWidget {
  final List<SetupItemTypeButton> children;
  final Function(int index)? onSelect;

  SetupItemsGroup({required this.children, this.onSelect});

  @override
  State<SetupItemsGroup> createState() => _SetupItemsGroupState();
}

class _SetupItemsGroupState extends State<SetupItemsGroup> {
  final Map<int, bool> _selected = {};

  @override
  Widget build(BuildContext context) {
    int index = 0;

    return Container(
      child: GridView.count(
        children: widget.children.map((child) {
          return SetupItemTypeButton(
            type: child.type,
            icon: child.icon,
            active: _selected[i] == true,
            onTap: () {
              if (widget.onSelect != null) {
                int i = index++;
                child.active = _selected[i] == true;

                setState(() {
                  _selected[i] = _selected[i] != true;
                  child.onTap();
                  widget.onSelect!(i);
                });
              }
            },
          );
        }).toList(),
      ),
    );
  }
}
Ido
  • 2,034
  • 1
  • 17
  • 16
-2

Simply Use:

Navigator.popAndPushNamed(context,'/xxx');
zhaopf
  • 17