I'm confused about the Flutter documentation on loading text assets.
It states:
Each Flutter app has a
rootBundle
object for easy access to the main asset bundle. It is possible to load assets directly using therootBundle
global static frompackage:flutter/services.dart
.However, it’s recommended to obtain the
AssetBundle
for the currentBuildContext
usingDefaultAssetBundle
, rather than the default asset bundle that was built with the app; this approach enables a parent widget to substitute a differentAssetBundle
at run time, which can be useful for localization or testing scenarios.
I don't understand how I can implement text asset loading the recommended way, while at the same time avoiding to load the assets each time the widget is built.
Consider the following naive example that uses a FutureBuilder
to display some text:
@override
Widget build(BuildContext context) {
var greeting = DefaultAssetBundle.of(context).loadString('assets/greeting');
return FutureBuilder<String>(
future: greeting,
builder (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.requireData);
} else {
return Text('Loading greeting...');
}
}
);
}
In the example, loadString
is called whenever the widget is built. This seems inefficient to me.
Also, it goes explicitly against the FutureBuilder
documentation, which tells me that:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
Now, I could go ahead and load my assets in any of the recommended methods, but none of them has a BuildContext
. Meaning I'd have to use the rootBundle
, which wasn't recommended.
Since I'm new to Flutter, I'm unsure if the documentation is contradicting itself or if there's some obvious thing I'm missing here. Any clarification would be much appreciated.
I came up with the following solution for loading assets the way it's recommended in the Flutter docs.
Load the assets in a Widget's State and assign the Future to a nullable instance variable. This works in conjunction with FutureBuilder. Here's an example:
class MyWidgetState extends State<MyWidget> {
Future<String>? _greeting;
@override
Widget build(BuildContext context) {
// Wrapping `loadString` in a condition such as the following ensures that
// the asset is loaded no more than once. Seems kinda crude, though.
if (_greeting == null) {
_greeting = DefaultAssetBundle.of(context).loadString('assets/greeting');
}
return FutureBuilder<String>(
future: greeting,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.requireData);
} else {
return Text('Loading greeting...');
}
});
}
}
This approach ensures that both recommendations from the Flutter docs are honored:
Bundle
for the current BuildContext
.FutureBuilder
restarts each build.