Flutter异常处理插件exception_handler的使用

发布于 1周前 作者 h691938207 来自 Flutter

Flutter异常处理插件exception_handler的使用

介绍

exception_handler 是一个 Dart 包,为在 Flutter 应用中处理 API 调用和异常提供了一个健壮的框架。它简化了网络请求、响应解析以及各种异常处理的过程,是 Flutter 开发者的重要工具。

特性

  • API 处理:通过结构化的方法简化 API 调用,确保代码清晰且易于维护。
  • 异常管理:全面的异常处理,包括网络问题和 HTTP 错误,提高应用的健壮性。

入门指南

要开始使用这个包,请在 pubspec.yaml 文件中添加依赖:

dependencies:
  exception_handler: ^latest_version

然后,在你想要使用的 Dart 文件中导入它:

import 'package:exception_handler/exception_handler.dart';

使用示例

以下是一些示例,展示如何使用该包的各种功能。

定义模型

首先创建数据模型,并为其定义一个用于 JSON 反序列化的工厂构造函数。例如,UserModel 模型如下所示:

class UserModel {
  final int id;
  final String name;
  final String email;

  UserModel({required this.id, required this.name, required this.email});

  factory UserModel.fromJson(Map<String, dynamic> json) {
    return UserModel(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}
基本 API 调用

这是一个简单的例子,展示如何进行 API 调用并处理响应:

import 'package:dio/dio.dart';
import 'package:exception_handler/exception_handler.dart';

Future<void> fetchUserData() async {
  final ApiHandler<Response, UserModel> apiHandler = ApiHandler(
    apiCall: () => dio.get('https://example.com/api/user'),
    parserModel: (data) => UserModel.fromJson(data),
  );

  ResultState<UserModel> result = await DioExceptionHandler.callApi_(apiHandler);

  switch (result) {
    case SuccessState<UserModel>(:UserModel data):
      print('UserModel data: $data');
    case FailureState<UserModel>(:ExceptionState exception):
      print('Error: ${exception.toString()}');
  }
}
高级 API 调用与自定义解析器

使用自定义解析器来处理复杂的 API 响应:

import 'package:dio/dio.dart';
import 'package:exception_handler/exception_handler.dart';

Future<void> fetchComplexData() async {
  final ApiHandler<Response, ComplexData> apiHandler = ApiHandler(
    apiCall: () => dio.get('https://example.com/api/complex'),
    parserModel: customParser,
  );

  ResultState<ComplexData> result = await DioExceptionHandler.callApi_(apiHandler);

  switch (result) {
    case SuccessState<ComplexData>(:ComplexData data):
      print('Complex Data: $data');
    case FailureState<ComplexData>(:ExceptionState exception):
      print('Error: ${exception.toString()}');
  }
}

ComplexData customParser(dynamic responseData) {
  // 自定义解析逻辑
  return ComplexData.fromResponse(responseData);
}
基本异常处理

处理基本异常并记录信息:

void handleApiCall() async {
  ResultState<UserModel> result = await DioExceptionHandler.callApi_(apiHandler);

  switch (result) {
    case SuccessState<UserModel>(:UserModel data):
      print('User data retrieved successfully: $data');
    case FailureState<UserModel>(:ExceptionState exception):
      print('Exception occurred: ${exception.toString()}');
      // 额外的日志或错误处理
  }
}
高级异常处理与特定情况

实现详细的异常处理:

void advancedExceptionHandling() async {
  ResultState<UserModel> result = await DioExceptionHandler.callApi_(apiHandler);

  switch (result) {
    case SuccessState<UserModel>(:UserModel data):
      print('Fetched data: $data');

    case FailureState<UserModel>(:ExceptionState exception):
      _handleExceptions(exception);
  }
}

void _handleExceptions(ExceptionState exception) {
  switch (exception) {
    case DataClientExceptionState():
      handleClientException(exception);
    case DataParseExceptionState():
      handleParseException(exception);
    case DataHttpExceptionState():
      handleHttpException(exception);
    case DataNetworkExceptionState():
      handleNetworkException(exception);
    case _:
      handleUnknownException(exception);
  }
}

void handleNetworkException(DataNetworkExceptionState exception) {
  print('Network Exception: ${exception.networkException}');
  // 处理网络异常的额外逻辑
}

void handleHttpException(DataHttpExceptionState exception) {
  print('HTTP Exception: ${exception.httpException}');
  // 处理HTTP异常的额外逻辑
}

void handleParseException(DataParseExceptionState exception) {
  print('Parse Exception: ${exception.parseException}');
  // 处理解析异常的额外逻辑
}

void handleClientException(DataClientExceptionState exception) {
  print('Client Exception: ${exception.clientException}');
  // 处理客户端异常的额外逻辑
}

void handleUnknownException(ExceptionState exception) {
  print('Unknown Exception: ${exception.toString()}');
  // 处理未知异常的额外逻辑
}

示例完整 Demo

以下是一个完整的 Flutter 应用示例,展示如何使用 exception_handler 插件处理 API 请求和异常:

import 'dart:math';
import 'package:dio/dio.dart';
import 'package:exception_handler/exception_handler.dart';
import 'package:flutter/material.dart';

// UserModel 类定义
class UserModel extends CustomEquatable {
  const UserModel({
    this.id,
    this.name,
    this.username,
    this.email,
    this.address,
    this.phone,
    this.website,
    this.company,
  });

  factory UserModel.fromJson(Map<String, dynamic> json) {
    if (json case {
          'id': int? id,
          'name': String? name,
          'username': String? username,
          'email': String? email,
          'address': Map<String, dynamic>? address,
          'phone': String? phone,
          'website': String? website,
          'company': Map<String, dynamic>? company,
        }) {
      return UserModel(
        id: id,
        name: name,
        username: username,
        email: email,
        address: address != null ? Address.fromJson(address) : null,
        phone: phone,
        website: website,
        company: company != null ? Company.fromJson(company) : null,
      );
    } else {
      throw FormatException('Invalid JSON: $json');
    }
  }

  final Address? address;
  final Company? company;
  final String? email;
  final int? id;
  final String? name;
  final String? phone;
  final String? username;
  final String? website;

  [@override](/user/override)
  Map<String, Object?> get namedProps => {
        'id': id,
        'name': name,
        'username': username,
        'email': email,
        'address': address,
        'phone': phone,
        'website': website,
        'company': company,
      };

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['id'] = id;
    data['name'] = name;
    data['username'] = username;
    data['email'] = email;
    if (address != null) {
      data['address'] = address!.toJson();
    }
    data['phone'] = phone;
    data['website'] = website;
    if (company != null) {
      data['company'] = company!.toJson();
    }
    return data;
  }
}

// Address 类定义
class Address extends CustomEquatable {
  const Address({
    this.street,
    this.suite,
    this.city,
    this.zipcode,
    this.geo,
  });

  factory Address.fromJson(Map<String, dynamic> json) {
    if (json case {
          'street': String? street,
          'suite': String? suite,
          'city': String? city,
          'zipcode': String? zipcode,
          'geo': Map<String, dynamic>? geo,
        }) {
      return Address(
        street: street,
        suite: suite,
        city: city,
        zipcode: zipcode,
        geo: geo != null ? Geo.fromJson(geo) : null,
      );
    } else {
      throw FormatException('Invalid JSON: $json');
    }
  }

  final String? city;
  final Geo? geo;
  final String? street;
  final String? suite;
  final String? zipcode;

  [@override](/user/override)
  Map<String, Object?> get namedProps => {
        'suite': suite,
        'street': street,
        'city': city,
        'zipcode': zipcode,
        'geo': geo,
      };

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['street'] = street;
    data['suite'] = suite;
    data['city'] = city;
    data['zipcode'] = zipcode;
    if (geo != null) {
      data['geo'] = geo!.toJson();
    }
    return data;
  }
}

// Geo 类定义
class Geo extends CustomEquatable {
  const Geo({this.lat, this.lng});

  factory Geo.fromJson(Map<String, dynamic> json) {
    if (json case {
          'lat': String? lat,
          'lng': String? lng,
        }) {
      final Geo geo = Geo(lat: lat, lng: lng);
      return geo;
    } else {
      throw FormatException('Invalid JSON: $json');
    }
  }

  final String? lat;
  final String? lng;

  [@override](/user/override)
  Map<String, Object?> get namedProps => {
        'lat': lat,
        'lng': lng,
      };

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['lat'] = lat;
    data['lng'] = lng;
    return data;
  }
}

// Company 类定义
class Company extends CustomEquatable {
  const Company({this.name, this.catchPhrase, this.bs});

  factory Company.fromJson(Map<String, dynamic> json) {
    if (json case {
          'name': String? name,
          'catchPhrase': String? catchPhrase,
          'bs': String? bs,
        }) {
      return Company(
        name: name,
        catchPhrase: catchPhrase,
        bs: bs,
      );
    } else {
      throw FormatException('Invalid JSON: $json');
    }
  }

  final String? bs;
  final String? catchPhrase;
  final String? name;

  [@override](/user/override)
  Map<String, Object?> get namedProps => {
        'name': name,
        'catchPhrase': catchPhrase,
        'bs': bs,
      };

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = <String, dynamic>{};
    data['name'] = name;
    data['catchPhrase'] = catchPhrase;
    data['bs'] = bs;
    return data;
  }
}

// UserService 类定义
class UserService {
  final Dio dio = Dio();

  Future<ResultState<UserModel>> getDataUser(int id) async {
    final ResultState<UserModel> result = await DioExceptionHandler.callApi_<Response, UserModel>(
      ApiHandler(
        apiCall: () => dio.get('https://jsonplaceholder.typicode.com/users/$id'),
        parserModel: (Object? data) => UserModel.fromJson(data as Map<String, dynamic>),
      ),
    );
    return result;
  }

  Future<ResultState<UserModel>> getDataUserExtensionDio(int id) async {
    final ResultState<UserModel> result = await dio
        .get('https://jsonplaceholder.typicode.com/users/$id')
        .fromJson(UserModel.fromJson);

    return result;
  }
}

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 1;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text(
                'You have pushed the button this many times:',
              ),
              Text(
                'ID: $_counter',
                style: Theme.of(context).textTheme.headlineMedium,
              ),
              const SizedBox(height: 16),
              FutureBuilder(
                future: UserService().getDataUserExtensionDio(_counter),
                builder: (
                  BuildContext context,
                  AsyncSnapshot<ResultState<UserModel>> snapshot,
                ) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return const CircularProgressIndicator();
                  }
                  final ResultState<UserModel> resultState = snapshot.requireData;

                  final StatelessWidget uiWidget = switch (resultState) {
                    SuccessState<UserModel> success =>
                      UiUserWidget(success.data),
                    FailureState<UserModel> failure =>
                      UiExceptionWidget(failure.exception),
                  };
                  return uiWidget;
                },
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: _decrementCounter,
            tooltip: 'Decrement',
            child: const Icon(Icons.remove),
          ),
          const SizedBox(width: 10),
          FloatingActionButton(
            onPressed: _incrementCounter,
            tooltip: 'Increment',
            child: const Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}

// UiExceptionWidget 类定义
class UiExceptionWidget extends StatelessWidget {
  const UiExceptionWidget(
    this.exception, {
    super.key,
  });

  static const List<String> links = [
    'https://http.pizza',
    'https://http.garden',
    'https://httpducks.com',
    'https://httpgoats.com',
    'https://http.dog',
    'https://httpcats.com',
  ];

  final ExceptionState<UserModel> exception;

  [@override](/user/override)
  Widget build(BuildContext context) {
    final String textException = switch (exception) {
      DataClientExceptionState<UserModel>() =>
        'Debugger Error Client: $exception',
      DataParseExceptionState<UserModel>() =>
        'Debugger Error Parse: $exception',
      DataHttpExceptionState<UserModel>() => 'Debugger Error Http: $exception',
      DataNetworkExceptionState<UserModel>() =>
        'Debugger Error Network: $exception\n\nError: ${exception.toString().split('.').last}',
      DataCacheExceptionState<UserModel>() =>
        'Debugger Error Cache: $exception',
      DataInvalidInputExceptionState<UserModel>() =>
        'Debugger Error Invalid Input: $exception',
      DataUnknownExceptionState<UserModel>() =>
        'Debugger Error Unknown: $exception',
    };

    final Text text = Text(
      textException,
      style: TextStyle(color: Colors.orange[800]),
    );

    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: text));
    });

    final Widget imageException = switch (exception) {
      DataClientExceptionState<UserModel>() =>
        const Icon(Icons.devices_outlined, size: 200),
      DataParseExceptionState<UserModel>() =>
        const Icon(Icons.sms_failed_outlined, size: 200),
      DataHttpExceptionState<UserModel>() => Image.network(
          '${links[Random().nextInt(links.length)]}/404.webp',
        ),
      DataNetworkExceptionState<UserModel>() =>
        const Icon(Icons.wifi_off_outlined, size: 200),
      DataCacheExceptionState<UserModel>() =>
        const Icon(Icons.storage_outlined, size: 200),
      DataInvalidInputExceptionState<UserModel>() =>
        const Icon(Icons.textsms_outlined, size: 200),
      DataUnknownExceptionState<UserModel>() =>
        const Icon(Icons.close, size: 200),
    };
    return Column(
      children: [
        SizedBox(
          height: 400,
          child: imageException,
        ),
        const SizedBox(height: 8),
        text,
      ],
    );
  }
}

// UiUserWidget 类定义
class UiUserWidget extends StatelessWidget {
  const UiUserWidget(
    this.user, {
    super.key,
  });

  final UserModel user;

  [@override](/user/override)
  Widget build(BuildContext context) {
    final TextTheme textTheme = Theme.of(context).textTheme;

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Name: ${user.name}',
          style: textTheme.headlineMedium,
        ),
        const SizedBox(height: 8),
        Text(
          'Phone: ${user.phone}',
          style: textTheme.bodyMedium,
        ),
        const SizedBox(height: 8),
        Text(
          'Address: ${user.address?.street} - ${user.address?.city} ${user.address?.zipcode}',
          style: textTheme.bodyMedium,
        ),
        const SizedBox(height: 8),
        Text(
          'Email: ${user.email}',
          style: textTheme.bodyMedium,
        ),
      ],
    );
  }
}

更多关于Flutter异常处理插件exception_handler的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter异常处理插件exception_handler的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用exception_handler插件进行异常处理的示例代码。exception_handler插件可以帮助你捕获未处理的异常并将其发送到指定的处理函数。

首先,你需要在pubspec.yaml文件中添加exception_handler依赖:

dependencies:
  flutter:
    sdk: flutter
  exception_handler: ^x.y.z  # 请使用最新版本号替换x.y.z

然后,运行flutter pub get来获取依赖。

接下来,在你的Flutter应用中配置ExceptionHandler。以下是一个完整的示例,包括如何在应用启动时注册异常处理函数,以及如何故意抛出一个异常来测试处理逻辑。

main.dart

import 'package:flutter/material.dart';
import 'package:exception_handler/exception_handler.dart';

void main() {
  ExceptionHandler.registerGlobalExceptionHandler((error, stackTrace) {
    // 在这里处理未捕获的异常
    print("Caught an exception: $error");
    print("Stack trace: $stackTrace");
    
    // 你可以在这里执行一些其他的逻辑,比如发送错误报告到服务器
  });

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  void _throwError() {
    // 这里故意抛出一个异常来测试异常处理
    throw Exception("This is a test exception");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Exception Handler Demo'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            try {
              _throwError();
            } catch (e, stack) {
              // 这里捕获并处理按钮点击时抛出的异常
              // 但全局异常处理器仍然会捕获未在其他地方捕获的异常
              print("Caught in button: $e");
              print("Stack trace in button: $stack");
              
              // 可以选择重新抛出异常以让全局处理器捕获
              // throw e;
            }
          },
          child: Text('Throw Error'),
        ),
      ),
    );
  }
}

解释

  1. 注册全局异常处理器

    ExceptionHandler.registerGlobalExceptionHandler((error, stackTrace) {
      print("Caught an exception: $error");
      print("Stack trace: $stackTrace");
    });
    

    main函数中,我们使用ExceptionHandler.registerGlobalExceptionHandler注册了一个全局异常处理函数。这个函数会捕获所有未在其他地方捕获的异常。

  2. 故意抛出异常

    void _throwError() {
      throw Exception("This is a test exception");
    }
    

    _MyHomePageState类中,我们定义了一个_throwError方法,该方法会抛出一个异常。

  3. 按钮点击事件

    ElevatedButton(
      onPressed: () {
        try {
          _throwError();
        } catch (e, stack) {
          print("Caught in button: $e");
          print("Stack trace in button: $stack");
          // throw e; // 如果取消注释,这个异常会再次被全局处理器捕获
        }
      },
      child: Text('Throw Error'),
    )
    

    在按钮的点击事件中,我们调用_throwError方法,并在try-catch块中捕获异常。这里的catch块仅用于演示如何在局部捕获异常,但你可以选择重新抛出异常以让全局处理器捕获。

通过上述代码,你可以看到如何在Flutter项目中使用exception_handler插件来捕获和处理未处理的异常。

回到顶部