2

I'm new to Flutter and confused about how InheritedWidget works with routes. I'm using an SQLite database with the sqflite library. Basically, what I'm trying to achieve is, when my app is launched, I want all widgets that don't require the database to show right away. For instance, the bottomNavigationBar of my Scaffold doesn't need the database but the body does. So I want the bottomNavigationBar to show right away, and a CircularProgressIndicator to be shown in the body. Once the database is open, I want the body to show content loaded from the database.

So, in my attempt to achieve this, I use FutureBuilder before my Scaffold to open the database. While the Future is not completed, I pass null for the drawer and a CircularProgressBar for the body, and the bottomNavigationBar as normal. When the Future completes, I wrap the drawer and body (called HomePage) both with their own InheritedWidget (called DataAccessor). This seems to work, as I can access the DataAccessor in my HomePage widget. But, when I use the Navigator in my drawer to navigate to my SettingsScreen, my DataAccessor is not accessible and returns null.

Here's some example code, not using a database but just a 5 second delayed Future:

import 'package:flutter/material.dart';

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

class App extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: FutureBuilder(
        future: Future.delayed(Duration(seconds: 5)),
        builder: (context, snapshot) {
          Widget drawer;
          Widget body;
          if (snapshot.connectionState == ConnectionState.done) {
            drawer = DataAccessor(
              child: Drawer(
                child: ListView(
                  children: <Widget>[
                    ListTile(
                      title: Text("Settings"),
                      onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen()))
                    )
                  ]
                )
              )
            );
            body = DataAccessor(child: HomePage());  
          }
          else {
            drawer = null;
            body = Center(child: CircularProgressIndicator());
          }
          return Scaffold(
            drawer: drawer,
            body: body,
            bottomNavigationBar: BottomNavigationBar(
              items: <BottomNavigationBarItem>[
                BottomNavigationBarItem(icon: Container(), title: Text("One")),
                BottomNavigationBarItem(icon: Container(), title: Text("Two"))
              ]
            )
          );
        }
      )
    );
  }
}

class HomePage extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS NOT null here
        print("HomePage: ${dataAccessor == null}");
        return Text("HomePage");
    }
}

class SettingsScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        DataAccessor dataAccessor = DataAccessor.of(context); //dataAccessor IS null here
        print("SettingsScreen: ${dataAccessor == null}");
        return Text("SettingsScreen");
    }
}

class DataAccessor extends InheritedWidget {

    DataAccessor({Key key, Widget child}) : super(key: key, child: child);

    @override
    bool updateShouldNotify(InheritedWidget oldWidget) => false;

    static DataAccessor of(BuildContext context) => context.inheritFromWidgetOfExactType(DataAccessor);

}

It's possible I'm doing things wrong. Not sure how good of practice storing widgets in variables is. Or using the same InheritedWidget twice? I've also tried wrapping the entire Scaffold with my DataAccessor (and having the database as null while it is loading), but the issue still remains where I can't get my DataAccessor in my SettingsScreen.

I've read that a possible solution is to put my InheritedWidget before the MaterialApp but I don't want to resort to this. I don't want a whole new screen to show while my database is opening, I want my widgets that don't need the database to be shown. This should be possible somehow.

Thanks!

Altherat
  • 701
  • 1
  • 13
  • 21

1 Answers1

3

The solution in the last paragraph is what you need. The MaterialApp contains the Navigator which manages the routes, so for all of your routes to have access to the same InheritedWidget that has to be above the Navigator, i.e. above the MaterialApp.

Use Remi's method and you end up with a widget tree like this:

  • MyApp (has the static .of() returning MyAppState)
  • MyAppState, whose build returns _MyInherited(child: MaterialApp(...)) and whose initState starts loading the database, calling setState when loaded.

When building your home page you have access to MyAppState via .of, so can ascertain whether the database has loaded or not. If it has not, just build the database independent widgets; if it has, build all the widgets.

Richard Heap
  • 48,344
  • 9
  • 130
  • 112