Recipe collection made easy: Build your own Flutter app with API integration.

Sun Mar 26 2023

10 min read

This guide is designed for beginners interested in learning how to develop a recipe list app using Flutter and API.

Throughout this guide, we will focus on constructing an API structure for the project and integrating it into the app.

It's important to note that this tutorial won't cover creating customized widgets or other layout designs.

Step 1: Setting up the Project and Installing Dependencies

If you're starting a new project in Flutter, it's essential to configure and install the necessary dependencies before integrating your new app from scratch. In this first step, we'll walk you through setting up your project and installing the required dependencies.

First, open your main.dart file and remove everything except MyApp and StatelessWidget. Modify the title to Food Recipe, disable the debug banner, and add white as the primary color for the body text in the theme data. For now, set the home widget to an empty scaffold.

Now, go to pubspec.yaml and turn off the null safety feature.

yaml
environment:
sdk: ">=2.7.0 <3.0.0"

Finally, we need to set up the dependencies we'll use in our project. Add http: ^0.13.1 to your dependencies section in the pubspec.yaml file. Here's what your dependencies section should look like so far:

yaml
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
http: ^0.13.1

Step 2: Developing the Project Structure

Organising your code is crucial for making it reusable and easy to manage. We'll create the right project structure for our recipe list app in this second step.

First, navigate to the lib folder and create two new folders: models and views. Create two files inside the models folder: recipe.dart and recipe.api.dart. Then, inside the views folder, create home.dart and a new folder called widgets. Finally, create a new file called recipe_card.dart inside the widgets folder.

To clarify, here is a breakdown of the folder structure:

  • models : this folder will contain our Recipe class and API class.

    • recipe.dart : this file will contain our Recipe object.
    • recipe.api.dart : this file will contain the functions for communicating between the app and the web server using an API.
  • views : this folder will contain our app's pages.

    • home.dart : this file is the main page of the app.
  • widgets : this folder will contain our custom widgets.

    • recipe_card.dart : this file is our custom recipe card widget.

If your app has multiple models, creating separate files for each one is advisable, such as a file for the Recipe object and another for the Recipe API functions. This structure could look like: models -> recipe -> [recipe.api.dart, recipe.dart].

However, since our recipe list app only uses one model, the "Recipe class", we won't create a separate file for it.

Step 3: Generating a Custom Recipe Widget

In this step, a custom recipe widget card will be created that displays the recipe's image, name, and description on the home screen. This will be done by creating a new file named "recipe_card.dart" inside the "widgets" folder and adding the provided code.

The code imports the required packages and defines a new "RecipeCard" widget that takes four parameters: title, rating, cookTime, and thumbnailUrl.

The widget builds a container decorated with a black background color, rounded borders, and a box shadow. It also has an image that serves as a background and applies a color filter to it.

The widget also aligns a text widget displaying the recipe's name, a star icon representing the rating, and a clock icon representing the cooking time. Here is the code for it:

dart
import 'package:flutter/material.dart';
class RecipeCard extends StatelessWidget {
final String title;
final String rating;
final String cookTime;
final String thumbnailUrl;
RecipeCard({
@required this.title,
@required this.cookTime,
@required this.rating,
@required this.thumbnailUrl,
});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 22, vertical: 10),
width: MediaQuery.of(context).size.width,
height: 180,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.6),
offset: Offset(0.0, 10.0),
blurRadius: 10.0,
spreadRadius: -6.0,
),
],
image: DecorationImage(
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.35),
BlendMode.multiply,
),
image: NetworkImage(thumbnailUrl),
fit: BoxFit.cover,
),
),
child: Stack(
children: [
Align(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 5.0),
child: Text(
title,
style: TextStyle(
fontSize: 19,
),
overflow: TextOverflow.ellipsis,
maxLines: 2,
textAlign: TextAlign.center,
),
),
alignment: Alignment.center,
),
Align(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
borderRadius: BorderRadius.circular(15),
),
child: Row(
children: [
Icon(
Icons.star,
color: Colors.yellow,
size: 18,
),
SizedBox(width: 7),
Text(rating),
],
),
),
Container(
padding: EdgeInsets.all(5),
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
borderRadius: BorderRadius.circular(15),
),
child: Row(
children: [
Icon(
Icons.schedule,
color: Colors.yellow,
size: 18,
),
SizedBox(width: 7),
Text(cookTime),
],
),
)
],
),
alignment: Alignment.bottomLeft,
),
],
),
);
}
}

Step 4: Creating the Home Page with a Recipe Card Widget

In this step, we will create the initial home page for our app. Open the home.dart file and create a stateful widget that returns a scaffold widget instead of a container. Inside the scaffold, add an AppBar widget and set the title to "Food Recipes" with a restaurant icon.

Next, import the RecipeCard widget and add it to the body of the scaffold. Let's populate the RecipeCard with fake data, which we will replace with API data later.

Here is the code for the home.dart file:

dart
import 'package:flutter/material.dart';
import 'package:food_recipe/views/widgets/recipe_card.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restaurant_menu),
SizedBox(width: 10),
Text('Food Recipes'),
],
),
),
body: RecipeCard(
title: 'My recipe',
rating: '4.9',
cookTime: '30 min',
thumbnailUrl:
'https://lh3.googleusercontent.com/ei5eF1LRFkkcekhjdR_8XgOqgdjpomf-rda_vvh7jIauCgLlEWORINSKMRR6I6iTcxxZL9riJwFqKMvK0ixS0xwnRHGMY4I5Zw=s360',
),
);
}
}

Step 5: Refactoring the main.dart File

In this step, we will refactor the main.dart file. We will create a new MyApp class which will return a MaterialApp widget. We will also import the HomePage class from the home.dart file.

Here's what your code should look like after refactoring:

dart
import 'package:flutter/material.dart';
import 'package:food_recipe/views/home.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Food recipe',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.white,
textTheme: TextTheme(
bodyText2: TextStyle(color: Colors.white),
),
),
home: HomePage(),
);
}
}

Upon refreshing and reloading, your app should appear like this.

Step 6: Integrating API with an app

This step will integrate the Yummly API with our app to receive recipes. Firstly, we need to register on the Yummly API website, where we can make 500 requests per month for free.

We will use the API's GET feeds/list endpoint to get the recipes list. Please note that we will blur the x-rapidapi-key because of the limitation of 500 requests. You should use your key.

Here is the code to make a GET request to the API:

dart
import 'package:http/http.dart' as http;
Future<void> fetchRecipes() async {
final url =
Uri.parse('https://api.yummly.com/v2/feeds/list?start=0&limit=18');
final response = await http.get(
url,
headers: {
'x-rapidapi-key': 'YOUR_API_KEY',
'x-rapidapi-host': 'api.yummly.com',
},
);
print(response.body);
}

Keep the tab open and return to the Flutter app later.

Step 7: Creating a Recipe class

In this step, we will create a Recipe class inside the recipe.dart file in the models folder.

The Recipe class will have four properties: name, images, rating, and totalTime. We will also create a factory method to convert the JSON response from the API into Recipe objects.

Additionally, we will create a static method to convert the list of recipe JSONs into a list of Recipe objects. Here is the code for the Recipe class:

dart
class Recipe {
final String name;
final String images;
final double rating;
final String totalTime;
Recipe({this.name, this.images, this.rating, this.totalTime});
factory Recipe.fromJson(dynamic json) {
return Recipe(
name: json['name'] as String,
images: json['images'][0]['hostedLargeUrl'] as String,
rating: json['rating'] as double,
totalTime: json['totalTime'] as String);
}
static List<Recipe> recipesFromSnapshot(List snapshot) {
return snapshot.map((data) {
return Recipe.fromJson(data);
}).toList();
}
@override
String toString(){
return 'Recipe {name: $name, image: $images, rating: $rating, totalTime: $totalTime}';
}
}

Step 8: Formulating a Recipe API

To serve as an interface between your app and the web server, you will in this step build a class called RecipeAPI. The Yummly API is used by the RecipeAPI class to retrieve recipe info.

The RecipeApi class has a static method called getRecipe, which returns a Future that resolves to a List of Recipe objects. The method sends a GET request to the Yummly API endpoint, passing in the parameters for the number of recipes to return, the starting index, and a tag for popular recipes. It also includes the API key in the request headers.

The response is then decoded in JSON format, and the relevant recipe data is extracted and stored in a temporary list. This list is then passed to a static method called recipesFromSnapshot in the Recipe class, which returns a List of Recipe objects.

Note that you will need to replace YOUR API KEY FROM YUMMLY API in the headers section with your API key from Step 6.

To start, go to the recipe.api.dart file and add the following code:

dart
import 'dart:convert';
import 'package:food_recipe/models/recipe.dart';
import 'package:http/http.dart' as http;
class RecipeApi {
static Future<List<Recipe>> getRecipe() async {
var uri = Uri.https('yummly2.p.rapidapi.com', '/feeds/list',
{"limit": "18", "start": "0", "tag": "list.recipe.popular"});
final response = await http.get(uri, headers: {
"x-rapidapi-key": "YOUR API KEY FROM YUMMLY API",
"x-rapidapi-host": "yummly2.p.rapidapi.com",
"useQueryString": "true"
});
Map data = jsonDecode(response.body);
List _temp = [];
for (var i in data['feed']) {
_temp.add(i['content']['details']);
}
return Recipe.recipesFromSnapshot(_temp);
}
}

Step 9. Call API to fetch recipes in HomePage widget

In this step, we will use the Recipe API that we created in Step 8 to fetch the recipes and display them on the home page. The getRecipes() method is called inside the initState() method which is called when the widget is inserted into the tree for the first time. Then, we set the _isLoading flag to false once the recipes have been fetched.

The HomePage widget displays a CircularProgressIndicator if _isLoading is true, otherwise, it displays a ListView of the fetched recipes.

Here is the code for this:

dart
import 'package:flutter/material.dart';
import 'package:food_recipe/models/recipe.api.dart';
import 'package:food_recipe/models/recipe.dart';
import 'package:food_recipe/views/widgets/recipe_card.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<Recipe> _recipes;
bool _isLoading = true;
@override
void initState() {
super.initState();
getRecipes();
}
Future<void> getRecipes() async {
_recipes = await RecipeApi.getRecipe();
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.restaurant_menu),
SizedBox(width: 10),
Text('Food Recipe')
],
),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: _recipes.length,
itemBuilder: (context, index) {
return RecipeCard(
title: _recipes[index].name,
cookTime: _recipes[index].totalTime,
rating: _recipes[index].rating.toString(),
thumbnailUrl: _recipes[index].images);
},
));
}
}

This is the final result of the app:

Wrap Up

This guide walked through building a food recipe app using Flutter. The guide covered various topics, including creating the project structure, setting up the app theme, adding the necessary packages, creating the UI components, creating the Recipe model and Recipe API, and finally, calling the API and displaying the recipes on the home page.

By following these steps, you can create a fully functional food recipe app that communicates with a web server to fetch data and display it in a user-friendly way.