Ted Henry
Ted Henry

Reputation: 1492

Accessing Flutter context when creating StatefulWidget

I'm having trouble accessing a services object when initializing a stateful widget. The problem comes from the context object not being available in initState.

I'm using InheritedWidget to inject a services object in my main.dart file like so

void main() async {
  final sqflite.Database database = await _openDatabase('db.sqlite3');
  runApp(
    Services(
      database: database,
      child: MyApp(),
    ),
  );
}

The Services object is quite straightforward. It will have more than just the database as a member. The idea is that the widgets don't need to know if a local database, local cache, or remote server is being accessed.

class Services extends InheritedWidget {
  final Database database;

  const Services({
    Key key,
    @required Widget child,
    @required this.database,
  })  : assert(child != null),
        assert(database != null),
        super(key: key, child: child);

  Future<List<models.Animal>> readAnimals() async {
    return db.readAnimals(database: this.database);
  }

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) {
    return false;
  }

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

The trouble comes in my _HomePageState state when I want to load all the animals from the database. I need to access the Services object. I cannot access the Services object in initState so I am using didChangeDependencies. A problem comes when the home page is removed from the stack. It seems didChangeDependences is called and the access to the context object is illegal. So I created an _initialized flag that I can use in didChangeDependencies to ensure I only load the animals the first time. This seems very inelegant. Is there a better way?

class _HomePageState extends State<HomePage> {
  bool _initialized = false;
  bool _loading = false;
  List<Animal> _animals;

  @override
  Widget build(final BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(Strings.of(this.context).appName),
      ),
      body: _HomeBody(
        loading: this._loading,
        animals: this._animals,
      ),
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    if (!this._initialized) {
      this._initialized = true;
      this._loadAnimals();
    }
  }

  void _loadAnimals() async {
    this.setState(() {
      this._loading = true;
      this._animals = null;
    });

    final List<Animal> animals = await Services.of(this.context).readAnimals();

    this.setState(() {
      this._loading = false;
      this._animals = animals;
    });
  }
}

Upvotes: 2

Views: 3823

Answers (1)

diegoveloper
diegoveloper

Reputation: 103351

For that case you could use addPostFrameCallback of your WidgetsBinding instance to execute some code after your widget was built.

  _onLayoutDone(_) {
     this._loadAnimals();
  }

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
    super.initState();
  }

Upvotes: 4

Related Questions