Flutter便捷类型定义插件convenience_types的使用

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

Flutter便捷类型定义插件 convenience_types 的使用

概述

convenience_types 是由 Capyba 开发的包,旨在提供一些常用的便捷类型,以帮助开发者在 Flutter 项目中更安全、高效地处理异步任务、可选值、表单字段和请求状态等常见场景。

动机

在开发多个项目的过程中,Capyba 发现了一些有助于提高代码安全性、减少错误并提升生产力的类型。为了在不同项目之间共享这些类型,并可能启发其他开发者使用这些类型,他们创建了这个包。

目录

  1. 开始使用
  2. 类型
  3. AppError
  4. 工具

开始使用

要在 Flutter 项目中安装并使用该包,请运行以下命令:

flutter pub add convenience_types

如果你是在 Dart 项目中,请运行:

dart pub add convenience_types

类型

Result

Result 是一个泛型联合类型,用于表示异步任务的两种可能结果:成功(Success)或失败(Failure)。它提供了一种声明式的方式来处理异步任务的结果。

示例:

import 'package:convenience_types/types/result.dart';

Future<Result<String>> asyncTaskReturningStringResult() async {
  // 模拟异步任务
  await Future.delayed(Duration(seconds: 2));
  return Success('Hello, World!');
}

void handleResult() async {
  Result<String> result = await asyncTaskReturningStringResult();

  result.handle(
    onSuccess: (String data) {
      print("Success: $data");
    },
    onFailure: (AppError error) {
      print("Failure: ${error.slug}");
    },
  );
}

Maybe

Maybe 是一个泛型联合类型,用于安全地处理可选值。它可以表示两种状态:有值(Just)或无值(Nothing)。

示例:

import 'package:convenience_types/types/maybe.dart';

void handleMaybeValue() {
  Maybe<String> maybeValue = Just('test');

  final debugValue = maybeValue.map(
    nothing: (_) => "No value",
    just: (data) => data.value,
  );

  print(debugValue); // 输出: test
}

RequestStatus

RequestStatus 是一个泛型联合类型,用于表示请求的不同状态:空闲(Idle)、加载中(Loading)、成功(Succeeded)或失败(Failed)。

示例:

import 'package:convenience_types/types/request_status.dart';

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> {
  RequestStatus<Number> numberRequestStatus = const Idle();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: numberRequestStatus.when(
          idle: () => const Text('Idle'),
          loading: () => const CircularProgressIndicator(),
          succeeded: (data) => Text('Succeeded: ${data.text}'),
          failed: (error) => Text('Failed: ${error.slug}'),
        ),
      ),
    );
  }
}

FormField

FormField 是一个泛型类,用于表示表单中的字段。它结合了字段名称和字段数据(一个 Maybe 类型),并且提供了方便的方法来进行验证和序列化。

示例:

import 'package:convenience_types/types/form_field.dart' as form;
import 'package:convenience_types/util/form_utils.dart';

@freezed
class FormExample with _$FormExample, FormUtils {
  const FormExample._();
  const factory FormExample({
    @Default(form.FormField(name: 'firstFieldJsonName')) form.FormField<String> firstField,
    @Default(form.FormField(name: 'secondFieldJsonName')) form.FormField<String> secondField,
  }) = _FormExample;

  Result<String> get firstFieldValidation => validateField(
        field: firstField.field,
        validators: <String? Function(String)>[
          // 列表验证器
        ],
      );

  Map<String, dynamic> toJson() => fieldsToJson([firstField, secondField]);
}

AppError

AppError 是一个抽象类,用于建模应用程序中的错误。它包含一些预设的具体错误实现,如 HttpErrorCacheError 等。

工具

FormUtils

FormUtils 是一个 Dart Mixin,用于方便地处理表单的验证和序列化。

SeedTestStateMixin

SeedTestStateMixin 是一个 Mixin,用于帮助在测试中播种状态。

示例:

class MyStateNotifier extends StateNotifier<MyState> with SeedTestStateMixin<MyState> {}

// 在测试中使用:
test('Test description', () {
  myStateNotifier.setSeedState(mySeedState);
  // 测试主体
});

完整示例 Demo

以下是一个完整的示例,展示了如何在 Flutter 应用中使用 convenience_types 包。

import 'package:flutter/material.dart';
import 'package:convenience_types/errors/errors.dart';
import 'package:convenience_types/types/form_field.dart' as form;
import 'package:convenience_types/types/maybe.dart';
import 'package:convenience_types/types/request_status.dart';
import 'package:convenience_types/types/result.dart';
import 'package:dio/dio.dart';

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(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Convenience Types Example'),
    );
  }
}

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> {
  form.FormField<String> numberField = const form.FormField(name: 'number');
  RequestStatus<Number> numberRequestStatus = const Idle();
  Dio client = Dio(BaseOptions(baseUrl: 'http://numbersapi.com/'));

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('Enter a number to search for a trivia:'),
              TextField(
                onChanged: _onFieldChanged,
              ),
              const SizedBox(height: 8),
              ElevatedButton(
                onPressed: _onButtonPressed,
                child: const Text('Search'),
              ),
              const SizedBox(height: 8),
              numberRequestStatus.when(
                idle: () => const SizedBox(),
                loading: () => const LinearProgressIndicator(),
                succeeded: (data) => Text('You\'ve just searched ${data.number}, here is a trivia for it: ${data.text}!'),
                failed: (error) => Text('Something didn\'t go well! Sorry! hint: ${error.slug}'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _onFieldChanged(String value) {
    numberField = numberField.copyWith(field: Just(value));
  }

  void _onButtonPressed() async {
    setState(() {
      numberRequestStatus = const Loading();
    });

    Result<Number> numberRes;

    try {
      numberRes = (await client.get('${numberField.field.getOrElse('')}?json')).mapSuccess(_mapRequestToNumber);
    } catch (e) {
      numberRes = Failure(AppUnknownError(slug: e.toString()));
    }

    setState(() {
      numberRequestStatus = RequestStatus.fromResult(numberRes);
    });
  }

  Result<Number> _mapRequestToNumber(data) {
    try {
      return Success(Number.fromJson(data));
    } catch (e) {
      return Failure(HttpUnknownError(slug: e.toString()));
    }
  }
}

class Number {
  final String text;
  final int number;
  final String type;

  Number(this.text, this.number, this.type);

  factory Number.fromJson(Map<String, dynamic> json) {
    try {
      return Number(json['text']!, json['number']!, json['type']!);
    } catch (e) {
      throw Exception(e);
    }
  }
}

更多关于Flutter便捷类型定义插件convenience_types的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter便捷类型定义插件convenience_types的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用convenience_types插件的示例代码。convenience_types是一个方便的类型定义插件,可以简化一些常见的类型定义。

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  convenience_types: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖。

2. 导入插件

在你需要使用convenience_types的Dart文件中导入插件:

import 'package:convenience_types/convenience_types.dart';

3. 使用便捷类型

以下是一些常见的使用示例:

使用NonEmptyString

NonEmptyString是一个非空字符串类型,确保字符串不为空。

void printNonEmptyString(NonEmptyString nonEmptyString) {
  print('The non-empty string is: $nonEmptyString');
}

void main() {
  final NonEmptyString name = NonEmptyString('Alice');
  printNonEmptyString(name);  // 正常输出
  // printNonEmptyString(NonEmptyString('')); // 这将导致编译时错误
}

使用PositiveIntPositiveDouble

PositiveIntPositiveDouble 确保数值为正数。

void printPositiveNumber(PositiveInt positiveInt, PositiveDouble positiveDouble) {
  print('The positive integer is: $positiveInt');
  print('The positive double is: $positiveDouble');
}

void main() {
  final PositiveInt age = PositiveInt(30);
  final PositiveDouble height = PositiveDouble(1.75);
  printPositiveNumber(age, height);  // 正常输出
  // final PositiveInt invalidAge = PositiveInt(-5); // 这将导致编译时错误
  // final PositiveDouble invalidHeight = PositiveDouble(-1.5); // 这将导致编译时错误
}

使用NullableNonNull

NullableNonNull 可以用来明确标识可空和不可空类型。

void printValues(Nullable<String> nullableString, NonNull<int> nonNullInt) {
  print('The nullable string is: $nullableString');
  print('The non-null integer is: $nonNullInt');
}

void main() {
  final Nullable<String> maybeName = Nullable<String>('Bob');
  final NonNull<int> id = NonNull<int>(123);
  printValues(maybeName, id);  // 正常输出
  // printValues(Nullable<String>(null), id); // 允许为null,不会报错
  // final NonNull<int> invalidId = NonNull<int>(null); // 这将导致编译时错误
}

总结

通过使用convenience_types插件,你可以更清晰地定义和确保类型安全,减少运行时的类型错误。以上示例展示了如何使用一些常见的便捷类型定义,但插件可能包含更多类型,具体请参考官方文档以获取完整信息。

回到顶部