Chapters

Hide chapters

Flutter Apprentice

Third Edition · Flutter 3.3 · Dart 2.18.0 · Android Studio 2021.2.1

Section IV: Networking, Persistence and State

Section 4: 7 chapters
Show chapters Hide chapters

Appendices

Section 7: 2 chapters
Show chapters Hide chapters

11. Networking in Flutter
Written by Kevin D Moore

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

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://www.edamam.com/.

Click the Signup API button at the top-right to create an account.

Fill in a username, email, password, organization, in the Choose your plan dropdown choose Recipe Search API -> Developer, read the Term and Privacy Policy, check the I have read and agree box and press Submit.

Next sign in by entering the username and password you created earlier and press Submit.

The main page will now show the APIs button at the top middle. Select the Recipe Search API.

Next select the Get Started button under the Developer column:

You’ll see the Sign Up dialog again. At the bottom, choose Login.

On the next screen, select Go to Dashboard.

Now click on the Applications tab and then the Create a new application.

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.

Note: As with any API, be sure you’re OK with the API’s Legal Terms and Conditions.

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, click on APIs, right-click on the Recipe Search API* 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.4

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 'dart:developer';
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
    final response = await get(Uri.parse(url));
    // 3
    if (response.statusCode == 200) {
      // 4
      return response.body;
    } else {
      // 5
      log(response.body);
    }
  }
  // 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;
}

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';
import 'package:cached_network_image/cached_network_image.dart';
Widget recipeCard(APIRecipe recipe) {
child: Image.asset(
    'assets/images/pizza_w700.png',
     height: 200,
     width: 200,
),
child: CachedNetworkImage(
    imageUrl: recipe.image, height: 210, fit: BoxFit.fill),
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;
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 a Future.
  • 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.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now