I am trying to do a search on food recipes where an API will fetch the recipes and display them on the screen, but whenever I press on search, the food does not appear
Below is my services that handles fetching data from the API
class SearchFoodService {
static late String foodToBeSearched;
late int foodTotalNumber;
List foodIdSearchList = [];
List foodNameList = [];
List foodImageList = [];
List instructionsList = [];
List foodDurationList = [];
List foodIdList = [];
List calories = [];
List fats = [];
List carbohydrates = [];
List proteins = [];
final String apiKey = '*************************';
searchFood() async {
try {
http.Response response = await http.get(
Uri.parse(
'https://api.spoonacular.com/recipes/complexSearch?apiKey=$apiKey&query=$foodToBeSearched&number=1'),
);
dynamic foodData = response.body;
Map data = json.decode(foodData);
foodTotalNumber = data['number'];
for (int i = 0; i < foodTotalNumber; i ) {
int foodId = data['results'][i]['id'];
foodIdSearchList.add(foodId);
}
} catch (e) {
print('fetching error');
}
for (int i = 0; i < foodIdSearchList.length; i ) {
try {
http.Response response = await http.get(
Uri.parse(
'https://api.spoonacular.com/recipes/${foodIdSearchList[i]}/information?apiKey=$apiKey&includeNutrition=true'),
);
dynamic foodData = response.body;
Map data = json.decode(foodData);
String foodName = data['title'];
foodNameList.add(foodName);
String foodImage = data['image'];
foodImageList.add(foodImage);
int foodDuration = data['readyInMinutes'];
foodDurationList.add(foodDuration);
var foodCalories = data['nutrition']['nutrients'][0]['amount'];
var foodCarbs = data['nutrition']['nutrients'][3]['amount'];
var foodFat = data['nutrition']['nutrients'][1]['amount'];
var foodProtein = data['nutrition']['nutrients'][9]['amount'];
String foodInstruction = data['instructions'];
instructionsList.add(foodInstruction);
calories.add(foodCalories);
carbohydrates.add(foodCarbs);
fats.add(foodFat);
proteins.add(foodProtein);
} catch (p) {
print('p ---> $p');
}
}
return [
foodNameList,
foodImageList,
foodDurationList,
calories,
carbohydrates,
fats,
proteins,
instructionsList,
];
}
}
Below is the search bar where I have a TextEditingcontroller
bool textInBar = false;
class SearchBar extends StatefulWidget {
const SearchBar({
Key? key,
}) : super(key: key);
@override
State<SearchBar> createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
final textItem = TextEditingController();
@override
Widget build(BuildContext context) {
return TextField(
controller: textItem,
style: constants.TextStyles.subTitle,
cursorColor: Colors.black54,
decoration: InputDecoration(
suffixIcon: IconButton(
onPressed: () {
textInBar = true;
setState(() {
SearchFoodService.foodToBeSearched = textItem.text;
});
},
icon: const Icon(
Icons.search,
color: Colors.black54,
size: 35,
),
),
hintText: 'Search any recipe',
hintStyle: constants.TextStyles.title
.copyWith(fontSize: 20, color: Colors.black),
fillColor: const Color(0xFFf2f2f2),
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(
color: Color(0xFFf2f2f2),
width: 0,
),
borderRadius: BorderRadius.circular(25),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(width: 0, color: Color(0xFFf2f2f2)),
borderRadius: BorderRadius.circular(25),
),
),
);
}
}
And finally below is the screen which is to display the items on the screen
class SearchScreen extends StatefulWidget {
static String id = 'search_screen';
showContent() {
return Expanded(
child: FutureBuilder(
future: SearchFoodService().searchFood(),
builder: (context, dynamic snapshot) {
if (snapshot.hasData) {
List recipeName = snapshot.data[0] ?? [];
List images = snapshot.data[1] ?? [];
List foodDuration = snapshot.data[2] ?? [];
List foodCalories = snapshot.data[3] ?? [];
List foodCarbs = snapshot.data[4] ?? [];
List foodFat = snapshot.data[5] ?? [];
List foodProteins = snapshot.data[6] ?? [];
List foodInstruction = snapshot.data[7] ?? [];
return GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 15,
childAspectRatio: 0.65,
),
itemBuilder: (context, index) {
return FoodContainer(
foodLabel: recipeName[index],
foodImage: images[index],
time: foodDuration[index],
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FoodScreen(
foodName: recipeName[index],
image: images[index],
foodTime: foodDuration[index],
calories: foodCalories[index],
protein: foodProteins[index],
carbs: foodCarbs[index],
fat: foodFat[index],
foodInstructions: foodInstruction[index],
),
),
);
},
);
},
);
} else if (snapshot.hasError) {
return const Text('Error');
} else {
return const Align(child: CircularProgressIndicator());
}
},
),
);
}
const SearchScreen({Key? key}) : super(key: key);
@override
_SearchScreenState createState() => _SearchScreenState();
}
class _SearchScreenState extends State<SearchScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
const Padding(
padding: EdgeInsets.only(top: 8.0, left: 15, right: 15),
child: SearchBar(),
),
textInBar == false ? Text('Search') : widget.showContent(),
],
),
),
);
}
}
Whenever I put a food recipe, for example pizza, in place of the foodToBeSearched in the services file, the food loads automatically after hot reloading. That means maybe there might be a problem with the algorithm for assigning foodToBeSearch = textItem.text;.
CodePudding user response:
This code:
setState(() {
SearchFoodService.foodToBeSearched = textItem.text;
});
will not force a new build, since the build method of your StatefulWidget does not use this variable. SearchFoodService class uses it, but that is not a widget class, so setState will have no effect.
I would suggest to add the search term to your State<SearchScreen> class:
String? _foodToBeSearched;
Then use this member in the future. You have to move the FutureBuilder to the State<SearchScreen> class, and use for example like this:
future: SearchFoodService().searchFood(_foodToBeSearched),
And your setState should set this member:
setState(() {
_foodToBeSearched = textItem.text;
});
This will rebuild the widget, and the future will be executed using the entered search term as parameter.
