Flutter自定义业务逻辑插件custom_bloc的使用

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

Flutter自定义业务逻辑插件custom_bloc的使用

简介

custom_bloc 是一个基于 BLOC(Business Logic Component)模式的简单自定义流构建库,它使用底层的 Stream 来处理数据流。该库允许轻松地从网络添加数据,并且可以方便地管理 BLOC 的生命周期。

功能

  • 支持从网络获取数据并将其添加到流中。
  • 提供了便捷的方法来处理加载状态、错误状态和无数据状态。
  • 支持多个流的组合使用。
  • 支持无限滚动加载。

使用方法

1. 初始化项目

首先,确保你已经在 pubspec.yaml 文件中添加了 custom_bloc 依赖:

dependencies:
  custom_bloc: ^latest_version

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

2. 创建 BLOC 类

CounterBloc 是一个简单的 BLOC 类,用于管理计数器的状态。它继承自 BaseBloc<int, String>,其中 int 是数据类型,String 是错误信息类型。

class CounterBloc extends BaseBloc<int, String> {
  int value = 0;

  CounterBloc() {
    fetchCurrent(false);
  }

  // 获取当前值
  fetchCurrent(bool addError) async {
    setAsLoading(); // 设置为加载状态

    if (!addError) {
      await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求延迟
      value++;
      addToModel(value); // 将新值添加到流中
    } else {
      value = 0;
      addToError('Could not fetch data'); // 添加错误信息
    }
  }

  // 重置数据
  resetData() {
    value = 0;
    setAsNoContent(); // 设置为无数据状态
  }

  // 无效化 BLOC
  invalidate() {
    invalidateBaseBloc();
  }

  // 释放资源
  dispose() {
    disposeBaseBloc();
  }
}

3. 创建 UI 页面

接下来,我们创建一个简单的 UI 页面来展示 CounterBloc 的功能。我们将使用 CustomStreamBuilder 来监听 BLOC 的流,并根据不同的状态显示不同的内容。

class Example extends StatefulWidget {
  const Example({Key? key}) : super(key: key);

  [@override](/user/override)
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final counterBloc = CounterBloc();

  [@override](/user/override)
  void dispose() {
    super.dispose();
    counterBloc.dispose(); // 释放 BLOC 资源
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomStreamBuilder(
                stream: counterBloc.behaviorSubject, // 监听 BLOC 的流
                dataBuilder: (context, data) {
                  return ListView.separated(
                    itemCount: 1,
                    scrollDirection: Axis.horizontal,
                    padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 24),
                    itemBuilder: (context, index) {
                      return Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 12),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              'index: $index: data: $data', // 显示数据
                              style: const TextStyle(fontSize: 34),
                            ),
                          ],
                        ),
                      );
                    },
                    separatorBuilder: (context, index) {
                      return const Padding(
                        padding: EdgeInsets.symmetric(vertical: 10.0),
                        child: VerticalDivider(),
                      );
                    },
                  );
                },
                loadingBuilder: (context) => const Center(
                  child: CircularProgressIndicator(), // 加载时显示进度条
                ),
                errorBuilder: (context, error) => Text(
                  error, // 错误时显示错误信息
                  style: const TextStyle(),
                ),
              ),
            ),
            const SizedBox(height: 34),
            Row(
              children: [
                TextButton(
                  onPressed: () {
                    counterBloc.fetchCurrent(false); // 添加正常数据
                  },
                  child: const Text('Add Value'),
                ),
                const SizedBox(width: 34),
                TextButton(
                  onPressed: () {
                    counterBloc.resetData(); // 重置数据
                  },
                  child: const Text('Set to no data'),
                ),
                const SizedBox(width: 34),
                TextButton(
                  onPressed: () {
                    counterBloc.fetchCurrent(true); // 添加错误
                  },
                  child: const Text('Add Error'),
                ),
              ],
            ),
            const SizedBox(height: 34),
          ],
        ),
      ),
    );
  }
}

4. 多个 BLOC 的组合使用

如果你有多个 BLOC 需要同时管理,可以使用 CustomStreamBuilder.twoSubjectCustomStreamBuilder.multiSubject 来组合多个流。

class Example2 extends StatefulWidget {
  const Example2({Key? key}) : super(key: key);

  [@override](/user/override)
  State<Example2> createState() => _Example2State();
}

class _Example2State extends State<Example2> {
  final counterBloc = CounterBloc();
  final newCounterBloc = CounterBloc2();

  [@override](/user/override)
  void dispose() {
    super.dispose();
    counterBloc.dispose();
    newCounterBloc.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomStreamBuilder.twoSubject(
                streams: [
                  counterBloc.behaviorSubject, // 第一个流
                  newCounterBloc.behaviorSubject // 第二个流
                ],
                dataBuilder2: (context, data, secondData) {
                  var counterBlocData = data.model;
                  var counterBlocState = data.itemState;
                  var counterBlocDataError = data.error;

                  var newCounterBlocData = secondData.model;
                  var newCounterBlocState = secondData.itemState;
                  var newCounterBlocError = secondData.error;

                  return Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 12),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        if (counterBlocState == ItemState.loading)
                          const Center(child: CircularProgressIndicator())
                        else if (counterBlocState == ItemState.hasError)
                          Text('counterBlocData error: $counterBlocDataError', style: const TextStyle(fontSize: 34))
                        else if (counterBlocState == ItemState.noContent)
                          const SizedBox()
                        else
                          Text('counterBlocData: $counterBlocData', style: const TextStyle(fontSize: 34)),

                        const SizedBox(height: 34),

                        if (newCounterBlocState == ItemState.loading)
                          const Center(child: CircularProgressIndicator())
                        else if (newCounterBlocState == ItemState.hasError)
                          Text('newCounterBlocError error: $newCounterBlocError', style: const TextStyle(fontSize: 34))
                        else if (newCounterBlocState == ItemState.noContent)
                          const SizedBox()
                        else
                          Text('newCounterBlocData: $newCounterBlocData', style: const TextStyle(fontSize: 34)),
                      ],
                    ),
                  );
                },
                loadingBuilder: (context) => const Center(child: CircularProgressIndicator()),
                errorBuilder: (context, error) => Text(error.toString()),
              ),
            ),
            const SizedBox(height: 34),
            Wrap(
              children: [
                TextButton(onPressed: () => counterBloc.fetchCurrent(false), child: const Text('Add Value to stream 1')),
                const SizedBox(width: 34),
                TextButton(onPressed: () => counterBloc.resetData(), child: const Text('Set to no data stream 1')),
                const SizedBox(width: 34),
                TextButton(onPressed: () => counterBloc.fetchCurrent(true), child: const Text('Add Error to stream 1')),
                const SizedBox(width: 34),
                TextButton(onPressed: () => newCounterBloc.fetchCurrent(false), child: const Text('Add Value to stream 2', style: TextStyle(color: Colors.red))),
                const SizedBox(width: 34),
                TextButton(onPressed: () => newCounterBloc.resetData(), child: const Text('Set to no data stream 2', style: TextStyle(color: Colors.red))),
                const SizedBox(width: 34),
                TextButton(onPressed: () => newCounterBloc.fetchCurrent(true), child: const Text('Add Error to stream 2', style: TextStyle(color: Colors.red))),
              ],
            ),
            const SizedBox(height: 34),
          ],
        ),
      ),
    );
  }
}

5. 无限滚动加载

CounterInfiniteScrollingBloc 是一个支持无限滚动加载的 BLOC 类。它继承自 BaseBlocInfiniteLoadingFactoryBloc<double, String>,并且实现了 initFetchfetchNextPage 方法来处理分页加载。

class CounterInfiniteScrollingBloc extends BaseBlocInfiniteLoadingFactoryBloc<double, String> {
  int get page => pageNumber;
  int get itemsPerPage => perPage;

  double value = 0;

  [@override](/user/override)
  initFetch() async {
    super.initFetch();

    setAsLoading(); // 设置为加载状态

    await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求延迟
    value += 0.5;
    addToModel(value); // 将新值添加到流中

    incrementPageCountAndStartLoaderSubject(); // 增加页码并启动加载器
  }

  [@override](/user/override)
  fetchNextPage({Function()? onSuccessfulFetch, Function()? onFailedFetch}) async {
    bool isSuccessful = await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求延迟

    if (isSuccessful) {
      value += 0.5;
      addToModel(value); // 将新值添加到流中
      incrementPageCountAndStartLoaderSubject(); // 增加页码并启动加载器
    } else {
      stopLoaderSubject(); // 停止加载器
    }
  }

  resetData() {
    value = 0;
    setAsNoContent(); // 设置为无数据状态
  }

  invalidate() {
    invalidateBaseBloc();
  }

  dispose() {
    disposeBaseBloc();
  }
}

6. 无限滚动加载的 UI 实现

在 UI 中,我们可以使用 CustomInfiniteScrollingStreamBuilder 来实现无限滚动加载的功能。

class ExampleInfiniteScrolling extends StatefulWidget {
  const ExampleInfiniteScrolling({Key? key}) : super(key: key);

  [@override](/user/override)
  State<ExampleInfiniteScrolling> createState() => _ExampleInfiniteScrollingState();
}

class _ExampleInfiniteScrollingState extends State<ExampleInfiniteScrolling> {
  final counterBloc = CounterInfiniteScrollingBloc();

  [@override](/user/override)
  void dispose() {
    super.dispose();
    counterBloc.dispose(); // 释放 BLOC 资源
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Expanded(
              child: CustomInfiniteScrollingStreamBuilder(
                loaderStream: counterBloc.loaderSubject, // 监听加载器流
                builder: (context, loadingData) => CustomStreamBuilder(
                  stream: counterBloc.behaviorSubject, // 监听 BLOC 的流
                  dataBuilder: (context, data) {
                    return NotificationListener(
                      onNotification: (notification) {
                        if (notification is ScrollNotification) {
                          counterBloc.sink.add(notification); // 监听滚动事件
                        }
                        return true;
                      },
                      child: ListView.separated(
                        itemCount: 1,
                        scrollDirection: Axis.horizontal,
                        padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 24),
                        itemBuilder: (context, index) {
                          return Padding(
                            padding: const EdgeInsets.symmetric(horizontal: 12),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  'index: $index: data: $data', // 显示数据
                                  style: const TextStyle(fontSize: 34),
                                ),
                              ],
                            ),
                          );
                        },
                        separatorBuilder: (context, index) {
                          return const Padding(
                            padding: EdgeInsets.symmetric(vertical: 10.0),
                            child: VerticalDivider(),
                          );
                        },
                      ),
                    );
                  },
                  loadingBuilder: (context) => const Center(child: CircularProgressIndicator()), // 加载时显示进度条
                  errorBuilder: (context, error) => Text(error), // 错误时显示错误信息
                ),
              ),
            ),
            const SizedBox(height: 34),
            Row(
              children: [
                TextButton(onPressed: () => counterBloc.fetchNextPage(), child: const Text('Add Value')),
                const SizedBox(width: 34),
                TextButton(onPressed: () => counterBloc.resetData(), child: const Text('Set to no data')),
                const SizedBox(width: 34),
                TextButton(onPressed: () => counterBloc.fetchNextPage(), child: const Text('Add Error')),
              ],
            ),
            const SizedBox(height: 34),
          ],
        ),
      ),
    );
  }
}

更多关于Flutter自定义业务逻辑插件custom_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter自定义业务逻辑插件custom_bloc的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中创建和使用自定义业务逻辑插件 custom_bloc 的示例。这个示例将展示如何定义一个简单的BLoC(Business Logic Component),并在Flutter应用中使用它。

1. 创建 custom_bloc 插件

首先,我们需要创建一个Flutter插件项目。假设你已经在你的开发环境中安装并配置好了Flutter SDK。

创建插件项目

flutter create --template=plugin custom_bloc

定义BLoC逻辑

进入插件项目目录,并在 lib 文件夹中创建一个新的 Dart 文件,例如 counter_bloc.dart

// lib/counter_bloc.dart
import 'dart:async';

abstract class CounterEvent {}

class IncrementEvent implements CounterEvent {}
class DecrementEvent implements CounterEvent {}

class CounterState {
  final int count;

  CounterState(this.count);
}

class CounterBloc {
  CounterBloc() : _controller = StreamController<CounterState>.broadcast() {
    _counter = 0;
    _subscription = _eventController.stream.listen(_mapEventToState);
  }

  final _eventController = StreamController<CounterEvent>.broadcast();
  final _controller = StreamController<CounterState>.broadcast();
  int _counter;
  StreamSubscription<CounterEvent> _subscription;

  Stream<CounterState> get state => _controller.stream;

  void dispatch(CounterEvent event) {
    _eventController.add(event);
  }

  void _mapEventToState(CounterEvent event) {
    if (event is IncrementEvent) {
      _counter++;
    } else if (event is DecrementEvent) {
      _counter--;
    }
    _controller.add(CounterState(_counter));
  }

  void dispose() {
    _controller.close();
    _eventController.close();
    _subscription.cancel();
  }
}

更新插件的 pubspec.yaml

确保你的插件 pubspec.yaml 文件包含必要的依赖和元数据。

name: custom_bloc
description: A custom Flutter plugin for handling business logic.
version: 0.0.1
homepage:

environment:
  sdk: ">=2.12.0 <3.0.0"
  flutter: ">=1.20.0"

dependencies:
  flutter:
    sdk: flutter

dev_dependencies:
  flutter_test:
    sdk: flutter

flutter:
  plugin:
    platforms:
      android:
        package: com.example.custom_bloc
        pluginClass: CustomBlocPlugin
      ios:
        pluginClass: CustomBlocPlugin

注意:虽然这个示例没有实际的平台特定代码,但你需要为插件项目提供必要的平台和插件类定义。

2. 在Flutter应用中使用 custom_bloc 插件

添加依赖

在你的Flutter应用项目的 pubspec.yaml 文件中添加对 custom_bloc 插件的依赖。

dependencies:
  flutter:
    sdk: flutter
  custom_bloc:
    path: ../path/to/your/custom_bloc/plugin  # 使用本地路径或发布到pub.dev后使用版本号

使用BLoC

在你的Flutter应用中,你可以这样使用 CounterBloc

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:custom_bloc/counter_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = CounterBloc();

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Custom Bloc Example'),
        ),
        body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            StreamBuilder<CounterState>(
              stream: counterBloc.state,
              initialData: CounterState(0),
              builder: (context, snapshot) {
                return Text(
                  '${snapshot.data.count}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
        floatingActionButton: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          crossAxisAlignment: CrossAxisAlignment.end,
          children: <Widget>[
            FloatingActionButton(
              onPressed: () {
                counterBloc.dispatch(IncrementEvent());
              },
              tooltip: 'Increment',
              child: Icon(Icons.add),
            ),
            SizedBox(height: 10),
            FloatingActionButton(
              onPressed: () {
                counterBloc.dispatch(DecrementEvent());
              },
              tooltip: 'Decrement',
              child: Icon(Icons.remove),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    // 在这里处理BLoC的dispose逻辑,通常在State类中使用
    // counterBloc.dispose();
    super.dispose();
  }
}

注意:在上面的代码中,dispose 方法在 MyApp 类中被注释掉了。在真实的应用中,你应该在适当的 State 类中管理BLoC的生命周期,并在 dispose 方法中调用 counterBloc.dispose()

这个示例展示了如何创建和使用一个简单的自定义BLoC插件。根据你的业务需求,你可以扩展这个BLoC来处理更复杂的业务逻辑。

回到顶部