FLutter 中的网络和错误处理
使用 Dio、flutter_bloc 和 Freezed 包在 Flutter 中进行网络和错误处理
在本文中,我将向您展示如何使用dio、flutter_bloc和freezed包处理网络调用和异常
首先,我们需要在pubspec.yaml文件中添加依赖项。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.3
dio: 3.0.8
freezed: 0.10.9
flutter_bloc: 5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
build_runner:
我们正在使用
首先,我们需要添加以设置 API 调用。
我们正在使用dio包,因此我们将创建一个DioClient类
| import 'dart:io'; | |
| import 'package:dio/dio.dart'; | |
| import 'package:flutter/foundation.dart'; | |
| const _defaultConnectTimeout = Duration.millisecondsPerMinute; | |
| const _defaultReceiveTimeout = Duration.millisecondsPerMinute; | |
| class DioClient { | |
| final String baseUrl; | |
| Dio _dio; | |
| final List<Interceptor> interceptors; | |
| DioClient( | |
| this.baseUrl, | |
| Dio dio, { | |
| this.interceptors, | |
| }) { | |
| _dio = dio ?? Dio(); | |
| _dio | |
| ..options.baseUrl = baseUrl | |
| ..options.connectTimeout = _defaultConnectTimeout | |
| ..options.receiveTimeout = _defaultReceiveTimeout | |
| ..httpClientAdapter | |
| ..options.headers = {'Content-Type': 'application/json; charset=UTF-8'}; | |
| if (interceptors?.isNotEmpty ?? false) { | |
| _dio.interceptors.addAll(interceptors); | |
| } | |
| if (kDebugMode) { | |
| _dio.interceptors.add(LogInterceptor( | |
| responseBody: true, | |
| error: true, | |
| requestHeader: false, | |
| responseHeader: false, | |
| request: false, | |
| requestBody: false)); | |
| } | |
| } | |
| Future<dynamic> get( | |
| String uri, { | |
| Map<String, dynamic> queryParameters, | |
| Options options, | |
| CancelToken cancelToken, | |
| ProgressCallback onReceiveProgress, | |
| }) async { | |
| try { | |
| var response = await _dio.get( | |
| uri, | |
| queryParameters: queryParameters, | |
| options: options, | |
| cancelToken: cancelToken, | |
| onReceiveProgress: onReceiveProgress, | |
| ); | |
| return response.data; | |
| } on SocketException catch (e) { | |
| throw SocketException(e.toString()); | |
| } on FormatException catch (_) { | |
| throw FormatException("Unable to process the data"); | |
| } catch (e) { | |
| throw e; | |
| } | |
| } | |
| Future<dynamic> post( | |
| String uri, { | |
| data, | |
| Map<String, dynamic> queryParameters, | |
| Options options, | |
| CancelToken cancelToken, | |
| ProgressCallback onSendProgress, | |
| ProgressCallback onReceiveProgress, | |
| }) async { | |
| try { | |
| var response = await _dio.post( | |
| uri, | |
| data: data, | |
| queryParameters: queryParameters, | |
| options: options, | |
| cancelToken: cancelToken, | |
| onSendProgress: onSendProgress, | |
| onReceiveProgress: onReceiveProgress, | |
| ); | |
| return response.data; | |
| } on FormatException catch (_) { | |
| throw FormatException("Unable to process the data"); | |
| } catch (e) { | |
| throw e; | |
| } | |
| } | |
| } |
| import 'dart:io'; | |
| import 'package:dio/dio.dart'; | |
| import 'package:flutter/foundation.dart'; | |
| const _defaultConnectTimeout = Duration.millisecondsPerMinute; | |
| const _defaultReceiveTimeout = Duration.millisecondsPerMinute; | |
| class DioClient { | |
| final String baseUrl; | |
| Dio _dio; | |
| final List<Interceptor> interceptors; | |
| DioClient( | |
| this.baseUrl, | |
| Dio dio, { | |
| this.interceptors, | |
| }) { | |
| _dio = dio ?? Dio(); | |
| _dio | |
| ..options.baseUrl = baseUrl | |
| ..options.connectTimeout = _defaultConnectTimeout | |
| ..options.receiveTimeout = _defaultReceiveTimeout | |
| ..httpClientAdapter | |
| ..options.headers = {'Content-Type': 'application/json; charset=UTF-8'}; | |
| if (interceptors?.isNotEmpty ?? false) { | |
| _dio.interceptors.addAll(interceptors); | |
| } | |
| if (kDebugMode) { | |
| _dio.interceptors.add(LogInterceptor( | |
| responseBody: true, | |
| error: true, | |
| requestHeader: false, | |
| responseHeader: false, | |
| request: false, | |
| requestBody: false)); | |
| } | |
| } | |
| Future<dynamic> get( | |
| String uri, { | |
| Map<String, dynamic> queryParameters, | |
| Options options, | |
| CancelToken cancelToken, | |
| ProgressCallback onReceiveProgress, | |
| }) async { | |
| try { | |
| var response = await _dio.get( | |
| uri, | |
| queryParameters: queryParameters, | |
| options: options, | |
| cancelToken: cancelToken, | |
| onReceiveProgress: onReceiveProgress, | |
| ); | |
| return response.data; | |
| } on SocketException catch (e) { | |
| throw SocketException(e.toString()); | |
| } on FormatException catch (_) { | |
| throw FormatException("Unable to process the data"); | |
| } catch (e) { | |
| throw e; | |
| } | |
| } | |
| Future<dynamic> post( | |
| String uri, { | |
| data, | |
| Map<String, dynamic> queryParameters, | |
| Options options, | |
| CancelToken cancelToken, | |
| ProgressCallback onSendProgress, | |
| ProgressCallback onReceiveProgress, | |
| }) async { | |
| try { | |
| var response = await _dio.post( | |
| uri, | |
| data: data, | |
| queryParameters: queryParameters, | |
| options: options, | |
| cancelToken: cancelToken, | |
| onSendProgress: onSendProgress, | |
| onReceiveProgress: onReceiveProgress, | |
| ); | |
| return response.data; | |
| } on FormatException catch (_) { | |
| throw FormatException("Unable to process the data"); | |
| } catch (e) { | |
| throw e; | |
| } | |
| } | |
| } |
我们将使用存储库访问此类
import 'package:dio/dio.dart';
import 'package:network_handling/services/dio_client.dart';
class APIRepository {
DioClient dioClient;
String _baseUrl = "";
APIRepository() {
var dio = Dio();
dioClient = DioClient(_baseUrl, dio);
}
}
现在,如果你看到当我们点击 API 时,我们会得到 2 个结果
Api 调用
--->> 成功 🏆
--->> 失败 ❌
创建两个freezed类来处理数据,
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'network_exceptions.dart';
part 'api_result.freezed.dart';
@freezed
abstract class ApiResult<T> with _$ApiResult<T> {
const factory ApiResult.success({@required T data}) = Success<T>;
const factory ApiResult.failure({@required NetworkExceptions error}) =
Failure<T>;
}
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'network_exceptions.freezed.dart';
@freezed
abstract class NetworkExceptions with _$NetworkExceptions {
const factory NetworkExceptions.requestCancelled() = RequestCancelled;
const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest;
const factory NetworkExceptions.badRequest() = BadRequest;
const factory NetworkExceptions.notFound(String reason) = NotFound;
const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed;
const factory NetworkExceptions.notAcceptable() = NotAcceptable;
const factory NetworkExceptions.requestTimeout() = RequestTimeout;
const factory NetworkExceptions.sendTimeout() = SendTimeout;
const factory NetworkExceptions.conflict() = Conflict;
const factory NetworkExceptions.internalServerError() = InternalServerError;
const factory NetworkExceptions.notImplemented() = NotImplemented;
const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable;
const factory NetworkExceptions.noInternetConnection() = NoInternetConnection;
const factory NetworkExceptions.formatException() = FormatException;
const factory NetworkExceptions.unableToProcess() = UnableToProcess;
const factory NetworkExceptions.defaultError(String error) = DefaultError;
const factory NetworkExceptions.unexpectedError() = UnexpectedError;
}
如果您看到该类ApiResult,如果成功,我将返回T类型的数据,但如果失败,我必须返回网络异常。
我将如何返回网络异常并确定发生了哪个网络异常?
现在你已经看到了这一行,part of'<file-name>.freezed.dart'
我们需要生成冻结文件
Flutter 软件包 pub run build_runner 构建
- Run this command in terminal to watch auto change
Flutter 软件包 pub run build_runner watch
- Run this command in terminal to watch auto change and delete previously generated files
Flutter 软件包 pub run build_runner watch --delete-conflicting-outputs
####Create a new method in NetworkExceptions class which will return NetworkExceptions
```dart
static NetworkExceptions getDioException(error) {
if (error is Exception) {
try {
NetworkExceptions networkExceptions;
if (error is DioError) {
switch (error.type) {
case DioErrorType.CANCEL:
networkExceptions = NetworkExceptions.requestCancelled();
break;
case DioErrorType.CONNECT_TIMEOUT:
networkExceptions = NetworkExceptions.requestTimeout();
break;
case DioErrorType.DEFAULT:
networkExceptions = NetworkExceptions.noInternetConnection();
break;
case DioErrorType.RECEIVE_TIMEOUT:
networkExceptions = NetworkExceptions.sendTimeout();
break;
case DioErrorType.RESPONSE:
switch (error.response.statusCode) {
case 400:
networkExceptions = NetworkExceptions.unauthorisedRequest();
break;
case 401:
networkExceptions = NetworkExceptions.unauthorisedRequest();
break;
case 403:
networkExceptions = NetworkExceptions.unauthorisedRequest();
break;
case 404:
networkExceptions = NetworkExceptions.notFound("Not found");
break;
case 409:
networkExceptions = NetworkExceptions.conflict();
break;
case 408:
networkExceptions = NetworkExceptions.requestTimeout();
break;
case 500:
networkExceptions = NetworkExceptions.internalServerError();
break;
case 503:
networkExceptions = NetworkExceptions.serviceUnavailable();
break;
default:
var responseCode = error.response.statusCode;
networkExceptions = NetworkExceptions.defaultError(
"Received invalid status code: $responseCode",
);
}
break;
case DioErrorType.SEND_TIMEOUT:
networkExceptions = NetworkExceptions.sendTimeout();
break;
}
} else if (error is SocketException) {
networkExceptions = NetworkExceptions.noInternetConnection();
} else {
networkExceptions = NetworkExceptions.unexpectedError();
}
return networkExceptions;
} on FormatException catch (e) {
// Helper.printError(e.toString());
return NetworkExceptions.formatException();
} catch (_) {
return NetworkExceptions.unexpectedError();
}
} else {
if (error.toString().contains("is not a subtype of")) {
return NetworkExceptions.unableToProcess();
} else {
return NetworkExceptions.unexpectedError();
}
}
}
此方法将返回 NetworkException,我们可以使用它来显示不同类型的错误。
此外,有时我们需要针对不同的错误显示相应的错误消息。
static String getErrorMessage(NetworkExceptions networkExceptions) {
var errorMessage = "";
networkExceptions.when(notImplemented: () {
errorMessage = "Not Implemented";
}, requestCancelled: () {
errorMessage = "Request Cancelled";
}, internalServerError: () {
errorMessage = "Internal Server Error";
}, notFound: (String reason) {
errorMessage = reason;
}, serviceUnavailable: () {
errorMessage = "Service unavailable";
}, methodNotAllowed: () {
errorMessage = "Method Allowed";
}, badRequest: () {
errorMessage = "Bad request";
}, unauthorisedRequest: () {
errorMessage = "Unauthorised request";
}, unexpectedError: () {
errorMessage = "Unexpected error occurred";
}, requestTimeout: () {
errorMessage = "Connection request timeout";
}, noInternetConnection: () {
errorMessage = "No internet connection";
}, conflict: () {
errorMessage = "Error due to a conflict";
}, sendTimeout: () {
errorMessage = "Send timeout in connection with API server";
}, unableToProcess: () {
errorMessage = "Unable to process the data";
}, defaultError: (String error) {
errorMessage = error;
}, formatException: () {
errorMessage = "Unexpected error occurred";
}, notAcceptable: () {
errorMessage = "Not acceptable";
});
return errorMessage;
}
查看完整的网络异常类
| import 'dart:io'; | |
| import 'package:dio/dio.dart'; | |
| import 'package:freezed_annotation/freezed_annotation.dart'; | |
| part 'network_exceptions.freezed.dart'; | |
| @freezed | |
| abstract class NetworkExceptions with _$NetworkExceptions { | |
| const factory NetworkExceptions.requestCancelled() = RequestCancelled; | |
| const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest; | |
| const factory NetworkExceptions.badRequest() = BadRequest; | |
| const factory NetworkExceptions.notFound(String reason) = NotFound; | |
| const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed; | |
| const factory NetworkExceptions.notAcceptable() = NotAcceptable; | |
| const factory NetworkExceptions.requestTimeout() = RequestTimeout; | |
| const factory NetworkExceptions.sendTimeout() = SendTimeout; | |
| const factory NetworkExceptions.conflict() = Conflict; | |
| const factory NetworkExceptions.internalServerError() = InternalServerError; | |
| const factory NetworkExceptions.notImplemented() = NotImplemented; | |
| const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable; | |
| const factory NetworkExceptions.noInternetConnection() = NoInternetConnection; | |
| const factory NetworkExceptions.formatException() = FormatException; | |
| const factory NetworkExceptions.unableToProcess() = UnableToProcess; | |
| const factory NetworkExceptions.defaultError(String error) = DefaultError; | |
| const factory NetworkExceptions.unexpectedError() = UnexpectedError; | |
| static NetworkExceptions getDioException(error) { | |
| if (error is Exception) { | |
| try { | |
| NetworkExceptions networkExceptions; | |
| if (error is DioError) { | |
| switch (error.type) { | |
| case DioErrorType.CANCEL: | |
| networkExceptions = NetworkExceptions.requestCancelled(); | |
| break; | |
| case DioErrorType.CONNECT_TIMEOUT: | |
| networkExceptions = NetworkExceptions.requestTimeout(); | |
| break; | |
| case DioErrorType.DEFAULT: | |
| networkExceptions = NetworkExceptions.noInternetConnection(); | |
| break; | |
| case DioErrorType.RECEIVE_TIMEOUT: | |
| networkExceptions = NetworkExceptions.sendTimeout(); | |
| break; | |
| case DioErrorType.RESPONSE: | |
| switch (error.response.statusCode) { | |
| case 400: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 401: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 403: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 404: | |
| networkExceptions = NetworkExceptions.notFound("Not found"); | |
| break; | |
| case 409: | |
| networkExceptions = NetworkExceptions.conflict(); | |
| break; | |
| case 408: | |
| networkExceptions = NetworkExceptions.requestTimeout(); | |
| break; | |
| case 500: | |
| networkExceptions = NetworkExceptions.internalServerError(); | |
| break; | |
| case 503: | |
| networkExceptions = NetworkExceptions.serviceUnavailable(); | |
| break; | |
| default: | |
| var responseCode = error.response.statusCode; | |
| networkExceptions = NetworkExceptions.defaultError( | |
| "Received invalid status code: $responseCode", | |
| ); | |
| } | |
| break; | |
| case DioErrorType.SEND_TIMEOUT: | |
| networkExceptions = NetworkExceptions.sendTimeout(); | |
| break; | |
| } | |
| } else if (error is SocketException) { | |
| networkExceptions = NetworkExceptions.noInternetConnection(); | |
| } else { | |
| networkExceptions = NetworkExceptions.unexpectedError(); | |
| } | |
| return networkExceptions; | |
| } on FormatException catch (e) { | |
| // Helper.printError(e.toString()); | |
| return NetworkExceptions.formatException(); | |
| } catch (_) { | |
| return NetworkExceptions.unexpectedError(); | |
| } | |
| } else { | |
| if (error.toString().contains("is not a subtype of")) { | |
| return NetworkExceptions.unableToProcess(); | |
| } else { | |
| return NetworkExceptions.unexpectedError(); | |
| } | |
| } | |
| } | |
| static String getErrorMessage(NetworkExceptions networkExceptions) { | |
| var errorMessage = ""; | |
| networkExceptions.when(notImplemented: () { | |
| errorMessage = "Not Implemented"; | |
| }, requestCancelled: () { | |
| errorMessage = "Request Cancelled"; | |
| }, internalServerError: () { | |
| errorMessage = "Internal Server Error"; | |
| }, notFound: (String reason) { | |
| errorMessage = reason; | |
| }, serviceUnavailable: () { | |
| errorMessage = "Service unavailable"; | |
| }, methodNotAllowed: () { | |
| errorMessage = "Method Allowed"; | |
| }, badRequest: () { | |
| errorMessage = "Bad request"; | |
| }, unauthorisedRequest: () { | |
| errorMessage = "Unauthorised request"; | |
| }, unexpectedError: () { | |
| errorMessage = "Unexpected error occurred"; | |
| }, requestTimeout: () { | |
| errorMessage = "Connection request timeout"; | |
| }, noInternetConnection: () { | |
| errorMessage = "No internet connection"; | |
| }, conflict: () { | |
| errorMessage = "Error due to a conflict"; | |
| }, sendTimeout: () { | |
| errorMessage = "Send timeout in connection with API server"; | |
| }, unableToProcess: () { | |
| errorMessage = "Unable to process the data"; | |
| }, defaultError: (String error) { | |
| errorMessage = error; | |
| }, formatException: () { | |
| errorMessage = "Unexpected error occurred"; | |
| }, notAcceptable: () { | |
| errorMessage = "Not acceptable"; | |
| }); | |
| return errorMessage; | |
| } | |
| } |
| import 'dart:io'; | |
| import 'package:dio/dio.dart'; | |
| import 'package:freezed_annotation/freezed_annotation.dart'; | |
| part 'network_exceptions.freezed.dart'; | |
| @freezed | |
| abstract class NetworkExceptions with _$NetworkExceptions { | |
| const factory NetworkExceptions.requestCancelled() = RequestCancelled; | |
| const factory NetworkExceptions.unauthorisedRequest() = UnauthorisedRequest; | |
| const factory NetworkExceptions.badRequest() = BadRequest; | |
| const factory NetworkExceptions.notFound(String reason) = NotFound; | |
| const factory NetworkExceptions.methodNotAllowed() = MethodNotAllowed; | |
| const factory NetworkExceptions.notAcceptable() = NotAcceptable; | |
| const factory NetworkExceptions.requestTimeout() = RequestTimeout; | |
| const factory NetworkExceptions.sendTimeout() = SendTimeout; | |
| const factory NetworkExceptions.conflict() = Conflict; | |
| const factory NetworkExceptions.internalServerError() = InternalServerError; | |
| const factory NetworkExceptions.notImplemented() = NotImplemented; | |
| const factory NetworkExceptions.serviceUnavailable() = ServiceUnavailable; | |
| const factory NetworkExceptions.noInternetConnection() = NoInternetConnection; | |
| const factory NetworkExceptions.formatException() = FormatException; | |
| const factory NetworkExceptions.unableToProcess() = UnableToProcess; | |
| const factory NetworkExceptions.defaultError(String error) = DefaultError; | |
| const factory NetworkExceptions.unexpectedError() = UnexpectedError; | |
| static NetworkExceptions getDioException(error) { | |
| if (error is Exception) { | |
| try { | |
| NetworkExceptions networkExceptions; | |
| if (error is DioError) { | |
| switch (error.type) { | |
| case DioErrorType.CANCEL: | |
| networkExceptions = NetworkExceptions.requestCancelled(); | |
| break; | |
| case DioErrorType.CONNECT_TIMEOUT: | |
| networkExceptions = NetworkExceptions.requestTimeout(); | |
| break; | |
| case DioErrorType.DEFAULT: | |
| networkExceptions = NetworkExceptions.noInternetConnection(); | |
| break; | |
| case DioErrorType.RECEIVE_TIMEOUT: | |
| networkExceptions = NetworkExceptions.sendTimeout(); | |
| break; | |
| case DioErrorType.RESPONSE: | |
| switch (error.response.statusCode) { | |
| case 400: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 401: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 403: | |
| networkExceptions = NetworkExceptions.unauthorisedRequest(); | |
| break; | |
| case 404: | |
| networkExceptions = NetworkExceptions.notFound("Not found"); | |
| break; | |
| case 409: | |
| networkExceptions = NetworkExceptions.conflict(); | |
| break; | |
| case 408: | |
| networkExceptions = NetworkExceptions.requestTimeout(); | |
| break; | |
| case 500: | |
| networkExceptions = NetworkExceptions.internalServerError(); | |
| break; | |
| case 503: | |
| networkExceptions = NetworkExceptions.serviceUnavailable(); | |
| break; | |
| default: | |
| var responseCode = error.response.statusCode; | |
| networkExceptions = NetworkExceptions.defaultError( | |
| "Received invalid status code: $responseCode", | |
| ); | |
| } | |
| break; | |
| case DioErrorType.SEND_TIMEOUT: | |
| networkExceptions = NetworkExceptions.sendTimeout(); | |
| break; | |
| } | |
| } else if (error is SocketException) { | |
| networkExceptions = NetworkExceptions.noInternetConnection(); | |
| } else { | |
| networkExceptions = NetworkExceptions.unexpectedError(); | |
| } | |
| return networkExceptions; | |
| } on FormatException catch (e) { | |
| // Helper.printError(e.toString()); | |
| return NetworkExceptions.formatException(); | |
| } catch (_) { | |
| return NetworkExceptions.unexpectedError(); | |
| } | |
| } else { | |
| if (error.toString().contains("is not a subtype of")) { | |
| return NetworkExceptions.unableToProcess(); | |
| } else { | |
| return NetworkExceptions.unexpectedError(); | |
| } | |
| } | |
| } | |
| static String getErrorMessage(NetworkExceptions networkExceptions) { | |
| var errorMessage = ""; | |
| networkExceptions.when(notImplemented: () { | |
| errorMessage = "Not Implemented"; | |
| }, requestCancelled: () { | |
| errorMessage = "Request Cancelled"; | |
| }, internalServerError: () { | |
| errorMessage = "Internal Server Error"; | |
| }, notFound: (String reason) { | |
| errorMessage = reason; | |
| }, serviceUnavailable: () { | |
| errorMessage = "Service unavailable"; | |
| }, methodNotAllowed: () { | |
| errorMessage = "Method Allowed"; | |
| }, badRequest: () { | |
| errorMessage = "Bad request"; | |
| }, unauthorisedRequest: () { | |
| errorMessage = "Unauthorised request"; | |
| }, unexpectedError: () { | |
| errorMessage = "Unexpected error occurred"; | |
| }, requestTimeout: () { | |
| errorMessage = "Connection request timeout"; | |
| }, noInternetConnection: () { | |
| errorMessage = "No internet connection"; | |
| }, conflict: () { | |
| errorMessage = "Error due to a conflict"; | |
| }, sendTimeout: () { | |
| errorMessage = "Send timeout in connection with API server"; | |
| }, unableToProcess: () { | |
| errorMessage = "Unable to process the data"; | |
| }, defaultError: (String error) { | |
| errorMessage = error; | |
| }, formatException: () { | |
| errorMessage = "Unexpected error occurred"; | |
| }, notAcceptable: () { | |
| errorMessage = "Not acceptable"; | |
| }); | |
| return errorMessage; | |
| } | |
| } |
调用APIRepository类中的API
我正在使用movieDBAPI 来获取热门电影。
| class MovieResponse { | |
| int page; | |
| int totalResults; | |
| int totalPages; | |
| List<Movie> results; | |
| MovieResponse({this.page, this.totalResults, this.totalPages, this.results}); | |
| MovieResponse.fromJson(Map<String, dynamic> json) { | |
| page = json['page']; | |
| totalResults = json['total_results']; | |
| totalPages = json['total_pages']; | |
| if (json['results'] != null) { | |
| results = new List<Movie>(); | |
| json['results'].forEach((v) { | |
| results.add(new Movie.fromJson(v)); | |
| }); | |
| } | |
| } | |
| Map<String, dynamic> toJson() { | |
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |
| data['page'] = this.page; | |
| data['total_results'] = this.totalResults; | |
| data['total_pages'] = this.totalPages; | |
| if (this.results != null) { | |
| data['results'] = this.results.map((v) => v.toJson()).toList(); | |
| } | |
| return data; | |
| } | |
| } | |
| class Movie { | |
| int voteCount; | |
| int id; | |
| bool video; | |
| var voteAverage; | |
| String title; | |
| double popularity; | |
| String posterPath; | |
| String originalLanguage; | |
| String originalTitle; | |
| List<int> genreIds; | |
| String backdropPath; | |
| bool adult; | |
| String overview; | |
| String releaseDate; | |
| Movie( | |
| {this.voteCount, | |
| this.id, | |
| this.video, | |
| this.voteAverage, | |
| this.title, | |
| this.popularity, | |
| this.posterPath, | |
| this.originalLanguage, | |
| this.originalTitle, | |
| this.genreIds, | |
| this.backdropPath, | |
| this.adult, | |
| this.overview, | |
| this.releaseDate}); | |
| Movie.fromJson(Map<String, dynamic> json) { | |
| voteCount = json['vote_count']; | |
| id = json['id']; | |
| video = json['video']; | |
| voteAverage = json['vote_average']; | |
| title = json['title']; | |
| popularity = json['popularity']; | |
| posterPath = json['poster_path']; | |
| originalLanguage = json['original_language']; | |
| originalTitle = json['original_title']; | |
| genreIds = json['genre_ids'].cast<int>(); | |
| backdropPath = json['backdrop_path']; | |
| adult = json['adult']; | |
| overview = json['overview']; | |
| releaseDate = json['release_date']; | |
| } | |
| Map<String, dynamic> toJson() { | |
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |
| data['vote_count'] = this.voteCount; | |
| data['id'] = this.id; | |
| data['video'] = this.video; | |
| data['vote_average'] = this.voteAverage; | |
| data['title'] = this.title; | |
| data['popularity'] = this.popularity; | |
| data['poster_path'] = this.posterPath; | |
| data['original_language'] = this.originalLanguage; | |
| data['original_title'] = this.originalTitle; | |
| data['genre_ids'] = this.genreIds; | |
| data['backdrop_path'] = this.backdropPath; | |
| data['adult'] = this.adult; | |
| data['overview'] = this.overview; | |
| data['release_date'] = this.releaseDate; | |
| return data; | |
| } | |
| } |
| class MovieResponse { | |
| int page; | |
| int totalResults; | |
| int totalPages; | |
| List<Movie> results; | |
| MovieResponse({this.page, this.totalResults, this.totalPages, this.results}); | |
| MovieResponse.fromJson(Map<String, dynamic> json) { | |
| page = json['page']; | |
| totalResults = json['total_results']; | |
| totalPages = json['total_pages']; | |
| if (json['results'] != null) { | |
| results = new List<Movie>(); | |
| json['results'].forEach((v) { | |
| results.add(new Movie.fromJson(v)); | |
| }); | |
| } | |
| } | |
| Map<String, dynamic> toJson() { | |
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |
| data['page'] = this.page; | |
| data['total_results'] = this.totalResults; | |
| data['total_pages'] = this.totalPages; | |
| if (this.results != null) { | |
| data['results'] = this.results.map((v) => v.toJson()).toList(); | |
| } | |
| return data; | |
| } | |
| } | |
| class Movie { | |
| int voteCount; | |
| int id; | |
| bool video; | |
| var voteAverage; | |
| String title; | |
| double popularity; | |
| String posterPath; | |
| String originalLanguage; | |
| String originalTitle; | |
| List<int> genreIds; | |
| String backdropPath; | |
| bool adult; | |
| String overview; | |
| String releaseDate; | |
| Movie( | |
| {this.voteCount, | |
| this.id, | |
| this.video, | |
| this.voteAverage, | |
| this.title, | |
| this.popularity, | |
| this.posterPath, | |
| this.originalLanguage, | |
| this.originalTitle, | |
| this.genreIds, | |
| this.backdropPath, | |
| this.adult, | |
| this.overview, | |
| this.releaseDate}); | |
| Movie.fromJson(Map<String, dynamic> json) { | |
| voteCount = json['vote_count']; | |
| id = json['id']; | |
| video = json['video']; | |
| voteAverage = json['vote_average']; | |
| title = json['title']; | |
| popularity = json['popularity']; | |
| posterPath = json['poster_path']; | |
| originalLanguage = json['original_language']; | |
| originalTitle = json['original_title']; | |
| genreIds = json['genre_ids'].cast<int>(); | |
| backdropPath = json['backdrop_path']; | |
| adult = json['adult']; | |
| overview = json['overview']; | |
| releaseDate = json['release_date']; | |
| } | |
| Map<String, dynamic> toJson() { | |
| final Map<String, dynamic> data = new Map<String, dynamic>(); | |
| data['vote_count'] = this.voteCount; | |
| data['id'] = this.id; | |
| data['video'] = this.video; | |
| data['vote_average'] = this.voteAverage; | |
| data['title'] = this.title; | |
| data['popularity'] = this.popularity; | |
| data['poster_path'] = this.posterPath; | |
| data['original_language'] = this.originalLanguage; | |
| data['original_title'] = this.originalTitle; | |
| data['genre_ids'] = this.genreIds; | |
| data['backdrop_path'] = this.backdropPath; | |
| data['adult'] = this.adult; | |
| data['overview'] = this.overview; | |
| data['release_date'] = this.releaseDate; | |
| return data; | |
| } | |
| } |
import 'package:dio/dio.dart';
import 'package:network_handling/model/movie_response.dart';
import 'package:network_handling/services/api_result.dart';
import 'package:network_handling/services/dio_client.dart';
import 'package:network_handling/services/network_exceptions.dart';
class APIRepository {
DioClient dioClient;
final String _apiKey = <apikey>;
String _baseUrl = "http://api.themoviedb.org/3/";
APIRepository() {
var dio = Dio();
dioClient = DioClient(_baseUrl, dio);
}
Future<ApiResult<List<Movie>>> fetchMovieList() async {
try {
final response = await dioClient
.get("movie/popular", queryParameters: {"api_key": _apiKey});
List<Movie> movieList = MovieResponse.fromJson(response).results;
return ApiResult.success(data: movieList);
} catch (e) {
return ApiResult.failure(error: NetworkExceptions.getDioException(e));
}
}
}
现在,这只是 API 调用,如何在 UI 中显示它?
我用它来flutter_bloc管理 UI 中的数据
在bloc课堂上,我们需要两件东西
由于我们只有一个调用 API 的事件,因此类中只有一个事件
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'movie_event.freezed.dart';
@freezed
abstract class MovieEvent with _$MovieEvent {
const factory MovieEvent.loadMovie() = LoadMovies;
}
现在我们需要了解为什么这很重要。
当我们打开任何应用程序时,我们通常会看到一些事件发生,例如加载、显示数据、显示错误。
这些就是我们需要在应用程序中实现的,所以所有这些事件都将是我们的结果状态。
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:network_handling/services/network_exceptions.dart';
part 'result_state.freezed.dart';
@freezed
abstract class ResultState<T> with _$ResultState<T> {
const factory ResultState.idle() = Idle<T>;
const factory ResultState.loading() = Loading<T>;
const factory ResultState.data({@required T data}) = Data<T>;
const factory ResultState.error({@required NetworkExceptions error}) =
Error<T>;
}
现在生成这些文件
flutter packages pub run build_runner build
现在我们已经创建了事件和状态,让我们创建 Bloc 类。
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:network_handling/api_repository.dart';
import 'package:network_handling/bloc/movie/movie_event.dart';
import 'package:network_handling/bloc/movie/result_state.dart';
import 'package:network_handling/model/movie_response.dart';
class MovieBloc extends Bloc<MovieEvent, ResultState<List<Movie>>> {
final APIRepository apiRepository;
MovieBloc({this.apiRepository})
: assert(apiRepository != null),
super(Idle());
@override
Stream<ResultState<List<Movie>>> mapEventToState(MovieEvent event) {
}
}
现在我们需要在这里调用 API,并将状态沉入区块中
class MovieBloc extends Bloc<MovieEvent, ResultState<List<Movie>>> {
final APIRepository apiRepository;
MovieBloc({this.apiRepository})
: assert(apiRepository != null),
super(Idle());
@override
Stream<ResultState<List<Movie>>> mapEventToState(MovieEvent event) async* {
yield ResultState.loading();
if (event is LoadMovies) {
ApiResult<List<Movie>> apiResult = await apiRepository.fetchMovieList();
yield* apiResult.when(success: (List<Movie> data) async* {
yield ResultState.data(data: data);
}, failure: (NetworkExceptions error) async* {
yield ResultState.error(error: error);
});
}
}
}
如果你看到我们已经实现了所有状态
Idle一开始的状态Loading状态现在我们已经创建了Bloc类并实现了其中的逻辑,现在我们需要在 UI 中使用这个 bloc 类。
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<MovieBloc>(
create: (BuildContext context) {
return MovieBloc(apiRepository: APIRepository());
},
child: MyHomePage(),
)
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(),
),
);
}
}
将 BlocProvider 添加到小部件树的顶部,以便我们可以访问数据。
现在HomePage我们将调用 API
@override
void initState() {
context.bloc<MovieBloc>().add(LoadMovies());
super.initState();
}
我们将会聆听其中的变化BlocBuilder。
BlocBuilder<MovieBloc, ResultState<List<Movie>>>(
builder: (BuildContext context, ResultState<List<Movie>> state) {
return state.when(
loading: () {
return Center(child: CircularProgressIndicator());
},
idle: () {
return Container();
},
data: (List<Movie> data) {
return dataWidget(data);
},
error: (NetworkExceptions error) {
return Text(NetworkExceptions.getErrorMessage(error));
},
);
},
)
查看UI代码
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
context.bloc<MovieBloc>().add(LoadMovies());
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Movies"),
),
body: BlocBuilder<MovieBloc, ResultState<List<Movie>>>(
builder: (BuildContext context, ResultState<List<Movie>> state) {
return state.when(
loading: () {
return Center(child: CircularProgressIndicator());
},
idle: () {
return Container();
},
data: (List<Movie> data) {
return dataWidget(data);
},
error: (NetworkExceptions error) {
return Text(NetworkExceptions.getErrorMessage(error));
},
);
},
),
);
}
Widget dataWidget(List<Movie> data) {
return ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 300,
width: 300,
child: Card(
elevation: 1,
child: Image.network(
"https://image.tmdb.org/t/p/w342${data[index].posterPath}",
),
),
);
},
);
}
}
现在让我们看看我们构建了什么:
点击此处查看完整代码
感谢您阅读这篇文章❤
如果我有错误,请在评论区告诉我。我很乐意改进。
让我们联系起来:https://www.ashishrawat.dev
鏂囩珷鏉ユ簮锛�https://dev.to/ashishrawat2911/handling-network-calls-and-exceptions-in-flutter-54me