Elegant Networking in Flutter with Chopper [FREE]

栏目: IT技术 · 发布时间: 4年前

内容简介: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

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:

Elegant Networking in Flutter with Chopper [FREE]

The final project will look like this:

Elegant Networking in Flutter with Chopper [FREE]

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.

Elegant Networking in Flutter with Chopper [FREE]

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

Elegant Networking in Flutter with Chopper [FREE]

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:

Elegant Networking in Flutter with Chopper [FREE]

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

Elegant Networking in Flutter with Chopper [FREE]

Choose click here in the Request an API Key section:

Elegant Networking in Flutter with Chopper [FREE]

Then, click either the Developer or the Professional link.

Elegant Networking in Flutter with Chopper [FREE]

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

Elegant Networking in Flutter with Chopper [FREE]

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.

Elegant Networking in Flutter with Chopper [FREE]

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:

Elegant Networking in Flutter with Chopper [FREE]

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

Elegant Networking in Flutter with Chopper [FREE]

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.

Elegant Networking in Flutter with Chopper [FREE]

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.

Elegant Networking in Flutter with Chopper [FREE]

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:

Elegant Networking in Flutter with Chopper [FREE]

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.

Elegant Networking in Flutter with Chopper [FREE]

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:

getPopularMovies
Call _buildMovieList

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.

Elegant Networking in Flutter with Chopper [FREE]

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 onraywenderlich.com

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Defensive Design for the Web

Defensive Design for the Web

37signals、Matthew Linderman、Jason Fried / New Riders / 2004-3-2 / GBP 18.99

Let's admit it: Things will go wrong online. No matter how carefully you design a site, no matter how much testing you do, customers still encounter problems. So how do you handle these inevitable bre......一起来看看 《Defensive Design for the Web》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试