7

i need some help understanding how to obtain data from inherited widget.

I usually get the parameter from my widget directly from the build method using

  @override
  Widget build(BuildContext context) {    
   //THIS METHOD
   var data = StateContainer.of(context).data;

   return Container(child:Text("${data.parameter}"));
  }

But this method cant be called from initState since there is no buildContext yet.

I need in the initState method to have that parameter (i call my fetch from server in that and i need to pass that data to my function), so, how should i do it?

@override
void initState() {
 otherData = fetchData(data);
 super.initState();
}

I tried using didChangeDipendencies() but it is called every time the view is rebuilt (popping from screen, etc.) so it is not what i want to use and neither the FutureBuilder widget.

Any suggestion?

Cristian Bregant
  • 1,797
  • 1
  • 16
  • 22

5 Answers5

13

First, note that you probably do want to use didChangeDependencies. But you can't just do your call there without any check. You need to wrap it in an if first.

A typical didChangeDependencies implementation should look similar to:

Foo foo;

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  final foo = Foo.of(context);
  if (this.foo != foo) {
    this.foo = foo;
    foo.doSomething();
  }
}

Using such code, doSomething will be executed only when foo changes.


Alternatively, if you are lazy and know for sure that your object will never ever change, there's another solution.

To obtain an InheritedWidget, the method typically used is:

BuildContext context;
InheritedWidget foo = context.inheritFromWidgetOfExactType(Foo);

and it is this method that cannot be called inside initState.

But there's another method that does the same thing:

BuildContext context;
InheritedWidget foo = context.ancestorInheritedElementForWidgetOfExactType(Foo)?.widget;

The twist is: - this method can be called inside initState - it won't handle the scenario where the value changed.

So if your value never changes, you can use that instead.

Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
1

1, If you only need InheritedWidget as a Provider of parameter for Widget. You can using on initState as bellow:

@override
void initState() {
    super.initState();
    var data = context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
}

2, If you need listener to re-render widget when data of InheritedWidget change. I suggest you wrapper your StatefulWidget insider a StatelessWidget, parameter of StatefulWidget is passed from StatelessWidget, when InheritedWidget change data, it will notify to StatelessWidget, on StatefulWidget we will get change on didChangeDependencies and you can refresh data. This is code guide:

class WrapperDemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    DemoData data = StateContainer.of(context).data;
    return Container();
  }
}

class ImplementWidget extends StatefulWidget {

  DemoData data;

  ImplementWidget({this.data});

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

class _ImplementWidgetState extends State<ImplementWidget> {

  @override
  void initState() {
    super.initState();
   //TODO Do sth with widget.data
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //TODO Do change with widget.data
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}
dangngocduc
  • 1,588
  • 8
  • 13
1

I tried Remi's solution but I think the ancestorInheritedElementForWidgetOfExactType method is deprecated now? I couldn't find it.

Used this instead:

BuildContext context;
InheritedWidget foo = context.findAncestorWidgetOfExactType<Foo>()?
Iain Smith
  • 9,230
  • 4
  • 50
  • 61
-1

I prefer the solution with didChangeDependencies because Future.delayed solution is a bit hack, looks unprofessional and unhealthy. However, it works out of the box.

This is the solution I prefer:

class _MyAppState extends State<MyApp> {

    bool isDataLoaded = false;

    @override
  void didChangeDependencies() {
    if (!isDataLoaded) {
            otherData = fetchData(data).then((_){
                this.isDataLoaded = true;
            });
    }
    super.didChangeDependencies();
  }
...
Mehmet Esen
  • 6,156
  • 3
  • 25
  • 44
  • 1
    While better than `Future.delayed`, a boolean misses the point of the life-cycle, that is handling the scenario where the data changes. – Rémi Rousselet Jul 23 '19 at 18:10
-2

You can also get the context in initState, try using a future with duration zero. You can find some examples here

void initState() {
    super.initState();
      Future.delayed(Duration.zero,() {
        //use context here
      showDialog(context: context, builder: (context) => AlertDialog(
          content: Column(
            children: <Widget>[
              Text('@todo')
            ],
          ),
          actions: <Widget>[
            FlatButton(onPressed: (){
              Navigator.pop(context);
            }, child: Text('OK')),
          ],
        ));
      });
  }

i use it to make loading screens using inherited widgets and avoid some global variables

RegularGuy
  • 3,516
  • 1
  • 12
  • 29