Flutter核心功能扩展插件micha_core的使用

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

Flutter核心功能扩展插件micha_core的使用

特性

micha_core 包包含了一些扩展和小部件,以帮助避免使用显式样板代码,从而依赖于 Flutter 的声明式 UI 方法。除此之外,这些功能彼此之间大多无关。这只是一个集合,包含了通常需要在许多项目中使用的有用功能,例如访问 BuildContext

使用

分割集合中的项

如果需要在现有列表或可迭代对象中的项目之间添加一些项目,可以使用 collection.separated(separator):

[1, 2, 3].separated(0); // [1, 0, 2, 0, 3]

如果这些分隔符取决于前一个和后一个项,可以使用 collection.separatedBy((before, after) => separator):

[1, 2, 3].separatedBy((before, after) => before + after); // [1, 3, 2, 5, 3]

拆分集合中的项

在 Dart 中,可以通过任何模式来拆分字符串,如下所示:

"abc".split("b"); // ["a", "b"]

这种功能不适用于任意列表和可迭代对象。现在作为扩展添加,并使用如下方式:

[1, 2, 3, 4].split((item) => item == 2); // [[1], [3, 4]]
[1, 2, 3, 4].splitIndexed((index, item) => index == 2); // [[1, 2], [4]]

可选参数包装器

是否需要区分一个可空参数被故意传递为 null 和一个值被简单地省略的情况?例如 copyWith 应该替换那些被传递的参数。但是如何处理可空参数?

// 状态-quo,坏的例子
Foo copyWith({
  double? foo,
}) {
  return Foo(
    // 调用者无法通过 null 替换非空的 foo 值。
    foo: foo ?? this.foo,
  );
}

虽然 Flutter 自身的类没有正确处理这种情况,但我们可以通过使用此包中的 Wrapper 类型来做得更好:

Foo copyWith({
  Wrapper<double?>? foo,
}) {
  return Foo(
    // 应用任何包装的 foo 值(包括 null)。
    // 完全省略 foo 则保留旧值。
    foo: foo == null ? this.foo : foo.value,
  );
}

任何值都可以通过调用 wrapped 获取器进行包装:

fooInstance.copyWith(foo: 1.5.wrapped);

映射或“转换”单个值

有时我们需要使用三元运算符来处理可空类型,这是不必要的命令式,并可能导致不必要的方法调用:

children: [
  foo() == null ? null : Text(foo()),
]

注意 foo 在这个例子中可能会被调用两次。

我们可以使用更函数化的方法:transform 可以调用任何值,并且行为类似于可迭代对象上的 map 函数:

children: [
  foo()?.transform((value) => Text(value)),
]

将空的可迭代对象或映射转换为 null

有一个扩展可以在 IterableMap 为空时将它们转换为 null:

final List<int> itMaybeEmpty = [];
final List<int>? itMaybeNull = itMaybeEmpty.nullWhenEmpty;
assert(itMaybeNull == null);

final Map<String, int> mapMaybeEmpty = {};
final Map<String, int>? mapMaybeNull = mapMaybeEmpty.nullWhenEmpty;
assert(mapMaybeNull == null);

final String strMaybeEmpty = "";
final String? strMaybeNull = strMaybeEmpty.nullWhenEmpty;
assert(strMaybeNull == null);

调用 nullWhenEmpty 相当于以下 transform

final maybeNull = maybeEmpty.transform((it) => it.isEmpty ? null : it);

从 Map 中获取非空值并插入默认值

从 Map 中获取值返回一个可空值:

final fooMap = {
  'foo': 1,
};

final foo = fooMap['foo']; // `foo` 的类型是 `int?` 而不是 `int`
assert(foo == 1);

final fee = fooMap['fee'];
assert(fee == null);

有时你可能希望获取一个默认值,同时立即插入到 Map 中。使用 getOrPut

final fooMap = {
  'foo': 1,
};

final foo = fooMap.getOrPut('foo', () => 0); // `foo` 的类型是 `int`
assert(foo == 1);

final fee = fooMap.getOrPut('fee', () => 0);
assert(fee == 0);

查找枚举值“byNameOrNull”

在 Dart 中,你可以通过名称查找枚举值,如下所示:

enum TestEnum { one, two, three }

TestEnum.values.byName('two');

但如果该值不存在,Dart 会抛出 ArgumentError。使用 byNameOrNull 来接收 null

TestEnum.values.byNameOrNull('four');

等待条件满足

waitFor 重复调用并等待给定的 condition 返回 true

bool condition() {
  // ...
}

void foo() async {
  await waitFor(
    condition,
    timeout: const Duration(seconds: 10),
    interval: const Duration(milliseconds: 100),
  );
}

调用者可以设置一个最大等待时间,超过该时间将抛出 TimeoutException。默认情况下没有超时。

调用者还可以覆盖默认的 interval(200 毫秒),这是检查 condition 之间的空闲持续时间。

重试操作

retried 函数重试给定的 operation 直到成功,并转发返回值。至少尝试一次,最多尝试 maxAttemptCount 次。

operation 在抛出异常类型为 TException 或任何异常时重试(如果没有指定类型)。使用 shouldRetry 进一步限制需要重试的异常。

默认使用指数退避策略,但可以使用不同的 strategyRetryException 可以通过自定义的 delay 来覆盖 strategy

final response = await retried<int, RetryException>(
  () async {
    final response = makeHttpCall();
    if (response.status == 429) {
      throw RetryException(
        'Too Many Requests',
        delay: Duration(seconds: int.parse(response.headers['Retry-After'])),
      );
    }
    return response;
  },
);

速率限制

RateLimiter 类强制执行任意操作之间的最小时间间隔。这在你想限制任务执行以避免过载系统或 API 的场景中很有用。

final rateLimiter = RateLimiter<int>(Duration(seconds: 1)); // 设置操作可以被调用的时间间隔

await rateLimiter.execute(() async => 42);
await rateLimiter.execute(() async => 42); // 等待时间间隔

日志记录

通过调用 initLogging() 快速设置终端的彩色日志打印。这还将使项目的日志级别独立于其依赖项的日志级别。

void main() {
  initLogging({
    // 这些是默认级别
    projectLogLevel: Level.ALL,
    dependenciesLogLevel: Level.WARNING,
  });
  runApp(const App());
}

然后创建并使用特定类型的日志记录器:

class MyClass {
  static final logger = createLogger(MyClass);

  ...

  void logSomething {
    logger.info('something');
  }
}

日志消息将格式化为 INFO 2023-11-26T20:53:56.712849 MyClass: something

ANSI 字符串格式化

你可以通过使用 ANSI 控制序列的获取器扩展方法来打印彩色或其他格式化的字符串到控制台:

logger.info('foo'.red.bold.italic.underline);

使用 resetAll 重置任何先前的样式。使用 hidden 使字符串不可见。使用 bolddimitalicunderlinedoverlinedstruckThrough 进行相应的格式化。使用 blackredgreenyellowbluemagentacyanwhite 应用相应的前景颜色。使用 bgBlackbgRedbgGreenbgYellowbgBluebgMagentabgCyanbgWhite 应用相应的背景颜色。使用 inverted 交换前景和背景颜色。

亮色未包含,因为它们不受所有终端支持。

间隙

我们经常需要在小部件之间留出一些空间。Flutter 的小部件目录有限的选择:

  • Flutter 的 Spacer 占据所有可用空间,这通常是我们想要的更多。
  • Flutter 的 Padding 不幸的是需要包裹小部件,增加了样板代码,而且在列表内部难以阅读。
  • Flutter 提供的最佳选择是一个具有指定 widthheightSizedBox。然而,这仍然有一些问题:我们需要根据 SizedBox 是放置在 Row 还是 Column 中来设置 widthheight。我们还需要在整个应用程序中重复相同的常量像素大小。

相反,考虑使用 Gap

Column(
  children: [
    Text('first'),
    Gap(),
    Text('second'),
  ],
);

Gap 在两个方向上占据固定的空间。默认情况下它占据 16 像素,但可以通过 GapThemeData 主题扩展或构造函数参数进行配置。当你需要相对于配置的主题多一点或少一点空间时,创建一个带有缩放因子的 Gap,如 Gap(scale: 2)Gap(scale: 0.5)。你也可以加、减、乘或除 Gap 以调整其大小,例如 Gap() * 2

还有一个名为 “Gap” 的包,其工作方式类似,但具有更多功能,我个人并不需要。

主题文本

避免手动访问 ThemeData 来设置主题化的 textStyle。使用 ThemedText 小部件:

ThemedText.headlineMedium('Some headline');

异步构建器

使用 Flutter 的 FutureBuilder 需要大量的显式样板代码。你需要显式地捕获加载、错误和无数据状态,同时还要保持 Future 在状态中。

如果你这样做:FutureBuilder(future: load(), builder: ...);,那么 FutureBuilder 会在你的小部件重建时调用 load()

作为替代方案,AsyncBuilder 提供了声明式的 API:

AsyncBuilder(
  createFuture: (context) => Future.delayed(
    const Duration(seconds: 1),
    () => 'some data',
  ),
  builder: (context, data) => Text(data),
);

AsyncBuilder 只在 key 更改时重新加载。你还可以自定义 initialData 并更改加载、无数据和错误状态的外观,这些状态各自有合理的默认值。

使用 AsyncBuilder.asset 以避免显式获取 BuildContext 中的 <a href="https://api.flutter.dev/flutter/widgets/DefaultAssetBundle/of.html"><code>DefaultAssetBundle</code>

AsyncBuilder.asset(
  (assetBundle) => assetBundle.loadString('assets/file.txt'),
  builder: (context, data) => Text(data),
);

加载指示器

Spinner 实际上是一个稍微改进的 CircularProgressIndicator。它总是居中显示,可以给定固定的 size,它的 strokeWidth 更窄一些,并且可以使用 SpinnerThemeData 进行主题化。它也是 AsyncBuilder 默认使用的加载指示器。

链接

Flutter 附带了一些可点击和可触摸的小部件,但没有看起来像常规 HTML <a> 标签的那种。Link 小部件就是这样的。它在被点击时执行操作,并使其 child: Text 看起来像一个带有下划线和经典蓝色颜色的 HTML 链接。可以通过构造函数参数或使用 LinkThemeData 主题扩展来自定义此样式。

Link(
  onTap: () {
    // 执行某些操作
  },
  child: const Text('点击我'),
),

分页

如果需要在屏幕上同时显示大量元素时提高性能,请尝试 Pagination

Pagination(
  maxPageSize: 20,
  // getPage 返回一个带有 `totalItemCount` 和泛型 `items` 的 `Paginated` 实例
  getPage: (int pageIndex) => ...,
  builder: (context, items) => ListView(
    children: [
      for (final item in items)
        ListTile(
          title: Text(item),
        ),
    ],
  ),
),

更多关于Flutter核心功能扩展插件micha_core的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter核心功能扩展插件micha_core的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于Flutter核心功能扩展插件micha_core的使用,下面是一个简要的代码示例,假设该插件提供了几个核心功能,比如网络请求、本地存储和UI工具。请注意,实际插件的具体API可能会有所不同,以下代码仅为示例。

首先,确保在pubspec.yaml文件中添加对micha_core插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  micha_core: ^latest_version  # 请替换为实际的最新版本号

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

示例代码

1. 网络请求

假设micha_core提供了一个简单的HTTP客户端:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String responseData = '';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Network Request Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () async {
                  var response = await MichaCore.httpClient.get('https://jsonplaceholder.typicode.com/posts/1');
                  if (response.statusCode == 200) {
                    setState(() {
                      responseData = response.body;
                    });
                  } else {
                    setState(() {
                      responseData = 'Failed to fetch data';
                    });
                  }
                },
                child: Text('Fetch Data'),
              ),
              Text(responseData),
            ],
          ),
        ),
      ),
    );
  }
}

2. 本地存储

假设micha_core提供了一个简单的键值存储功能:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String storedValue = '';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Local Storage Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () async {
                  await MichaCore.storage.save('myKey', 'Hello, Flutter!');
                },
                child: Text('Save Data'),
              ),
              ElevatedButton(
                onPressed: () async {
                  var value = await MichaCore.storage.load('myKey');
                  setState(() {
                    storedValue = value ?? 'No data found';
                  });
                },
                child: Text('Load Data'),
              ),
              Text(storedValue),
            ],
          ),
        ),
      ),
    );
  }
}

3. UI工具

假设micha_core提供了一些UI工具,比如一个简单的圆形进度指示器:

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

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool isLoading = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('UI Tool Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () {
                  setState(() {
                    isLoading = true;
                    // Simulate a loading process
                    Future.delayed(Duration(seconds: 2), () {
                      setState(() {
                        isLoading = false;
                      });
                    });
                  });
                },
                child: Text('Show Loader'),
              ),
              if (isLoading) MichaCore.circularProgressIndicator,
            ],
          ),
        ),
      ),
    );
  }
}

注意:上述代码示例中的MichaCore.httpClient, MichaCore.storage, 和 MichaCore.circularProgressIndicator 是假设的API,实际使用时需要参考micha_core插件的官方文档来确定正确的API调用方式。如果插件提供了不同的命名或结构,请相应调整代码。

回到顶部