岛上码农@公众号同名

V1

2022/06/26阅读:11主题:前端之巅同款

不到40行代码手撸一个BlocProvider

前言

上一篇我们对 BLoC做了整体的介绍,了解了 BLoC 的使用后,心里不禁有点痒痒,写了好几个状态管理插件了,能不能利用 BLoC自己也撸一个 Flutter 状态管理组件,用来构建基于 BLoC 的响应式Flutter页面。说干就干,我们给这个状态管理组件命名为 SimpleBlocProvider

SimpleBlocProvider定义

对于这个简单的状态管理组件 SimpleBlocProvider,因为需要放置到组件树中,因此肯定是一个 Widget,由于内部还需要维护数据,因此我们使用 StatefulWidget。等等!不是不推荐使用 StatefulWidget 吗?需要注意,如果这个组件自己维护自身状态,不影响外部组件那是没问题的。比如说,一个按钮点击后会有点击效果,这个其实也需要使用 StatefulWidget 实现,但是这个行为只会引起自身刷新,那是没问题的。 image.png 其次,我们要把原先 UI 组件的构建放到SimpleBlocProvider中来,那就需要定义一个 构建组件的builder 参数,所有原先 UI组件的构建都由这个 builder 来完成,同时这个 builder 应该携带最新的状态数据,以便更新 UI 组件。而且,状态数据是确定不了类型的,因此这个 builder 应该是返回 Widget 的泛型函数,定义如下。

typedef StateBuilder<T> = Widget Function(T state);

比如显示计数器的 Text,我们可以这么写:

SimpleBlocProvider<int> (
  builder: (count) => Text('$count'),
)

光有 builder 还不够,我们需要 Bloc 逻辑组件,以便从逻辑组件里获取最新的状态数据,因此需要将 Bloc 逻辑组件也作为参数给 SimpleBlocProvider。于是我们就得到了SimpleBlocProvider的基本定义了。

class SimpleBlocProvider<Textends StatefulWidget {
  final StateBuilder<T> builder;
  final BlocBase<T> bloc;
  const SimpleBlocProvider(
      {Key? key, required this.builder, required this.bloc})
      : super(key: key);

  @override
  _SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}

BLoC 刷新

要实现 BLoC 刷新,我们需要监听 BLoC 状态数据的变化,从上一篇我们知道BLoC 是基于 Stream 实现的,对于 Stream,可以使用其 listen 方法来监听 Stream 流数据的变化。listen 方法定义如下:

 StreamSubscription<T> listen(void onData(T event)?,
      {Function? onError, void onDone()?, bool? cancelOnError});

因此,我们可以在 listenonData 中调用 setState 就可以做到刷新界面了。我们组件销毁的时候需要取消监听,因此我们在_SimpleBlocProviderState 中定义一个属性_streamSubscription存储 listen 方法的返回值,并在 dispose 中取消监听。

_streamSubscription = widget.bloc.stream.listen((data) {
  setState(() {
    _state = data;
  });
});

//

@override
void dispose() {
  _streamSubscription.cancel();
  super.dispose();
}

接下来就比较简单了,在_SimpleBlocProviderStatebuild 方法中直接返回 builder 携带状态数据_state构建组件即可。

@override
Widget build(BuildContext context) {
  return widget.builder(_state);
}

这样,只要 BLoC 的状态数据发生了改变,就会通过 listen监听更新SimpleBlocProvider_state,并刷新SimpleBlocProvider组件,从而更新了 builder 构建的组件。完整代码如下:

typedef StateBuilder<T> = Widget Function(T state);

class SimpleBlocProvider<Textends StatefulWidget {
  final StateBuilder<T> builder;
  final BlocBase<T> bloc;
  const SimpleBlocProvider(
      {Key? key, required this.builder, required this.bloc})
      : super(key: key);

  @override
  _SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}

class _SimpleBlocProviderState<Textends State<SimpleBlocProvider<T>> {
  late T _state;
  late StreamSubscription<T> _streamSubscription;

  @override
  void initState() {
    _state = widget.bloc.state;
    super.initState();
    _streamSubscription = widget.bloc.stream.listen((data) {
      setState(() {
        _state = data;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return widget.builder(_state);
  }

  @override
  void dispose() {
    _streamSubscription.cancel();
    super.dispose();
  }
}

总共不到40行代码就搞定了

SimpleBlocProvider 应用

现在来看怎么用,我们先来一个计数器看看。

class CounterCubit extends Cubit<int{
  CounterCubit({initial = 0}) : super(initial);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  void onChange(Change<int> change) {
    super.onChange(change);
  }
}

class SimpleBlocCounterPage extends StatelessWidget {
  final counter = CounterCubit();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc 计数器'),
      ),
      body: Center(
        child: SimpleBlocProvider<int>(
          builder: (count) => Text(
            '$count',
            style: TextStyle(
              fontSize: 32,
              color: Colors.blue,
            ),
          ),
          bloc: counter,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter.increment();
        },
        tooltip: '点击增加',
        child: Icon(Icons.add),
      ),
    );
  }
}
运行结果
运行结果

是不是和我们之前在使用 MobX,GetX 的 GetBuilder 很类似?再来看自定义类,来个简单的 Person 类,然后用 Bloc 的 event 模式试试。

class Person {
  final String name;
  final String gender;

  const Person({required this.name, required this.gender});
}

abstract class PersonEvent {}

class UsingCnNameEvent extends PersonEvent {}

class UsingEnNameEvent extends PersonEvent {}

class PersonBloc extends Bloc<PersonEventPerson{
  PersonBloc(Person person) : super(person) {
    on<UsingCnNameEvent>(
        (event, emit) => emit(Person(name: '岛上码农', gender: '男')));
    on<UsingEnNameEvent>(
        (event, emit) => emit(Person(name: 'island-coder', gender: 'male')));
  }
}

class SimpleBlocCounterPage extends StatelessWidget {
  final personBloc = PersonBloc(Person(name: '岛上码农', gender: '男'));
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bloc 事件'),
      ),
      body: Center(
        child: SimpleBlocProvider<Person>(
          builder: (person) => Text(
            '姓名:${person.name},性别:${person.gender}',
            style: TextStyle(
              fontSize: 22,
              color: Colors.blue,
            ),
          ),
          bloc: personBloc,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          personBloc.add(UsingEnNameEvent());
        },
        tooltip: '点击增加',
        child: Icon(Icons.add),
      ),
    );
  }
}

运行起来也是没问题的。

总结

本篇介绍了使用 BLoC 实现简单状态管理的 SimpleBLocProvider,这个自定义的 BlocProvider不到40行,当然这个代码距离实际使用还有差距,但是对于了解一下这些第三方状态管理插件的实现机制还是有帮助的。接下来我们将使用官方的 flutter_bloc 插件来讲具体的应用实例。 关注岛上码农

分类:

移动端开发

标签:

移动端开发

作者介绍

岛上码农@公众号同名
V1