Flutter异步操作插件flutter_async的使用

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

Flutter 异步操作插件 flutter_async 的使用

Flutter Async 将传统的 Flutter 组件转换为异步版本,从而实现无缝处理异步操作。通过扩展熟悉的组件以支持异步功能,该包允许轻松执行后台任务、数据获取等操作,同时通过加载指示器提供反馈,并处理错误,而不会影响应用界面的响应性。

开始使用

可以选配地在应用的根部添加一个 Async 组件。这允许你配置或覆盖 flutter_async 的默认行为。你可以根据需求设置任意数量的 Async 组件。

return Async(
  config: AsyncConfig(
    loadingBuilder: (_) => CircularProgressIndicator(),
    textButtonConfig: AsyncButtonConfig(
      loadingBuilder: (_) => const Text('loading'),
    ),
  ),
  child: // 你的应用主体
);

AsyncIndicator

AsyncIndicator 是一个智能的 CircularProgressIndicator,它可以根据下方的颜色自动选择主色、次色或备用色。此外,它永远不会失真,可以叠加在其他组件上,并且当缩小时会线性插值笔画宽度。

AsyncIndicator()
// 或者通过扩展方法调用:
CircularProgressIndicator().asAsync()

这是 flutter_async 默认的 Async.loadingBuilder。你可以像使用任何进度指示器一样使用它:

Builder(
  builder: (context) {
    if (isLoading) return AsyncIndicator();
    return ... // 其他内容
  }
)

AsyncBuilder

AsyncBuilder 是一个强大的组件,简化了在 Flutter 中处理 FutureStream 对象。你无需定义任何构建器,flutter_async 会默认它们为 AsyncConfig

以下是 AsyncBuilder 的属性:

AsyncBuilder(
  future: myFuture, // 或者 stream
  noneBuilder: (context) {
    // 当操作尚未开始时显示(例如,future 和 stream 为空)
    return Text('none');
  },
  loadingBuilder: (context) {
    return const CircularProgressIndicator(); // 默认为 AsyncIndicator()
  },
  reloadingBuilder: (context) {
    // 在 `isLoading` 并且 `hasData` 或 `hasError` 时叠加显示
    // 可以通过设置 `AsyncBuilder.skipReloading` 为 true 来跳过此加载器。
    return const Align(alignment: Alignment.topCenter, child: LinearProgressIndicator());
  },
  errorBuilder: (context, error, stackTrace) {
    return Text('$error');
  },
  builder: (context, data) {
    return Text('$data');
  },
)

对于简单的状态管理场景,可以使用 function 构造函数来处理异步函数:

AsyncBuilder.function(
  future: () => myFutureFunction(), // 或者 stream
  interval: Duration(seconds: 5), // 自动重载间隔
  builder: (context, data) {
    return TextButton(
      child: Text('$data'),
      onPressed: () => AsyncBuilder.of(context).reload(), // 手动重载
    );
  },
)

对于分页处理,可以使用 paged 构造函数:

AsyncBuilder.paged(
  future: (page) async {
    await Future.delayed(duration);
    return List.generate(10, (i) => 'Item ${page * 10 + i}');
  },
  builder: (context, controller, list) {
    return ListView.builder(
      controller: controller,
      itemCount: list.length,
      itemBuilder: (context, index) {
        return ListTile(title: Text(list[index]));
      },
    );
  },
)

AsyncButton

只需在按钮前加上 Async 即可:

AsyncElevatedButton(
  onPressed: onHello,
  child: const Text('AsyncElevatedButton'),
),

或者使用 AsyncButtonExtension,适用于任何 ButtonStyleButton

ElevatedButton(
  onPressed: onHello,
  child: const Text('ElevatedButton'),
).asAsync(),

可以通过以下方式程序化控制按钮:

AsyncButton.at(context).press();
AsyncButton.at(context).longPress();
AsyncButtonConfig 属性
/// 是否在状态变化时保持按钮高度。默认为 `true`。
final bool? keepHeight;

/// 是否在状态变化时保持按钮宽度。默认为 `false`。
final bool? keepWidth;

/// 此按钮是否应对其大小进行动画处理。
final bool? animateSize;

/// [AnimatedSize] 的配置。
final AnimatedSizeConfig? animatedSizeConfig;

/// 显示错误组件的时间。
final Duration? errorDuration;

/// 样式动画之间的持续时间。
final Duration? styleDuration;

/// 样式动画使用的曲线。
final Curve? styleCurve;

/// 加载时显示的组件。
final WidgetBuilder? loadingBuilder;

/// 错误时显示的组件。
final ErrorBuilder? errorBuilder;

未来计划和发展

该插件目前仍在开发中,我们计划在未来推出许多令人兴奋的新功能。我们正在不断努力改进和扩展我们的异步组件的功能。作为路线图的一部分,我们希望引入各种新的组件,提供更多灵活性和功能。

您的反馈对我们非常重要,我们鼓励您通过提出新功能、改进建议和报告错误来参与贡献。我们也欢迎开源社区的贡献。

敬请期待未来的更新,祝您编码愉快!


示例代码

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

void main() => runApp(
      MaterialApp(
        theme: ThemeData(
          useMaterial3: true,
          colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.blue,
          ),
        ),
        home: const Scaffold(
          body: _MyWidget(),
        ),
      ),
    );

class _MyWidget extends StatelessWidget {
  const _MyWidget();

  static const duration = Duration(seconds: 1);
  static const fabPadding = EdgeInsets.all(8);

  Future<void> onError() async {
    await Future<void>.delayed(duration);
    // ignore: strict_raw_type
    throw ParallelWaitError<List, List>([], [
      'Invalid user',
      'The email is already in use',
      'The password is too weak.',
    ]);
  }

  Future<void> onSuccess() async {
    await Future<void>.delayed(duration);
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    const snapshot = AsyncSnapshot.withData(ConnectionState.done, 42);
    if (snapshot.isLoading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (snapshot.hasError) {
      return Center(child: Text(snapshot.error.toString()));
    }

    return Scaffold(
      body: Row(
        children: [
          switch (const AsyncSnapshot<int>.nothing()) {
            AsyncSnapshot(:final data?) => Center(child: Text(data.toString())),
            AsyncSnapshot(:final error?) => Center(child: Text(error.toString())),
            _ => const CircularProgressIndicator(),
          },
          Column(
            children: [
              AsyncButtonBuilder(
                onPressed: onError,
                child: const FlutterLogo(size: 120),
                builder: (context, state, child) {
                  return InkWell(
                    onTap: state.press,
                    child: child,
                  );
                },
              ),
              ElevatedButton(
                onPressed: onError,
                child: const Text('ElevatedButton'),
              ).asAsync(),
              ElevatedButton.icon(
                onPressed: onError,
                label: const Text('ElevatedButton.icon'),
                icon: const Icon(Icons.add),
              ).asAsync(),
              FilledButton(
                onPressed: onError,
                child: const Text('FilledButton'),
              ).asAsync(),
              FilledButton.icon(
                onPressed: onError,
                label: const Text('FilledButton.icon'),
                icon: const Icon(Icons.add),
              ).asAsync(),
              FilledButton.tonal(
                onPressed: onError,
                child: const Text('FilledButton.tonal'),
              ).asAsync(),
              FilledButton.tonalIcon(
                onPressed: onError,
                label: const Text('FilledButton.tonalIcon'),
                icon: const Icon(Icons.add),
              ).asAsync(),
              OutlinedButton(
                onPressed: onError,
                child: const Text('OutlinedButton'),
              ).asAsync(),
              OutlinedButton.icon(
                onPressed: onError,
                label: const Text('OutlinedButton.icon'),
                icon: const Icon(Icons.add),
              ).asAsync(),
              TextButton(
                onPressed: onError,
                child: const Text('TextButton'),
              ).asAsync(),
              TextButton.icon(
                onPressed: onError,
                label: const Text('TextButton.icon'),
                icon: const Icon(Icons.add),
              ).asAsync(),
            ].map((e) => Expanded(child: Center(child: e))).toList(),
          ),
          Column(
            children: [
              IconButton(
                onPressed: onError,
                icon: const Icon(Icons.add),
              ).asAsync(),
              IconButton.filled(
                onPressed: onSuccess,
                icon: const Icon(Icons.add),
              ).asAsync(successIcon: const Icon(Icons.check)),
              IconButton.filledTonal(
                onPressed: onError,
                icon: const Icon(Icons.add),
              ).asAsync(),
              IconButton.outlined(
                onPressed: onSuccess,
                icon: const Icon(Icons.add),
              ).asAsync(),
            ],
          ),
          SizedBox(
            height: 400,
            width: 400,
            child: AsyncBuilder.paged(
              future: (page) async {
                await Future<void>.delayed(duration);
                return List.generate(10, (i) => 'Patient ${page * 10 + i}');
              },
              builder: (context, controller, list) {
                return ListView.builder(
                  controller: controller,
                  itemCount: list.length,
                  itemBuilder: (context, index) {
                    return ListTile(title: Text(list[index]));
                  },
                );
              },
            ),
          ),
        ],
      ),
      // 使用 Async 作用域来配置其后代的 AsyncConfig。
      floatingActionButton: Async(
        config: AsyncConfig(
          buttonConfig: AsyncButtonConfig.icon(
            successIcon: const Icon(Icons.check),
            successColor: Colors.green,
          ),
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            FloatingActionButton.small(
              onPressed: onSuccess,
              child: const Icon(Icons.add),
            ).asAsync(),
            FloatingActionButton(
              onPressed: onSuccess,
              isExtended: true,
              child: const Icon(Icons.add),
            ).asAsync(),
            FloatingActionButton.extended(
              onPressed: onSuccess,
              icon: const Icon(Icons.add),
              label: const Text('extended'),
            ).asAsync(),
            FloatingActionButton.large(
              onPressed: onSuccess,
              child: const Icon(Icons.add),
            ).asAsync(),
          ].map((e) => Padding(padding: fabPadding, child: e)).toList(),
        ),
      ),
    );
  }
}

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

1 回复

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


在Flutter中,处理异步操作通常涉及到与设备硬件、网络请求或其他需要耗时任务进行交互。flutter_async 并不是一个官方或广泛认知的插件,用于处理Flutter中的异步操作。Flutter社区中更常见的是使用 Dart 的原生异步功能(如 async/await 关键字)和 Flutter 的其他官方插件来处理这些任务。

不过,为了展示如何在Flutter中处理异步操作,我将给出一个使用 Dart 的 async/await 以及 http 插件(一个用于网络请求的官方插件)的示例。这将模拟一个异步的网络请求。

首先,确保在 pubspec.yaml 文件中添加了 http 依赖:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3  # 确保使用最新版本

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

接下来,编写一个 Flutter 应用,该应用将在按钮点击时发起一个网络请求,并在接收到响应后更新UI。

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

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

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

class _MyHomePageState extends State<MyHomePage> {
  String responseData = '';

  // 模拟一个异步的网络请求
  Future<void> fetchData() async {
    try {
      final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'));

      // 检查响应状态码
      if (response.statusCode == 200) {
        // 解析JSON数据
        final jsonData = jsonDecode(response.body);
        setState(() {
          responseData = jsonData['title'].toString();
        });
      } else {
        throw Exception('Failed to load data');
      }
    } catch (e) {
      // 处理错误
      setState(() {
        responseData = 'Error: ${e.message}';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Async Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Response Data:',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 10),
            Text(
              responseData,
              style: TextStyle(fontSize: 24),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: fetchData,
              child: Text('Fetch Data'),
            ),
          ],
        ),
      ),
    );
  }
}

在这个示例中,我们创建了一个简单的 Flutter 应用,该应用包含一个按钮和一个用于显示响应数据的文本区域。当用户点击按钮时,fetchData 函数会被调用,该函数使用 http.get 方法发起一个网络请求,并在请求成功后更新UI。

注意:

  • 我们使用了 async/await 来处理异步操作,这使得代码看起来更像是同步的,但实际上是异步执行的。
  • 我们使用了 setState 方法来更新UI,这是Flutter中更新状态的标准方式。
  • 错误处理是通过 try-catch 块实现的,这确保了即使在请求失败时,应用也不会崩溃,而是显示一个错误消息。

这个示例展示了如何在Flutter中处理异步操作,尽管没有直接使用名为 flutter_async 的插件,但原理是相通的。

回到顶部