2

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; }); } } 
1
  • @pskink I don't have to use InheritedWidget directly but it seems like a common way to inject dependencies in the widget tree. Would ScopedModel or Provider make a difference? I thought they were based on InheritedWidget anyway. Commented Jun 24, 2019 at 4:52

1 Answer 1

4

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(); } 
Sign up to request clarification or add additional context in comments.

1 Comment

If I did this, would I need the _initialized member? Your code snippet doesn't check the value of _initialized to prevent the call to _loadAnimals.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.