Reputation: 1492
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
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