I am currently working on a small clicker game for university and its my first time using flutter dart.
Ive created an upgrade tab which displays different upgrades. Thoose upgrades are being stored and loaded from a .json file
Whenever i try to open the upgrade tab, dart throws an exception -> RangeError (index): Index out of range: no indices are valid: 0 This is due to the json not being loaded yet and axeList is empty But due to dart calling build method multiple times its being loaded afterwards
Still... there is a frame with an error. Here is a slowed down gif:
Here is my code:
class Upgrades extends StatefulWidget {
const Upgrades({Key? key}) : super(key: key);
@override
State<Upgrades> createState() => _UpgradesState();
}
class _UpgradesState extends State<Upgrades> with Store {
List axeList = [];
@override
void initState() {
readJson();
super.initState();
}
Future<void> readJson() async {
final String response = await rootBundle.loadString("/axe_list.json");
final data = await json.decode(response);
setState(() {
axeList = data["axe"];
});
}
@override
Widget build(BuildContext context) {
return //I need the content of axeList in this buildMethod
Order:
- initState()
- readJson()
- build() |-> exception because axeList isnt loaded yet
- readJson ready
The problem is that i cant use async in initState:
@override
Future<void> initState() async{
// Call the readJson method when the app starts & ensure loaded
await readJson();
super.initState();
}
This will resolve in following exception: "_UpgradeState.initState() returned a Future. State.initState() must be a void method without an async keyword"
Loading the json on app startup doesnt seem to be the solution either, because Upgrades will not be called directly at Startup, but at a later point. Also id have to hand over the data in like 7 constructors
How do i ensure the async method is loaded before the build method? thx for answers <3
CodePudding user response:
You can use FutureBuilder widget to load the data asynchronously in the UI.
The following example shows how to use it.
FutureBuilder<List<dynamic>>(
future: readJson(),
builder: (context,snapShot){
switch(snapShot.connectionState){
case ConnectionState.none:
// show error widget
break;
case ConnectionState.waiting:
// show loading widget
break;
case ConnectionState.active:
// show loading widget
break;
case ConnectionState.done:
// show widget that consumes data
//final data = snapShot.data
break;
}
//error widget if future fails or not handled
return const Text("Error");
},
),
- you don't need to call setState() to build the widget again
- make sure the return type of readJson() is same as what you pass to FutureBuilder<List>().
CodePudding user response:
a FutureBuilder
will fix your problem, follow with this.
first, replace your method with this:
since we will use FutureBuilder
, there is no need to SetState(() {})
:
Future<List> readJson() async {
final String response = await rootBundle.loadString("/axe_list.json");
final data = await json.decode(response);
return data["axe"];
}
then in the build()
:
return FutureBuilder<List>(
future: readJson(),
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData) {
List axeList = snapshot.data!;
return YourMainWidget(); // here use axeList to generate the widgets
}
return const Text("no data");
},
);
replace YourMainWidget()
with your main widget where you will need the axeList
.