Home · Flutter Tutorials

Elegant Networking in Flutter with Chopper

Learn how to easily create REST API calls and parse JSON data from the internet using the Flutter networking library Chopper.

5/5 3 Ratings

Version

  • Dart 2.7, Flutter 1.12, Android Studio 3.6

Flutter comes with basic networking and JSON serialization modules. These modules work but require a lot work to download JSON from the internet.

If you come from an Android or iOS background, you may be familiar with the Retrofit library on Android or the AlamoFire library on iOS. These libraries do the heavy lifting so you don’t have to do as much work. Wouldn’t it be nice if you had something similar for Flutter?

With the Chopper networking library, you do! Built to resemble Retrofit, this library makes it much easier to get JSON data from the internet.

In this tutorial, you’ll build a movie listing app that builds data modules and creates network calls. Along the way, you’ll see how Flutter implements:

  • Calling network APIs.
  • Parsing JSON data.
  • Showing JSON data in a ListView.
  • Displaying network images.
Note: If you’re new to Flutter, please check out our Getting Started With Flutter tutorial for an overview of the basics of working with this SDK.

Getting Started

Download the starter project by using the Download Materials button at the top or bottom of the page.

This tutorial uses Android Studio with the Flutter plugin. You can also use Visual Studio Code, IntelliJ IDEA or a text editor of your choice with Flutter at the command line. Make sure you have the Flutter plugin installed before you open the starter project.

You’ll build on the starter project to load a list of popular movies and show each movie in a card view inside of a listview. First, open the starter project. Then, open the pubspec.yaml and get the dependencies by clicking the Pub get button in the top-right corner.

Build and run the app. You’ll see a sample card like this:

Starter project app screenshot

The final project will look like this:

Final App Screenshot

Movie Listings

Since you want to show a list of the most popular movies, use The Movie Db website which has an API built for that purpose. You need to sign up for a TMDb account to use their API. If you decide you want to use their data in a published app, they ask that you attribute TMDb as the source of your data.

Head to The Movie Db site. Click the Join TMDb link in the upper right corner.

TMDb landing page

Fill in the fields to sign up for an account. Then, click Sign up.

TMDb sign up for an account page with Join TMDb circled

You’ll receive an email to verify your account. Once that’s done, sign back in. Then, tap your account icon in the top-right corner and choose the Settings menu item:

TMDb profile menu with Settings circled

Now, choose the API menu item in the settings Panel:

TMDb API side menu with API circled

Choose click here in the Request an API Key section:

TMDb API Details page with click here circled

Then, click either the Developer or the Professional link.

TMDb API Create page with choice of which API key to register

Change the Type of Use to Mobile, fill in the API Information and submit:

TMDb API Create application page

It doesn’t matter if you put in placeholder information for now – you should be automatically approved.

Once your app is approved, click the Details section. The API Key and the API Read Access Token, v4 auth, are the important sections. You’ll use the v4 auth token later in the project.

TMDb API Details with API key and access token codes

Using the Popular API

You’ll use the Popular Movie API found at Popular Movies Docs. To get the list of the most popular movies, you need to make a call to https://api.themoviedb.org/3/movie/popular with either your api_key or, as you’ll see later, your auth token. You can see the schema and example data displayed in the Popular Movies Docs API documentation under the Responses section:

API responses

Now, click the Try it out tab. Copy your API key from settings key into the api_key field. Then, press Send Request.

TMDb API Get Popular try it out screen

Here’s a sample of the data returned from calling the popular REST API. REST stands for REpresentational State Transfer. It’s an API architectural design pattern.

This particular call uses the GET method with the popular URL.

TMDb sample data in JSON pretty format

As you can see, you have the current page, total results, total pages and an array of movies, or results. You’ll need to use these JSON fields in your model classes.

Libraries

You’ll use several libraries:

  1. chopper makes the REST API call.
  2. json_annotation marks your models as serializable.
  3. provider makes your Chopper service available in your list.
  4. logging logs your network requests.

If not already open, open the pubspec.yaml file in your project. Under the cupertino_icons library, add the following, aligning with cupertino_icons:

  chopper: ^3.0.2
  provider: ^4.0.5
  json_annotation: ^3.0.1
  logging: ^0.11.4

Developer Libraries

Developer libraries are used during development and aren’t shipped with the app. You’ll use these libraries to generate the code that does most of the work for you:

  1. build_runner generates code.
  2. chopper_generator generates Chopper related code.
  3. json_serializable generates JSON serializable code for your models.

Continuing in pubspec.yaml, under the flutter_test library, add the following, making sure these libraries aline with flutter_test and not sdk::

  build_runner: ^1.9.0
  chopper_generator: ^3.0.4
  json_serializable: ^3.3.0

As you should anytime you update pubspec.yaml, click the Pub get link to get dependencies. You can also choose Tools ▸ Flutter ▸ Flutter Pub Get to download all the libraries.

pubspec.yaml screenshot with Pub get circled

Models

Before you download movie listings, you need a place to put the data. You could use a map of strings, as the default JSON parsing does, or you could put it into a class that makes getting to the data easy.

In the Project tab, go to the lib ▸ models folder. Right-click the models folder and choose New ▸ New Dart File. Name the file result.dart.

Add the following:

// 1
import 'package:json_annotation/json_annotation.dart';

// 2 
part 'result.g.dart';

// 3
@JsonSerializable()
class Result {
  // 4
  double popularity;
  // 5
  @JsonKey(name: 'vote_count')
  int voteCount;
  @JsonKey(name: 'video')
  bool video;
  @JsonKey(name: 'poster_path')
  String posterPath;
  @JsonKey(name: 'id')
  int id;
  @JsonKey(name: 'adult')
  bool adult;
  @JsonKey(name: 'backdrop_path')
  String backdropPath;
  @JsonKey(name: 'original_language')
  String originalLanguage;
  @JsonKey(name: 'original_title')
  String originalTitle;
  @JsonKey(name: 'genre_ids')
  List<int> genreIds;
  @JsonKey(name: 'title')
  String title;
  @JsonKey(name: 'vote_average')
  double voteAverage;
  @JsonKey(name: 'overview')
  String overview;
  @JsonKey(name: 'release_date')
  String releaseDate;

  // 6
  Result({this.popularity, this.voteCount, this.video, this.posterPath, this.id,
      this.adult, this.backdropPath, this.originalLanguage, this.originalTitle,
      this.genreIds, this.title, this.voteAverage, this.overview,
      this.releaseDate});

  // 7
  factory Result.fromJson(Map<String, dynamic> json) => _$ResultFromJson(json);

  // 8
  Map<String, dynamic> toJson() => _$ResultToJson(this);
}

Here’s a description of the class:

  1. It imports the JSON annotation library for annotations like JsonSerializable and JsonKey.
  2. The part directive lets you include a file called result.g.dart. This file doesn’t exist yet and will be generated for you.
  3. Using JsonSerializable tells the json_serializable package to generate the code in result.g.dart. This code serializes and deserializes JSON.
  4. It defines a double field called popularity.
  5. Use the JsonKey name property to define the JSON key field and your related code field, for example, vote_count and voteCount respectively. This lets you name the JSON field differently than your field.
  6. It defines the constructor that recieves the defined fields.
  7. Then it defines a factory constructor that creates a new Result instance using your soon to be automatically generated JSON deserializer.
  8. Finally, it defines a function to return a JSON map of the Result class, also known as serializing the class.

Look at the moviedb page you referenced earlier with a sample get popular movies response. You’ll see each of these fields matches those returned in the JSON string in the sample response.

Now, create the top-level popular class. Right-click the models folder and choose New ▸ New Dart File. Name the file popular.dart.

Then, add the following:

import 'package:json_annotation/json_annotation.dart';
import 'package:movies/models/result.dart';

part 'popular.g.dart';

@JsonSerializable()
class Popular {
  int page;
  @JsonKey(name: 'total_results')
  int totalResults;
  @JsonKey(name: 'total_pages')
  int totalPages;
  List<Result> results;

  Popular({this.page, this.totalResults, this.totalPages, this.results});
  factory Popular.fromJson(Map<String, dynamic> json) => _$PopularFromJson(json);

  Map<String, dynamic> toJson() => _$PopularToJson(this);

}

This is similar to the Result class and defines the Popular class top-level elements. Using the JSON array data, it creates a list of results.

Generating JSON files

To get rid of the errors from the missing *.g.dart files, you need to run the generator.

Open Android Studio’s Terminal tab. You’ll see a terminal prompt displaying the current folder:

Terminal

Type flutter pub run build_runner build and press enter. This runs the json_serializable tool to generate both result.g.dart and popular.g.dart. When the generator finishes running, you’ll see the two files in the models folder.

Now, open result.g.dart and popular.g.dart. You’ll see each file refers back to the parent file and then defines two methods: One to turn a map of strings into that class and another to change that class into a map – i.e. one method to serialize the class into JSON and one method to deserialize JSON into your class. You’ll also notice the red squiggle lines have disappeared from result.dart and popular.dart.

Movie API Service

To get the data needed for the models you just created, you need to make a network call. Instead of calling low-level network calls, you’ll define a class that defines your API calls and sets up the Chopper Client that does the work for you.

In the service folder, create a new dart file named movie_service.dart. Add the following:

// 1
import 'package:chopper/chopper.dart';
// 2
import 'package:movies/models/popular.dart';

// 3
part 'movie_service.chopper.dart';

// 4
@ChopperApi()
// 5
abstract class MovieService extends ChopperService {

  // 6
  @Get(path: 'movie/popular')
  // 7
  Future<Response<Popular>> getPopularMovies();

  // 8
  static MovieService create() {
    // 9
    final client = ChopperClient(
      // 10
      baseUrl: 'https://api.themoviedb.org/3',
      // 11
      services: [
        _$MovieService(),
      ],
    );
    // 12
    return _$MovieService(client);
  }
}

Here you:

  1. Import the chopper package.
  2. Then, import your models.
  3. Define your chopper generated file, which doesn’t exist yet.
  4. Then, use the ChopperApi annotation so the chopper generator knows to create the movie_service.chopper file.
  5. The abstract class extends ChopperService.
  6. Next, use the Get annotation to define the path for the popular movies. There are other HTTP methods you can use, such as Post, Put, Delete, but you won’t use those in this app.
  7. Next, you define a function that returns a Future of a Chopper Response using the previously created Popular class.
  8. Define a static method to create the movie service.
  9. Then, create a ChopperClient class.
  10. Set the baseUrl all calls will use, pre-pended to their path.
  11. Define all of the services used.
  12. Finally, return the generated MovieService class.

The MovieService class will host your method for getting the list of popular movies. Notice there are several errors:

  1. The .chopper file does not exist.
  2. _$MovieService does not exist.

You need to generate movie_service.chopper.dart, like you generated the model files. The chopper_generator service will generate them for you.

If it’s closed, open Android Studio’s Terminal tab, type flutter pub run build_runner build and press enter. This runs the chopper_generator tool to generate movie_service.chopper.dart.

Next, open movie_service.chopper.dart. You’ll see the _$MovieService extended class which does a bit more work for you.

Since MovieService is abstract you can’t create an instance. That’s where the static MovieService create() method comes in.

getPopularMovies is the method you’ll use to get the Popular class that contains the list of movies. Since it returns a Future, the method can run asynchronously.

Later in the UI Code, you’ll use create to get an instance of the MovieService. You’ll use baseUrl as the starting point for building the URL. The @Get annotation defines the rest of the path.

Interceptors

Interceptors intercept either a request or a response. They’re useful for logging requests and responses, adding headers to calls or handling authentication.

You’ll use one built-in interceptor that logs all your requests and responses to verify that what you’re calling and receiving is what you expect. Chopper includes HttpLoggingInterceptor for this purpose. You’ll also create a Header Interceptor that injects the authentication header to each call.

In movie_service.dart, under the baseUrl statement, add:

interceptors: [HttpLoggingInterceptor()],

For the logging interceptor to work, you need to set up logging for Flutter.

Open main.dart. At the top of the file, add the import statement for the logging package:

import 'package:logging/logging.dart';

Change the main method to:

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

Then, after the main() class, add:

void _setupLogging() {
  Logger.root.level = Level.ALL;
  Logger.root.onRecord.listen((rec) {
    print('${rec.level.name}: ${rec.time}: ${rec.message}');
  });
}

This sets up the logger so it prints a log in the specified format. Without this, the HttpLoggingInterceptor wouldn’t output anything.

Header Interceptor

Next, you’ll create a Header Interceptor that’s a Request interceptor that adds the Auth Token you received from the TMDb website.

Right-click the service folder and choose New Dart File. Then, name the file header_interceptor.dart and add the following:

import 'dart:async';
import 'package:chopper/chopper.dart';

// 1
class HeaderInterceptor implements RequestInterceptor {
  // 2
  static const String AUTH_HEADER = "Authorization";
  // 3
  static const String BEARER = "Bearer ";
  // 4
  static const String V4_AUTH_HEADER =
      "< your key here >";

  @override
  FutureOr<Request> onRequest(Request request) async {
    // 5
    Request newRequest = request.copyWith(headers: {AUTH_HEADER: BEARER + V4_AUTH_HEADER});
    return newRequest;
  }
  
}

Here’s a breakdown of what you added:

  1. Implement the RequestInterceptor interface.
  2. Constant for the Header type.
  3. Part of final authorization string.
  4. The auth key you attained from themoviedb.
  5. Create a copy of the request with your new header.

This interceptor creates a copy of the current request and adds your auth header to each call.

If you’ve closed https://www.themoviedb.org/, reopen it. Make sure you’re on your account’s Settings page.

themoviedb.org

Copy the API Read Access Token (v4 auth) and, in header_interceptor.dart, replace "< your key here >" with your V4 Auth Token.

Now you have to add a few things to MovieService. At the top, add the following import:

import 'header_interceptor.dart';

Inside ChopperClient, change the interceptors parameter to:

interceptors: [HeaderInterceptor(), HttpLoggingInterceptor()],

Converters

To convert the json string you get from the API into an instance of the Popular class, you need a converter. You’ll create a converter class modeled after the JsonConverter class included with Chopper.

First, right-click the service folder and create a file named model_converter.dart. Add the following:

import 'dart:convert';
import 'package:chopper/chopper.dart';
import 'package:movies/models/popular.dart';

class ModelConverter implements Converter {
  @override
  Request convertRequest(Request request) {
    final req = applyHeader(
      request,
      contentTypeKey,
      jsonHeaders,
      override: false,
    );

    return encodeJson(req);
  }
}

This adds the application/json content type to the header. After this method and before the ModelConverter closing }, add:

  Request encodeJson(Request request) {
    var contentType = request.headers[contentTypeKey];
    if (contentType != null && contentType.contains(jsonHeaders)) {
      return request.copyWith(body: json.encode(request.body));
    }
    return request;
  }

This method creates a new request that encodes the body of the original request.

To decode the JSON string add another method:

  Response decodeJson<BodyType, InnerType>(Response response) {
    var contentType = response.headers[contentTypeKey];
    var body = response.body;
    if (contentType != null && contentType.contains(jsonHeaders)) {
      body = utf8.decode(response.bodyBytes);
    }
    try {
      var mapData = json.decode(body);
      var popular = Popular.fromJson(mapData);
      return response.copyWith<BodyType>(body: popular as BodyType);
    } catch (e) {
      chopperLogger.warning(e);
      return response.copyWith<BodyType>(body: body);
    }
  }

Now you’ll use the built-in JSON converter to turn the body into a map of strings. The Popular.fromJson method is called with that map to turn it into a Popular class. Then a new response is sent with that data.

The last method added calls the decodeJson method.

  @override
  Response<BodyType> convertResponse<BodyType, InnerType>(Response response) {
    return decodeJson<BodyType, InnerType>(response);
  }

Open the MovieService file. At the top import the model_converter class, add:

 import 'model_converter.dart';

In the MovieService class, add the following after the interceptors:

      converter: ModelConverter(),
      errorConverter: JsonConverter(),

UI: Movie Listing

To access your MovieService class, you’ll use the Provider library to create the class and provide it to all widgets.

In main.dart, in the build method, replace return MaterialApp( with:

    return Provider(
        create: (_) => MovieService.create(),
        dispose: (_, MovieService service) => service.client.dispose(),
        child: MaterialApp(

At the top, add the needed import statements:

import 'package:movies/service/movie_service.dart';
import 'package:provider/provider.dart';

You’ll need to add an extra closing parenthesis at the end just before the ;. This uses Provider which creates the MovieService class in the create section and calls dispose() in the dispose section.

Open movie_listings.dart. At the top, add the needed import statements:

import 'package:chopper/chopper.dart';
import 'package:movies/models/popular.dart';
import 'package:movies/service/movie_service.dart';
import 'package:provider/provider.dart';

Replace the _buildBody method with:

// 1
FutureBuilder<Response<Popular>> _buildBody(BuildContext context) {
    return FutureBuilder<Response<Popular>>(
      // 2
      future: Provider.of<MovieService>(context).getPopularMovies(),
      builder: (context, snapshot) {
        // 3
        if (snapshot.connectionState == ConnectionState.done) {
          // 4
          if (snapshot.hasError) {
            return Center(
              child: Text(
                snapshot.error.toString(),
                textAlign: TextAlign.center,
                textScaleFactor: 1.3,
              ),
            );
          }
          // 5
          final popular = snapshot.data.body;
          // 6
          return _buildMovieList(context, popular);
        } else {
          // 7
          // Show a loading indicator while waiting for the movies
          return Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }

Here’s a breakdown:

  1. This method returns a FutureBuilder.
  2. It gets the MovieService from the Provider and calls getPopularMovies, which returns a Future.
  3. It checks to see if the call is done.
  4. If there’s an error, it shows the error string.
  5. The body contains the Popular class.
  6. Use Call _buildMovieList to build the ListView of movies.
  7. This shows a progress indicator if the results are not ready.

Now replace the _buildMovieList method with:

  ListView _buildMovieList(BuildContext context, Popular popular) {
    // 1
    return ListView.builder(
      // 2
      itemCount: popular.results.length,
      padding: EdgeInsets.all(8),
      itemBuilder: (context, index) {
        // 3
        return Card(
          elevation: 4,
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Container(
                  width: 150,
                  height: 200,
                  decoration: BoxDecoration(
                      image: DecorationImage(
                          // 4
                          image: NetworkImage(
                              IMAGE_URL + popular.results[index].posterPath),
                          fit: BoxFit.contain)),
                ),
                Expanded(
                  child: Container(
                    height: 200,
                    child: Column(
                      children: <Widget>[
                        // 5
                        Text(popular.results[index].title,
                          style: TextStyle(fontSize: 14),
                        ),
                        SizedBox(
                          height: 8,
                        ),
                        Expanded(
                            child: Container(
                                child: Text(
                          // 6
                          popular.results[index].overview,
                          style: TextStyle(fontSize: 12),
                        ))),
                      ],
                    ),
                  ),
                )
              ],
            ),
          ),
        );
      },
    );
  }

The above code is responsible for building up the list of cards to display your newly fetched movies. Breaking it down a bit more, it:

  1. Use ListView.builder to create your ListView.
  2. Set the itemCount since you know how many items there are.
  3. Use a Card as the main widget.
  4. Show the movie image,
  5. the movie title
  6. and the movie description.

Run the app and you’ll see the list of movies, each in their card. Depending on popularity, the list may look different.

App Screenshot

Where to Go From Here?

Congratulations! That was a lot of work, but you made it. Now you can handle any kind of networking, along with showing images in ListViews.

You can download the final version of this project by using the Download Materials button at the top or bottom of this tutorial.

If you want to learn more about Flutter, there are many Flutter articles on raywenderlich.com

I hope you enjoyed this tutorial. If you have any questions, comments or musings, please join the forum discussion below.

Average Rating

5/5

Add a rating for this content

3 ratings

More like this

Contributors

Comments