Flutter 中的网络调用和异常处理 FLutter 中的网络和错误处理

2025-06-08

Flutter 中网络调用和异常的处理

FLutter 中的网络和错误处理

在本文中,我将向您展示如何使用dioflutter_blocfreezed包处理网络调用和异常

首先,我们需要在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:


Enter fullscreen mode Exit fullscreen mode

我们正在使用


首先,我们需要添加以设置 API 调用。

创建 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;
}
}
}
view raw dio_client.dart hosted with ❤ by GitHub
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;
}
}
}
view raw dio_client.dart hosted with ❤ by GitHub

我们将使用存储库访问此类



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);
  }
}



Enter fullscreen mode Exit fullscreen mode

现在,如果你看到当我们点击 API 时,我们会得到 2 个结果

Api 调用
--->> 成功 🏆
--->> 失败 ❌

创建 API 结果处理程序

创建两个freezed类来处理数据,

  • API 结果
  • 网络异常(如果 API 结果返回失败)

api_result.dart



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>;
}



Enter fullscreen mode Exit fullscreen mode

网络异常.dart



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;

}



Enter fullscreen mode Exit fullscreen mode

如果您看到该类ApiResult,如果成功,我将返回T类型的数据,但如果失败,我必须返回网络异常。

我将如何返回网络异常并确定发生了哪个网络异常?

现在你已经看到了这一行,part of'<file-name>.freezed.dart'
我们需要生成冻结文件

  • 在终端中运行此命令```

Flutter 软件包 pub run build_runner 构建

- Run this command in terminal to watch auto change
Enter fullscreen mode Exit fullscreen mode

Flutter 软件包 pub run build_runner watch

- Run this command in terminal to watch auto change and delete previously generated files
Enter fullscreen mode Exit fullscreen mode

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();
      }
    }
  }


Enter fullscreen mode Exit fullscreen mode

此方法将返回 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;
  }


Enter fullscreen mode Exit fullscreen mode

查看完整的网络异常类

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 来获取热门电影。

创建一个 PODO 来解析来自 API 的响应 JSON。

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;
}
}

在 APIRepository 类中获取电影 API



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));
    }
  }
}



Enter fullscreen mode Exit fullscreen mode

现在,这只是 API 调用,如何在 UI 中显示它?

我用它来flutter_bloc管理 UI 中的数据

创建一个 Bloc 类

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;
}


Enter fullscreen mode Exit fullscreen mode

创建状态类

现在我们需要了解为什么这很重要。

当我们打开任何应用程序时,我们通常会看到一些事件发生,例如加载、显示数据、显示错误。
这些就是我们需要在应用程序中实现的,所以所有这些事件都将是我们的结果状态。

  • 闲置的
  • 加载中
  • 数据
  • 错误


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>;
}



Enter fullscreen mode Exit fullscreen mode

现在生成这些文件



flutter packages pub run build_runner build


Enter fullscreen mode Exit fullscreen mode

现在我们已经创建了事件和状态,让我们创建 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) {

  }
}



Enter fullscreen mode Exit fullscreen mode

现在我们需要在这里调用 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);

      });
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

如果你看到我们已经实现了所有状态

  • Idle一开始的状态
  • 发生一个事件,它开始传递Loading状态
  • 最后,是否返回数据或错误取决于 API 调用。

现在我们已经创建了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(),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

将 BlocProvider 添加到小部件树的顶部,以便我们可以访问数据。

现在HomePage我们将调用 API



  @override
  void initState() {

    context.bloc<MovieBloc>().add(LoadMovies());

    super.initState();
  }


Enter fullscreen mode Exit fullscreen mode

我们将会聆听其中的变化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));
            },
          );
        },
      )


Enter fullscreen mode Exit fullscreen mode

查看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}",
            ),
          ),
        );
      },
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

现在让我们看看我们构建了什么:

  • 当我们从 API 获得结果时
    替代文本

  • 当我们遇到错误时
    替代文本

点击此处查看完整代码

GitHub 徽标 ashishrawat2911 /网络处理

Flutter 中的网络和异常处理

FLutter 中的网络和错误处理

使用 Dio、flutter_bloc 和 Freezed 包在 Flutter 中进行网络和错误处理

截图







感谢您阅读这篇文章❤

如果我有错误,请在评论区告诉我。我很乐意改进。

让我们联系起来:https://www.ashishrawat.dev

鏂囩珷鏉ユ簮锛�https://dev.to/ashishrawat2911/handling-network-calls-and-exceptions-in-flutter-54me
PREV
10 个关注点以及如何让整个 React 代码库更简洁
NEXT
Llama 3.3 与 OpenAI O1