I am trying to fetch data from an API, and although data is fetched for a hardcoded request, I am unable to get it dynamically.
It gives the following error:
E/flutter (11813): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: RangeError (index): Invalid value: Valid value range is empty: 0
E/flutter (11813): #0 List.[] (dart:core-patch/growable_array.dart:264:36)
E/flutter (11813): #1 new Album.fromJson (package:todoapp/models/apihandler.dart:34:32)
E/flutter (11813): #2 fetchAlbum (package:todoapp/models/apihandler.dart:13:18)
E/flutter (11813): <asynchronous suspension>
My home screen code is :
import 'package:flutter/material.dart';
import 'package:todoapp/constants.dart';
import 'package:todoapp/models/apihandler.dart';
class NutrientFact extends StatefulWidget {
@override
State<NutrientFact> createState() => _NutrientFactState();
}
class _NutrientFactState extends State<NutrientFact> {
@override
late String inputdata;
// late Future<Album> futureAlbum;
late double proteins = 0;
late double carbs = 0;
late double fibers = 0;
late double fats = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Center(
child: Text(
'SelfTime',
style: TextStyle(fontWeight: FontWeight.bold),
)),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: TextField(
textAlign: TextAlign.center,
onChanged: (value) {
inputdata = value;
},
)),
FactEntry("Protein", proteins.toString()),
FactEntry("Carbohydrates", carbs.toString()),
FactEntry("Fats", fats.toString()),
FactEntry("Fiber", fibers.toString()),
GestureDetector(
child: Container(
alignment: Alignment.center,
margin: EdgeInsets.only(top: 10),
color: kBottomButtonColor,
width: double.infinity,
height: kBottomButtonHeight,
child: Text(
"SEARCH",
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 30),
),
),
onTap: () {
setState(() {
FutureBuilder<Album>(
future: fetchAlbum(ing: inputdata),
builder: (context, snapshot) {
if (snapshot.hasData) {
proteins = snapshot.data!.protein;
carbs = snapshot.data!.carb;
fats = snapshot.data!.fat;
fibers = snapshot.data!.fiber;
print(proteins);
} else if (snapshot.hasError) {
return Text('-----${snapshot.error}');
}
return CircularProgressIndicator();
},
);
});
},
),
],
));
}
}
class FactEntry extends StatelessWidget {
FactEntry(this.label, this.data);
late String label;
late String data;
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20),
child: Container(
decoration: kContainerDecoration,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: Text(
label,
style: TextStyle(fontSize: 20),
)),
Center(child: Text(data, style: TextStyle(fontSize: 20)))
],
),
),
),
);
}
}
And API handler is:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum({String ing = 'chicken'}) async {
final response = await http.get(
Uri.parse(
"https://api.edamam.com//api//food-database//v2//parser?app_id=$api_id&app_key=$api_key&ingr=ing&nutrition-type=cooking"),
);
if (response.statusCode == 200) {
return Album.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load album');
}
}
class Album {
final double carb;
final double protein;
final double fat;
final double fiber;
const Album({
required this.carb,
required this.protein,
required this.fat,
required this.fiber,
});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
protein: json["parsed"][0]["food"]["nutrients"]["PROCNT"],
fat: json["parsed"][0]["food"]["nutrients"]["FAT"],
fiber: json["parsed"][0]["food"]["nutrients"]["CHOCDF"],
carb: json["parsed"][0]["food"]["nutrients"]["FIBTG"]);
}
}
`
CodePudding user response:
Its seems your json["parsed"]
is empty, but you try to call first item in that, to avoid this happen you can parse your list dynamically like this:
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
protein: json["food"]["nutrients"]["PROCNT"],
fat: json["food"]["nutrients"]["FAT"],
fiber: json["food"]["nutrients"]["CHOCDF"],
carb: json["food"]["nutrients"]["FIBTG"]);
}
then change fetchAlbum to this:
Future<List<Album>> fetchAlbum({String ing = 'chicken'}) async {
final response = await http.get(
Uri.parse(
"https://api.edamam.com//api//food-database//v2//parser?app_id=$api_id&app_key=$api_key&ingr=ing&nutrition-type=cooking"),
);
if (response.statusCode == 200) {
return (jsonDecode(response.body)["parsed"] as List).map((e) => Album.fromJson(e)).toList();
} else {
throw Exception('Failed to load album');
}
}
also you need to change your FutureBuilder
to this:
FutureBuilder<List<Album>>(
future: fetchAlbum(ing: inputdata),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return CircularProgressIndicator();
default:
if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
} else {
List<Album> data = snapshot.data ?? [];
return ListView.builder(
itemBuilder: (context, index) {
proteins = data[index].protein;
carbs = data[index].carb;
fats = data[index].fat;
fibers = data[index].fiber;
print(proteins);
return Column(children: [
Text('${data[index].protein ?? ""}'),
]);
},
itemCount: data.length,
);
}
}
},
),