Jus10
Jus10

Reputation: 15679

Flutter: Inherited Widget and Routes

I want to have an inherited widget at the root of my application, which will contain my data providers, which I would use throughout the app. So I have this inherited widget, but every time I try to load it I get this The getter 'data' was called on null and I can't figure out why.

So here's my main.dart:

void main() => runApp(new MatAppRoot());

class MatAppRoot extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'MyCoolApp',
      routes: <String, WidgetBuilder>{
        'Login': (BuildContext context) => new LoginPage(),
        'Cool': (BuildContext context) => new CoolPage(),
      },
      home: new CoolApp(),
    );
  }
}

class CoolAppextends StatefulWidget {

  final Widget child;

  CoolApp({this.child});

  @override
  CoolAppState createState() => new CoolAppState();

  static CoolAppState of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data;
  }
}

class CoolAppState extends State<CoolApp> {
  String randomString = 'AYEEAS!!!';

  @override
  void initState() { super.initState();
    Navigator.of(context).pushNamedAndRemoveUntil('Login', (Route<dynamic> route) => false);
  }

  @override
  Widget build(BuildContext context) {
    return new CoolInherit(
      data: this,
      child: new LoginPage(),
    );
  }

}

class CoolInherit extends InheritedWidget {

  final CoolAppState data;

  CoolInherit({
    Key key,
    this.data,
    Widget child,
  }): super(
    key: key,
    child: child
  );

  @override
  bool updateShouldNotify(CoolInherit old) {
    return true;
  }
}

then my LoginPage basically redirects after the login like this:

if (logInSuccessful) {
  Navigator.of(context).pushNamedAndRemoveUntil('Cool', (Route<dynamic> route) => false);
}

In my Cool page I try to load another page when clicking a button like this:

viewCoolDetails() {
  Navigator.push(
    context,
    new MaterialPageRoute(builder: (context) => new CoolDetailsPage()),
  );
}

but in my CoolDetailsPage it crashes when I do this:

@override
Widget build(BuildContext context) {
  final inheritedWidget = CoolApp.of(context);
  print(inheritedWidget.randomString);   <-- ERROR: The getter 'data' was called on null
  return new Text('Cool!');
}

Error Details:

I/flutter ( 6129): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 6129): The following NoSuchMethodError was thrown building CoolDetailsPage(dirty, state:
I/flutter ( 6129): _CoolDetailsPage#ba0bb):
I/flutter ( 6129): The getter 'data' was called on null.
I/flutter ( 6129): Receiver: null
I/flutter ( 6129): Tried calling: data
I/flutter ( 6129):
I/flutter ( 6129): When the exception was thrown, this was the stack:
I/flutter ( 6129): #0      Object.noSuchMethod (dart:core/runtime/libobject_patch.dart:46:5)
I/flutter ( 6129): #1      CoolApp.of (/lib/main.dart:56:83)
... etc etc

main.dart:56 is return (context.inheritFromWidgetOfExactType(CoolInherit) as CoolInherit).data; and so if my detective work is up to par, I suspect it is something to with navigations/context, which is preventing my final widget from accessing the inheritedWidget, but I'm not sure about that.

UPDATE:

the best I can tell, I need to insert my InheritedWidget at a higher level; before the navigator. so I inserted this into the MaterialApp:

builder: (context, child) {
  return new CoolApp(child: child);
},

but that didn't seen to work...

E/flutter (32321): [ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter (32321): Navigator operation requested with a context that does not include a Navigator.
E/flutter (32321): The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.
E/flutter (32321): #0      Navigator.of.<anonymous closure> (package:flutter/src/widgets/navigator.dart:1180:9)
E/flutter (32321): #1      Navigator.of (package:flutter/src/widgets/navigator.dart:1187:6)

Upvotes: 5

Views: 6826

Answers (2)

John Stef
John Stef

Reputation: 51

I had the same problem for a long time and I realized that if you wrap the MaterialApp with the Inherited Widget, your data is accessible through the entire app. But in your case, you need to pass data after the user login so to do that you need to create a new Navigator and wrap it with your Inherited Widget. You can see this project https://github.com/johnstef99/inherited-widget-demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

Route generatePage(child) {
  return MaterialPageRoute(builder: (context) => child);
}

class MyNav extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MyData(
      data: 'omg',
      child: Navigator(
        onGenerateRoute: (settings) {
          switch (settings.name) {
            case 'page1':
              return generatePage(PageOne());
            case 'page2':
              return generatePage(PageTwo());
            case 'page3':
              return generatePage(PageThree());
          }
        },
        initialRoute: 'page1',
      ),
    );
  }
}

class MyData extends InheritedWidget {
  MyData({Key key, this.child, this.data}) : super(key: key, child: child);

  final Widget child;
  final String data;

  static MyData of(BuildContext context) {
    return (context.inheritFromWidgetOfExactType(MyData) as MyData);
  }

  @override
  bool updateShouldNotify(MyData oldWidget) {
    return true;
  }
}

class PageOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 1'),
      ),
      backgroundColor: Colors.red,
      body: RaisedButton(
        child: Text("Goto page 2, data=${MyData.of(context).data}"),
        onPressed: () {
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (_) => PageTwo()));
        },
      ),
    );
  }
}

class PageTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 2'),
      ),
      backgroundColor: Colors.red,
      body: RaisedButton(
        child: Text("Goto page 3, data=${MyData.of(context).data}"),
        onPressed: () {
          Navigator.of(context)
              .push(MaterialPageRoute(builder: (_) => PageThree()));
        },
      ),
    );
  }
}

class PageThree extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 3'),
      ),
      backgroundColor: Colors.green,
      body: RaisedButton(
        child: Text("Goto page 4, data=${MyData.of(context).data}"),
        onPressed: null,
      ),
    );
  }
}

Upvotes: 5

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277037

That is because you're trying to access CoolApp which is in the route / from another route (dynamic).

But inside your dynamic route, there's no CoolApp. So CoolApp.of(context) returns null, and therefore accessing .data crashes.

You need to find a way to have a CoolApp instance inside your new route.

For more informations, take a look at Get access to the context of InheritedWidget

Upvotes: 3

Related Questions