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.
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: flutterhttp: ^0.13.1
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.
views : this folder will contain our app's pages.
widgets : this folder will contain our custom widgets.
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.
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,});@overrideWidget 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,),],),);}}
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> {@overrideWidget 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',),);}}
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 {@overrideWidget 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.
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.
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();}@overrideString toString(){return 'Recipe {name: $name, image: $images, rating: $rating, totalTime: $totalTime}';}}
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);}}
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;@overridevoid initState() {super.initState();getRecipes();}Future<void> getRecipes() async {_recipes = await RecipeApi.getRecipe();setState(() {_isLoading = false;});}@overrideWidget 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:
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.