Flutter核心功能扩展插件micha_core的使用
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
有一个扩展可以在 Iterable
或 Map
为空时将它们转换为 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
进一步限制需要重试的异常。
默认使用指数退避策略,但可以使用不同的 strategy
。RetryException
可以通过自定义的 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
使字符串不可见。使用 bold
、dim
、italic
、underlined
、overlined
、struckThrough
进行相应的格式化。使用 black
、red
、green
、yellow
、blue
、magenta
、cyan
和 white
应用相应的前景颜色。使用 bgBlack
、bgRed
、bgGreen
、bgYellow
、bgBlue
、bgMagenta
、bgCyan
和 bgWhite
应用相应的背景颜色。使用 inverted
交换前景和背景颜色。
亮色未包含,因为它们不受所有终端支持。
间隙
我们经常需要在小部件之间留出一些空间。Flutter 的小部件目录有限的选择:
- Flutter 的
Spacer
占据所有可用空间,这通常是我们想要的更多。 - Flutter 的
Padding
不幸的是需要包裹小部件,增加了样板代码,而且在列表内部难以阅读。 - Flutter 提供的最佳选择是一个具有指定
width
或height
的SizedBox
。然而,这仍然有一些问题:我们需要根据SizedBox
是放置在Row
还是Column
中来设置width
或height
。我们还需要在整个应用程序中重复相同的常量像素大小。
相反,考虑使用 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
更多关于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调用方式。如果插件提供了不同的命名或结构,请相应调整代码。