Building a Recipe Search App with Flutter and the Edamam API: Part 1

Tamilnambi
3 min readNov 8, 2023

--

Beyond my fascination with JavaScript, I’m deeply drawn to Flutter — the future of mobile application development. Developed by Google and adopted for a multitude of apps, Flutter is becoming the go-to choice for building cross-platform mobile applications. With this project, I aim to harness the power of Flutter to create a simple yet compelling Recipe Search App, leveraging the Edamam Recipe Search API. Join me on this journey of mobile app development, where we’ll explore the exciting possibilities that Flutter offers.

You can get the complete code for the GitHub project from this link and watch the demo video below.

Getting Started

Create a flutter project in your IDE. I use both Android Studio and VS Code. You can use others also whichever suits you. I have named this project ‘recipe_rover’.

Creating icons

Since I wanted the app to be a little professional, I have decided to add an icon to this project. For that, I have used IconKitchen. You can get a detailed view of how to add these icons to your flutter project from this article.

Start Coding

This project is going to be simple and direct. We are going to have a TextField where the user will enter the ingredient and click an ElevatedButton to get the recipes. This event will trigger the _searchRecipes function which in return accesses our Edamam recipe search API and stores the results in a list. This list is displayed using a ListView builder. I have also used a CircularProgressIndicator to show a small animation when the API is fetching the data. The complete code will look like this.

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
runApp( MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Recipe Rover',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.yellow),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

final TextEditingController _ingredientController = TextEditingController();
List<Map<String, dynamic>> recipes = [];
bool isLoading = false; // Added loading indicator state

Future<void> _searchRecipes(String ingredient) async {
const appId = 'YOUR_APPID';
const appKey = 'YOUR_APPKEY';
const endpoint = 'https://api.edamam.com/search';

print("fetching data");
try {
setState(() {
isLoading = true; // show loading indicator
});
final response = await http.get(
Uri.parse('$endpoint?q=$ingredient&app_id=$appId&app_key=$appKey'));
print("data fetched");
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['hits'] != null) {
final hits = data['hits'];
final recipeData = hits.map<Map<String, dynamic>>((hit) {
final recipe = hit['recipe'];
return {
'label': recipe['label'],
'image': recipe['image'],
'url': recipe['url'],
'ingredients': recipe['ingredientLines'],
};
}).toList();

setState(() {
recipes = recipeData;
});
}
} else {
print('Failed to fetch recipes');
}
}
catch (e) {
print('error occured + $e');
} finally {
setState(() {
isLoading = false; //hide loading indicator
});
}
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
const SizedBox(
height: 50.0,
),
Padding(padding: EdgeInsets.all(20.0),
child: TextField(
controller: _ingredientController,
decoration: const InputDecoration(
labelText: 'Enter your ingredient'),
),),
const SizedBox(
height: 10.0,
),
ElevatedButton(onPressed: () {
print("Button clicked");
_searchRecipes(_ingredientController.text);
},
child: const Text("Search for Recipes")),
if (isLoading)
CircularProgressIndicator() // Show loading indicator while fetching data
else
if (recipes.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: recipes.length,
itemBuilder: (context, index) {
final recipe = recipes[index];
return ListTile(
title: Text(recipe['label']),
subtitle: Image.network(recipe['image']),
onTap: () {},
);
})),
],
),
),
);
}
}

Please sign up on edamam website and get your own appid and appkey when you run this program.

In the next post, we will try to get the complete recipe details when a particular recipe is clicked.

Happy coding!

--

--