内容简介: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:
The final project will look like this:
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.
Fill in the fields to sign up for an account. Then, click Sign up .
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:
Now, choose the API menu item in the settings Panel:
Choose click here in the Request an API Key section:
Then, click either the Developer or the Professional link.
Change the Type of Use to Mobile, fill in the API Information and submit:
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.
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:
Now, click the Try it out tab. Copy your API key from settings key into the api_key field . Then, press Send Request .
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.
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:
-
chopper
makes the REST API call. -
json_annotation
marks your models as serializable. -
provider
makes your Chopper service available in your list. -
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:
-
build_runner
generates code. -
chopper_generator
generates Chopper related code. -
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.
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:
-
It imports the JSON annotation library for annotations like
JsonSerializable
andJsonKey
. - The part directive lets you include a file called result.g.dart . This file doesn’t exist yet and will be generated for you.
-
Using
JsonSerializable
tells the json_serializable package to generate the code in result.g.dart . This code serializes and deserializes JSON. -
It defines a double field called
popularity
. -
Use the
JsonKey name
property to define the JSON key field and your related code field, for example,vote_count
andvoteCount
respectively. This lets you name the JSON field differently than your field. - It defines the constructor that recieves the defined fields.
-
Then it defines a
factory
constructor that creates a newResult
instance using your soon to be automatically generated JSON deserializer. -
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:
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:
- Import the chopper package.
- Then, import your models.
- Define your chopper generated file, which doesn’t exist yet.
- Then, use the ChopperApi annotation so the chopper generator knows to create the movie_service.chopper file.
- The abstract class extends ChopperService.
- 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.
-
Next, you define a function that returns a
Future
of a Chopper Response using the previously created Popular class. - Define a static method to create the movie service.
-
Then, create a
ChopperClient
class. -
Set the
baseUrl
all calls will use, pre-pended to their path. - Define all of the services used.
-
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:
- The .chopper file does not exist .
- _$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:
- Implement the RequestInterceptor interface.
- Constant for the Header type.
- Part of final authorization string.
- The auth key you attained from themoviedb.
- 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.
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:
- Use ListView.builder to create your ListView.
- Set the itemCount since you know how many items there are.
- Use a Card as the main widget.
- Show the movie image,
- the movie title
- 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.
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
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》 这本书的介绍吧!