11
Networking in Flutter
Written by Kevin D Moore
Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as
text.You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.
Loading data from the network to show it in a UI is a very common task for apps. In the previous chapter, you learned how to serialize JSON data. Now, you’ll continue the project to learn about retrieving JSON data from the network.
Note: You can also start fresh by opening this chapter’s starter project. If you choose to do this, remember to click the Get dependencies button or execute
flutter pub get
from Terminal.
By the end of the chapter, you’ll know how to:
- Sign up for a recipe API service.
- Trigger a search for recipes by name.
- Convert data returned by the API to model classes.
With no further ado, it’s time to get started!
Signing up with the recipe API
For your remote content, you’ll use the Edamam Recipe API. Open this link in your browser: https://developer.edamam.com/.
Click the SIGN UP button at the top-right and choose the Recipe Search API option.
The page will display multiple subscription choices. Choose the free option by clicking the START NOW button in the Developer column:
On the Sign Up Info pop-up window, enter your information and click SIGN UP. You’ll receive an email confirmation shortly.
Once you’ve received the email and verified your account, return to the site and sign in. On the menu bar, click the Get an API key now! button:
Next, click the Create a new application button.
On the Select service page, click the Recipe Search API link.
A New Application page will come up. Enter raywenderlich.com Recipes for the app’s name and An app to display raywenderlich.com recipes as the description — or use any values you prefer. When you’re done, press the Create Application button.
Once the site generates the API key, you’ll see a screen with your Application ID and Application Key.
You‘ll need your API Key and ID later, so save them somewhere handy or keep the browser tab open. Now, check the API documentation, which provides important information about the API including paths, parameters and returned data.
Accessing the API documentation
At the top of the window, right-click the API Developer Portal link and select Open Link in New Tab.
Using your API key
For your next step, you’ll need to use your newly created API key.
Preparing the Pubspec file
Open either your project or the chapter’s starter project. To use the http package for this app, you need to add it to pubspec.yaml, so open that file and add the following after the json_annotation package:
http: ^0.13.3
Using the HTTP package
The HTTP package contains only a few files and methods that you’ll use in this chapter. The REST protocol has methods like:
Connecting to the recipe service
To fetch data from the recipe API, you’ll create a Dart class to manage the connection. This Dart class file will contain your API Key, ID and URL.
import 'package:http/http.dart';
const String apiKey = '<Your Key>';
const String apiId = '<your ID>';
const String apiUrl = 'https://api.edamam.com/search';
class RecipeService {
// 1
Future getData(String url) async {
// 2
print('Calling url: $url');
// 3
final response = await get(Uri.parse(url));
// 4
if (response.statusCode == 200) {
// 5
return response.body;
} else {
// 6
print(response.statusCode);
}
}
// TODO: Add getRecipes
}
// 1
Future<dynamic> getRecipes(String query, int from, int to) async {
// 2
final recipeData = await getData(
'$apiUrl?app_id=$apiId&app_key=$apiKey&q=$query&from=$from&to=$to');
// 3
return recipeData;
}
Internet Permissions
In addition to creating the service, on Android you need to add the permission to access the internet. This is considered a “safe” permission and doesn’t require the user to approve it.
<uses-permission android:name="android.permission.INTERNET"/>
Building the user interface
Every good collection of recipes starts with a recipe card, so you’ll build that first.
Creating the recipe card
The file ui/recipe_card.dart contains a few methods for creating a card for your recipes. Open it now and add the following import:
import '../network/recipe_model.dart';
Widget recipeCard(APIRecipe recipe) {
imageUrl: recipe.image,
recipe.label,
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
getCalories(recipe.calories),
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 11,
),
),
),
child: recipeCard(recipe),
Adding a recipe list
Your next step is to create a way for your users to find which recipe they want to try: a recipe list.
import '../../network/recipe_service.dart';
List<APIHits> currentSearchList = [];
Retrieving recipe data
Still in recipe_list.dart, you need to create a method to get the data from RecipeService
. You’ll pass in a query along with the starting and ending positions and the API will return the decoded JSON results.
// 1
Future<APIRecipeQuery> getRecipeData(String query, int from, int to) async {
// 2
final recipeJson = await RecipeService().getRecipes(query, from, to);
// 3
final recipeMap = json.decode(recipeJson);
// 4
return APIRecipeQuery.fromJson(recipeMap);
}
// 1
Widget _buildRecipeList(BuildContext recipeListContext, List<APIHits> hits) {
// 2
final size = MediaQuery.of(context).size;
const itemHeight = 310;
final itemWidth = size.width / 2;
// 3
return Flexible(
// 4
child: GridView.builder(
// 5
controller: _scrollController,
// 6
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: (itemWidth / itemHeight),
),
// 7
itemCount: hits.length,
// 8
itemBuilder: (BuildContext context, int index) {
return _buildRecipeCard(recipeListContext, hits, index);
},
),
);
}
Removing the sample code
In the previous chapter, you added code to recipe_list.dart to show a single card. Now that you’re showing a list of cards, you need to clean up some of the existing code to use the new API.
APIRecipeQuery? _currentRecipes1 = null;
Widget _buildRecipeLoader(BuildContext context) {
// 1
if (searchTextController.text.length < 3) {
return Container();
}
// 2
return FutureBuilder<APIRecipeQuery>(
// 3
future: getRecipeData(searchTextController.text.trim(),
currentStartPosition, currentEndPosition),
// 4
builder: (context, snapshot) {
// 5
if (snapshot.connectionState == ConnectionState.done) {
// 6
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString(),
textAlign: TextAlign.center, textScaleFactor: 1.3),
);
}
// 7
loading = false;
final query = snapshot.data;
inErrorState = false;
if (query != null) {
currentCount = query.count;
hasMore = query.more;
currentSearchList.addAll(query.hits);
// 8
if (query.to < currentEndPosition) {
currentEndPosition = query.to;
}
}
// 9
return _buildRecipeList(context, currentSearchList);
}
// TODO: Handle not done connection
},
);
}
// 10
else {
// 11
if (currentCount == 0) {
// Show a loading indicator while waiting for the recipes
return const Center(child: CircularProgressIndicator());
} else {
// 12
return _buildRecipeList(context, currentSearchList);
}
}
Key points
- The HTTP package is a simple-to-use set of methods for retrieving data from the internet.
- The built-in
json.decode
transforms JSON strings into a map of objects that you can use in your code. -
FutureBuilder
is a widget that retrieves information from aFuture
. -
GridView
is useful for displaying columns of data.
Where to go from here?
You’ve learned how to retrieve data from the internet and parse it into data models. If you want to learn more about the HTTP package and get the latest version, go to https://pub.dev/packages/http.