Flutterflutterflutter

flutter_bloc使用解析---骚年,你还在手搭bloc吗

2020-08-05  本文已影响0人  小呆呆666

前言

表情1

项目效果(建议PC浏览器打开)

下面是Flutter_Bloc历程的一系列链接

问题

初次使用flutter_bloc框架,可能会有几个疑问

准备工作

引用

flutter_bloc: ^6.1.1 #状态管理框架
equatable: ^1.2.3 #增强组件相等性判断

插件

在Android Studio设置的Plugins里,搜索:Bloc

插件搜索

安装重启下,就OK了

新建bloc文件 目录结构新建bloc文件

Bloc范例

效果

bloc演示

初始化代码

来看下这三个生成的bloc文件:main_bloc,main_event,main_state

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainInitial());

  @override
  Stream<MainState> mapEventToState(
    MainEvent event,
  ) async* {
    // TODO: implement mapEventToState
  }
}
@immutable
abstract class MainEvent {}
@immutable
abstract class MainState {}

class MainInitial extends MainState {}

实现

class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState(selectedIndex: 0, isExtended: false));

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,会在此处回调,此处处理完数据,将数据yield,BlocBuilder就会刷新组件
    if (event is SwitchTabEvent) {
      ///获取到event事件传递过来的值,咱们拿到这值塞进MainState中
      ///直接在state上改变内部的值,然后yield,只能触发一次BlocBuilder,它内部会比较上次MainState对象,如果相同,就不build
      yield MainState()
        ..selectedIndex = event.selectedIndex
        ..isExtended = state.isExtended;
    } else if (event is IsExtendEvent) {
      yield MainState()
        ..selectedIndex = state.selectedIndex
        ..isExtended = !state.isExtended;
    }
  }
}
@immutable
abstract class MainEvent extends Equatable{
  const MainEvent();
}
///切换NavigationRail的tab
class SwitchTabEvent extends MainEvent{
  final int selectedIndex;

  const SwitchTabEvent({@required this.selectedIndex});

  @override
  List<Object> get props => [selectedIndex];
}
///展开NavigationRail,这个逻辑比较简单,就不用传参数了
class IsExtendEvent extends MainEvent{
  const IsExtendEvent();

  @override
  List<Object> get props => [];
}
class MainState{
   int selectedIndex;
   bool isExtended;
  
   MainState({this.selectedIndex, this.isExtended});
}
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
    );
  }
}

class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ///创建BlocProvider的,表明该Page,我们是用MainBloc,MainBloc是属于该页面的Bloc了
    return BlocProvider(
      create: (BuildContext context) => MainBloc(),
      child: BodyPage(),
    );
  }
}

class BodyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Bloc')),
      body: totalPage(),
    );
  }
}

Widget totalPage() {
  return Row(
    children: [
      navigationRailSide(),
      Expanded(child: Center(
        child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
          ///看这看这:刷新组件!
          return Text("selectedIndex:" + state.selectedIndex.toString());
        }),
      ))
    ],
  );
}

//增加NavigationRail组件为侧边栏
Widget navigationRailSide() {
  //顶部widget
  Widget topWidget = Center(
    child: Padding(
      padding: const EdgeInsets.all(8.0),
      child: Container(
          width: 80,
          height: 80,
          decoration: BoxDecoration(
            shape: BoxShape.circle,
            image: DecorationImage(
                image: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg"),
                fit: BoxFit.fill),
          )),
    ),
  );

  //底部widget
  Widget bottomWidget = Container(
    child: BlocBuilder<MainBloc, MainState>(
      builder: (context, state) {
        return FloatingActionButton(
          onPressed: () {
            ///添加NavigationRail展开,收缩事件
            BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
          },
          ///看这看这:刷新组件!
          child: Icon(state.isExtended ? Icons.send : Icons.navigation),
        );
      },
    ),
  );

  return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
    return NavigationRail(
      backgroundColor: Colors.white12,
      elevation: 3,
      ///看这看这:刷新组件!
      extended: state.isExtended,
      labelType: state.isExtended ? NavigationRailLabelType.none : NavigationRailLabelType.selected,
      //侧边栏中的item
      destinations: [
        NavigationRailDestination(
            icon: Icon(Icons.add_to_queue),
            selectedIcon: Icon(Icons.add_to_photos),
            label: Text("测试一")),
        NavigationRailDestination(
            icon: Icon(Icons.add_circle_outline),
            selectedIcon: Icon(Icons.add_circle),
            label: Text("测试二")),
        NavigationRailDestination(
            icon: Icon(Icons.bubble_chart),
            selectedIcon: Icon(Icons.broken_image),
            label: Text("测试三")),
      ],
      //顶部widget
      leading: topWidget,
      //底部widget
      trailing: bottomWidget,
      selectedIndex: state.selectedIndex,
      onDestinationSelected: (int index) {
        ///添加切换tab事件
        BlocProvider.of<MainBloc>(context).add(SwitchTabEvent(selectedIndex: index));
      },
    );
  });
}

Bloc范例优化

反思

从上面的代码来看,实际存在几个隐式问题,这些问题,刚开始使用时候,没异常的感觉,但是使用bloc久了后,感觉肯定越来越强烈

优化实现

这边完整走一下流程,让大家能有个完整的思路

class MainState {
  int selectedIndex;
  bool isExtended;

  ///初始化方法,基础变量也需要赋初值,不然会报空异常
  MainState init() {
    return MainState()
      ..selectedIndex = 0
      ..isExtended = false;
  }

  ///clone方法,此方法实现参考fish_redux的clone方法
  ///也是对官方Flutter Login Tutorial这个demo中copyWith方法的一个优化
  ///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial)
  MainState clone() {
    return MainState()
      ..selectedIndex = selectedIndex
      ..isExtended = isExtended;
  }
}
@immutable
abstract class MainEvent {}

///初始化事件,这边目前不需要传什么值
class MainInitEvent extends MainEvent {}

///切换NavigationRail的tab
class SwitchTabEvent extends MainEvent {
  final int selectedIndex;

  SwitchTabEvent({@required this.selectedIndex});
}

///展开NavigationRail,这个逻辑比较简单,就不用传参数了
class IsExtendEvent extends MainEvent {}
class MainBloc extends Bloc<MainEvent, MainState> {
  MainBloc() : super(MainState().init());

  @override
  Stream<MainState> mapEventToState(MainEvent event) async* {
    ///main_view中添加的事件,会在此处回调,此处处理完数据,将数据yield,BlocBuilder就会刷新组件
    if (event is MainInitEvent) {
      yield await init();
    } else if (event is SwitchTabEvent) {
      ///获取到event事件传递过来的值,咱们拿到这值塞进MainState中
      ///直接在state上改变内部的值,然后yield,只能触发一次BlocBuilder,它内部会比较上次MainState对象,如果相同,就不build
      yield switchTap(event);
    } else if (event is IsExtendEvent) {
      yield isExtend();
    }
  }

  ///初始化操作,在网络请求的情况下,需要使用如此方法同步数据
  Future<MainState> init() async {
    return state.clone();
  }

  ///切换tab
  MainState switchTap(SwitchTabEvent event) {
    return state.clone()..selectedIndex = event.selectedIndex;
  }

  ///是否展开
  MainState isExtend() {
    return state.clone()..isExtended = !state.isExtended;
  }
}
class MainPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      ///在MainBloc上使用add方法,添加初始化事件
      create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
      child: BodyPage(),
    );
  }
}
///下方其余代码省略...........

搞定

Cubit范例

创建

image-20201010155420462

新建好后,他会生成俩个文件:counter_cubit,counter_state,来看下生成的代码

原始模板代码

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterInitial());
}
@immutable
abstract class CounterState {}

class CounterInitial extends CounterState {}

按照生成的这种state方式去写,比较麻烦,这边调整下

模板代码优化

class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState().init());
}
class CounterState {
  ///初始化方法
  CounterState init() {
    return CounterState();
  }

  ///克隆方法,针对于刷新界面数据
  CounterState clone() {
    return CounterState();
  }
}

OK,这样调整了下,下面写起来就会舒服很多,也会很省事

实现计时器

效果

实现

实现很简单,三个文件就搞定,看下流程:state -> cubit -> view

class CounterState {
  int count;

  CounterState init() {
    return CounterState()..count = 0;
  }

  CounterState clone() {
    return CounterState()..count = count;
  }
}
class CounterCubit extends Cubit<CounterState> {
  CounterCubit() : super(CounterState().init());

  ///自增
  void increase() => emit(state.clone()..count = ++state.count);
}
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (BuildContext context) => CounterCubit(),
      child: BlocBuilder<CounterCubit, CounterState>(builder: _counter),
    );
  }

  Widget _counter(BuildContext context, CounterState state) {
    return Scaffold(
      appBar: AppBar(title: const Text('Cubit范例')),
      body: Center(
        child: Text('点击了 ${state.count} 次', style: TextStyle(fontSize: 30.0)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => BlocProvider.of<CounterCubit>(context).increase(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

总结

在Bloc模式里面,如果页面不是过于复杂,使用Cubit去写,基本完全够用了;但是如果业务过于复杂,还是需要用Bloc去写,需要将所有的事件行为管理起来,便于后期维护

OK,Bloc的简化模块,Cubit模式就这样讲完了,对于自己业务写的小项目,我就经常用这个Cubit去写

全局Bloc

说明

什么是全局Bloc?

使用场景

效果图

globalBloc

使用

来看下怎么创建和使用全局Bloc吧!

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
      builder: (BuildContext context, Widget child) {
        return MultiBlocProvider(
          providers: [
            ///此处通过BlocProvider创建的Bloc或者Cubit是全局的
            BlocProvider<SpanOneCubit>(
              create: (BuildContext context) => SpanOneCubit(),
            ),
          ],
          child: child,
        );
      },
    );
  }
}

需要用俩个Bloc模块来演示,这里分别用SpanOneCubitSpanTwoCubit来演示,其中SpanOneCubit是全局的

SpanOneCubit

class SpanOneState {
  int count;

  ///初始化方法
  SpanOneState init() {
    return SpanOneState()..count = 0;
  }

  ///克隆方法,针对于刷新界面数据
  SpanOneState clone() {
    return SpanOneState()..count = count;
  }
}
class SpanOnePage extends StatefulWidget {
  @override
  _SpanOnePageState createState() => _SpanOnePageState();
}

class _SpanOnePageState extends State<SpanOnePage> {

  @override
  void initState() {
    BlocProvider.of<SpanOneCubit>(context).init();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<SpanOneCubit, SpanOneState>(builder: _body);
  }

  Widget _body(BuildContext context, SpanOneState state) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: Text('跨页面-One')),
      floatingActionButton: FloatingActionButton(
        onPressed: () =>
            BlocProvider.of<SpanOneCubit>(context).toSpanTwo(context),
        child: const Icon(Icons.arrow_forward_outlined),
      ),
      body: Center(
        child: Text(
          'SpanTwoPage点击了 ${state.count} 次',
          style: TextStyle(fontSize: 30.0),
        ),
      ),
    );
  }
}
class SpanOneCubit extends Cubit<SpanOneState> {
  SpanOneCubit() : super(SpanOneState().init());

  void init() {
    emit(state.init());
  }

  ///跳转到跨页面
  void toSpanTwo(BuildContext context) {
    Navigator.push(context, MaterialPageRoute(builder: (context) => SpanTwoPage()));
  }

  ///自增
  void increase() {
    state..count = ++state.count;
    emit(state.clone());
  }
}

SpanTwoCubit

class SpanTwoState {
  int count;

  ///初始化方法
  SpanTwoState init() {
    return SpanTwoState()..count = 0;
  }

  ///克隆方法,针对于刷新界面数据
  SpanTwoState clone() {
    return SpanTwoState()..count = count;
  }
}
class SpanTwoPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => SpanTwoCubit()..init(context),
      child: BlocBuilder<SpanTwoCubit, SpanTwoState>(builder: _body),
    );
  }

  Widget _body(BuildContext context, SpanTwoState state) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(title: Text('跨页面-Two')),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          //改变SpanOneCubit模块数据
          BlocProvider.of<SpanOneCubit>(context).increase();

          //改变当前页面数据
          BlocProvider.of<SpanTwoCubit>(context).increase();
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Text(
          '当前点击了 ${state.count} 次',
          style: TextStyle(fontSize: 30.0),
        ),
      ),
    );
  }
}
class SpanTwoCubit extends Cubit<SpanTwoState> {
  SpanTwoCubit() : super(SpanTwoState().init());

  void init(BuildContext context){
    emit(state.init());
  }

  ///自增
  void increase() => emit(state.clone()..count = ++state.count);
}

总结

OK,这样便用全局Bloc实现了类似广播的一种效果

Bloc API说明

BlocBuilder

BlocBuilder是Flutter窗口小部件,需要Blocbuilder函数。BlocBuilder处理构建小部件以响应新状态。BlocBuilder与非常相似,StreamBuilder但具有更简单的API,可以减少所需的样板代码量。该builder函数可能会被多次调用,并且应该是一个纯函数,它会根据状态返回小部件。

看看BlocListener是否要响应状态更改“执行”任何操作,例如导航,显示对话框等。

如果省略cubit参数,BlocBuilder将使用BlocProvider和当前函数自动执行查找BuildContext

BlocBuilder<BlocA, BlocAState>(
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

仅当您希望提供一个范围仅限于单个窗口小部件且无法通过父级BlocProvider和当前类访问的bloc时,才指定该bloc BuildContext

BlocBuilder<BlocA, BlocAState>(
  cubit: blocA, // provide the local cubit instance
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

为了对何时builder调用该函数进行细粒度的控制,buildWhen可以提供一个可选的选项。buildWhen获取先前的块状态和当前的块状态并返回一个布尔值。如果buildWhen返回true,builder将使用进行调用,state并且小部件将重新生成。如果buildWhen返回false,builder则不会调用state且不会进行重建。

BlocBuilder<BlocA, BlocAState>(
  buildWhen: (previousState, state) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

BlocProvider

BlocProvider是Flutter小部件,可通过为其子元素提供块BlocProvider.of<T>(context)。它用作依赖项注入(DI)小部件,以便可以将一个块的单个实例提供给子树中的多个小部件。

在大多数情况下,BlocProvider应使用它来创建新的bloc,这些bloc将可用于其余子树。在这种情况下,由于BlocProvider负责创建块,它将自动处理关闭bloc。

BlocProvider(
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

默认情况下,BlocProvider将懒惰地创建bloc,这意味着create当通过查找块时将执行该bloc BlocProvider.of<BlocA>(context)

要覆盖此行为并强制create立即运行,lazy可以将其设置为false

BlocProvider(
  lazy: false,
  create: (BuildContext context) => BlocA(),
  child: ChildA(),
);

在某些情况下,BlocProvider可用于向小部件树的新部分提供现有的bloc。当需要将现有bloc用于新路线时,这将是最常用的。在这种情况下,BlocProvider由于不会创建bloc,因此不会自动关闭该bloc。

BlocProvider.value(
  value: BlocProvider.of<BlocA>(context),
  child: ScreenA(),
);

然后从ChildAScreenA中检索BlocA

// with extensions
context.read<BlocA>();

// without extensions
BlocProvider.of<BlocA>(context)复制到剪贴板错误复制的

MultiBlocProvider

MultiBlocProvider是Flutter小部件,可将多个BlocProvider小部件合并为一个。 MultiBlocProvider提高了可读性,消除了嵌套多个元素的需求BlocProviders。通过使用,MultiBlocProvider我们可以从:

BlocProvider<BlocA>(
  create: (BuildContext context) => BlocA(),
  child: BlocProvider<BlocB>(
    create: (BuildContext context) => BlocB(),
    child: BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
      child: ChildA(),
    )
  )
)

至:

MultiBlocProvider(
  providers: [
    BlocProvider<BlocA>(
      create: (BuildContext context) => BlocA(),
    ),
    BlocProvider<BlocB>(
      create: (BuildContext context) => BlocB(),
    ),
    BlocProvider<BlocC>(
      create: (BuildContext context) => BlocC(),
    ),
  ],
  child: ChildA(),
)

BlocListener

BlocListener是Flutter小部件,它带有BlocWidgetListener和可选,Bloc并调用,listener以响应bloc中的状态变化。它应用于需要在每次状态更改时发生一次的功能,例如导航,显示a SnackBar,显示aDialog等。

listener`与in和函数不同,每次状态更改(**不**包括初始状态)仅被调用一次。`builder``BlocBuilder``void

如果省略cubit参数,BlocListener将使用BlocProvider和当前函数自动执行查找BuildContext

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

仅当您希望提供无法通过BlocProvider和当前访问的bloc时,才指定该bloc BuildContext

BlocListener<BlocA, BlocAState>(
  cubit: blocA,
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container()
)

为了对何时listener调用该函数进行细粒度的控制,listenWhen可以提供一个可选的选项。listenWhen获取先前的bloc状态和当前的bloc状态并返回一个布尔值。如果listenWhen返回true,listener将使用调用state。如果listenWhen返回false,listener则不会调用state

BlocListener<BlocA, BlocAState>(
  listenWhen: (previousState, state) {
    // return true/false to determine whether or not
    // to call listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  child: Container(),
)

MultiBlocListener

MultiBlocListener是Flutter小部件,可将多个BlocListener小部件合并为一个。 MultiBlocListener提高了可读性,消除了嵌套多个元素的需求BlocListeners。通过使用,MultiBlocListener我们可以从:

BlocListener<BlocA, BlocAState>(
  listener: (context, state) {},
  child: BlocListener<BlocB, BlocBState>(
    listener: (context, state) {},
    child: BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
      child: ChildA(),
    ),
  ),
)

至:

MultiBlocListener(
  listeners: [
    BlocListener<BlocA, BlocAState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocB, BlocBState>(
      listener: (context, state) {},
    ),
    BlocListener<BlocC, BlocCState>(
      listener: (context, state) {},
    ),
  ],
  child: ChildA(),
)

BlocConsumer

BlocConsumer公开builderlistener以便对新状态做出反应。BlocConsumer与嵌套类似BlocListenerBlocBuilder但减少了所需的样板数量。BlocConsumer仅应在需要重建UI和执行其他对状态更改进行响应的情况下使用cubitBlocConsumer取需要BlocWidgetBuilderBlocWidgetListener和任选的cubitBlocBuilderConditionBlocListenerCondition

如果cubit省略该参数,BlocConsumer将使用BlocProvider和当前函数自动执行查找 BuildContext

BlocConsumer<BlocA, BlocAState>(
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

可选的listenWhenbuildWhen可以实现,以更精细地控制何时listenerbuilder被调用。在listenWhenbuildWhen将在每个被调用cubit state的变化。它们各自采用先前的state和当前的,state并且必须返回a bool,以确定是否将调用builderand / orlistener函数。以前state会被初始化为statecubit的时候BlocConsumer被初始化。listenWhen并且buildWhen是可选的,如果未实现,则默认为true

BlocConsumer<BlocA, BlocAState>(
  listenWhen: (previous, current) {
    // return true/false to determine whether or not
    // to invoke listener with state
  },
  listener: (context, state) {
    // do stuff here based on BlocA's state
  },
  buildWhen: (previous, current) {
    // return true/false to determine whether or not
    // to rebuild the widget with state
  },
  builder: (context, state) {
    // return widget here based on BlocA's state
  }
)

RepositoryProvider

RepositoryProvider是Flutter小部件,它通过为其子节点提供存储库RepositoryProvider.of<T>(context)。它用作依赖项注入(DI)小部件,以便可以将存储库的单个实例提供给子树中的多个小部件。BlocProvider应该用于提供块,而RepositoryProvider只能用于存储库。

RepositoryProvider(
  create: (context) => RepositoryA(),
  child: ChildA(),
);

然后ChildA我们可以通过以下方式检索Repository实例:

// with extensions
context.read<RepositoryA>();

// without extensions
RepositoryProvider.of<RepositoryA>(context)

MultiRepositoryProvider

MultiRepositoryProvider是Flutter小部件,将多个RepositoryProvider小部件合并为一个。 MultiRepositoryProvider提高了可读性,消除了嵌套多个元素的需求RepositoryProvider。通过使用,MultiRepositoryProvider我们可以从:

RepositoryProvider<RepositoryA>(
  create: (context) => RepositoryA(),
  child: RepositoryProvider<RepositoryB>(
    create: (context) => RepositoryB(),
    child: RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
      child: ChildA(),
    )
  )
)

至:

MultiRepositoryProvider(
  providers: [
    RepositoryProvider<RepositoryA>(
      create: (context) => RepositoryA(),
    ),
    RepositoryProvider<RepositoryB>(
      create: (context) => RepositoryB(),
    ),
    RepositoryProvider<RepositoryC>(
      create: (context) => RepositoryC(),
    ),
  ],
  child: ChildA(),
)

最后

相关地址

Flutter状态管理和Dialog问题

fish_redux使用详解---看完就会用!

一种更优雅的Flutter Dialog解决方案

上一篇下一篇

猜你喜欢

热点阅读