Dart is a modern programming language that incorporates many of the language’s features, especially the responsive programming style.

Dart provides a Stream mechanism that allows Flutter to easily build responsive programming while eliminating cross-page and cross-widget data management issues.

The responsive programming of Flutter has the following characteristics.

  • Data management is carried out around the Stream through the Stream sink and listen
  • Once a Widget sends out a Stream, it doesn’t need to be aware of external influences. Similarly, when a Widget listens to Stream, it only needs to build its UI based on changes in data
  • The widgets are no longer coupled to each other, and the data is retrieved through the Stream pipe without dependence on each other

By virtue of this property of Flutter, Google proposed a BLoC model for data management.

BLoC mode was designed by Paolo Soares and Cong Hui and first proposed by Google in the 2018 DartConf. It stands for Business Logic Component.

In BLoC mode, widgets are completely decoupled from Data:

  • The business logic processing of App is all in BLoC
  • The Widget sends data to BLoC by Sink
  • BLoC rebuilds the UI via the Stream notification Widget

In fact, this is somewhat similar to MVP and MVC modes. BLoC mode divides the whole App into three layers: Data Layer, BLoC Layer, UI Layer. Data Layer and UI Layer can only communicate with BLoC Layer bidirectional, but they are isolated from each other.

The official counter demo will be rewritten in BLoC mode to let you know the general paradigm of BLoC mode creation.

Create the BLoC business processing class

BLoC class is a business logic processing class, which does not contain any UI logic, and a BLoC class only processes an independent business logic. In the official Demo, the business logic consists of the following parts.

  • Record hits
  • Click to increase the number of clicks

Therefore, the BLoC class created only exposes these two businesses, namely the external Stream and increment functions.

abstract class BlocBase { void dispose(); } class IncrementBloc implements BlocBase {// _ implements BlocBase; StreamController<int> _countController; IncrementBloc() { _count = 0; _countController = StreamController<int>(); } Stream<int> get value => _countController.stream; increment() { _countController.sink.add(++_count); } dispose() { _countController.close(); }}Copy the code

BlocBase simply wraps the Dispose function, which is used to dispose the resource. IncrementBloc is the processing core of this business, through the Stream, so that the outside world can listen for changes in data.

A standard BLoC class usually contains the following parts.

  • Private Model and StreamController
  • The exposed get method returns Stream
  • Exposed business processing functions
  • The dispose function

Create the BLoC Management class

The BLoC Management class is a generic processing class that manages the BLoC business processing class with the help of the StatefulWidget. It also acts as a glue for data and UI, injecting the BLoC class of a specific business into a specific business UI.

class BlocProvider<T extends BlocBase> extends StatefulWidget { BlocProvider({ Key key, @required this.child, @required this.bloc, }) : super(key: key); final T bloc; final Widget child; @override _BlocProviderState<T> createState() => _BlocProviderState<T>(); static T of<T extends BlocBase>(BuildContext context) { BlocProvider<T> provider = context.findAncestorWidgetOfExactType<BlocProvider<T>>(); return provider.bloc; } } class _BlocProviderState<T> extends State<BlocProvider<BlocBase>> { @override void dispose() { widget.bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return widget.child; }}Copy the code

The BLoC management class actually does two things.

  • Treat the business UI as a child Widget
  • Provides the business UI with the specified BLoC logic processing class

Create a BLoC UI

@override Widget build(BuildContext context) { return BlocProvider<IncrementBloc>( bloc: IncrementBloc(), child: CounterPage(), ); } class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context); return Scaffold( body: Center( child: StreamBuilder<int>( stream: bloc.value, initialData: 0, builder: (BuildContext context, AsyncSnapshot<int> snapshot) { return Text('You hit me: ${snapshot.data} times'); }, ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () => bloc.increment(), ), ); }}Copy the code

In the UI layer, BlocProvider. Of <IncrementBloc>(context) can be used to retrieve the BLoC of the specified type, so that the interface and data defined within it can be used.

At the UI level, there are two ways to write this: either use the StatelessWidget directly and initialize blocprovider.of <IncrementBloc>(context) in the build function, or use the StatefulWidget, Init in didChangeDependencies(), because didChangeDependencies is a safer way to get the Context than initState.

This approach achieves complete decoupling. As long as the interface and data model in BLoC are defined and the UI is displayed at the front end, it is completely independent of data.

At the UI level, all you need to do is parse the data to listen on via the StreamBuilder. The Builder function of the StreamBuilder is an AsyncWidgetBuilder that builds widgets asynchronously, Its parameter AsyncSnapshot<int> snapshot is a snapshot of data in a stream. You can access data in a stream through snapshot.data or obtain abnormal information through snapshot.hasError or snapshot.error.

BLoC stream unicast and broadcast

A Flutter can be divided into two types of streams: unicast and multicast. By default, a unicast Stream is created, so only one StreamBuilder can listen to a Flutter. You need to change the Stream created by default to a multicast Stream.

_countController = StreamController.broadcast<int>();
Copy the code

One thing to note when using multiple pages is that the flow is real-time and not sticky. For example, when the first interface adds data to a stream and the second interface opens, it is not possible to directly retrieve the latest data of the stream after the StreamBuilder is created, because the data in the stream will end before the StreamBuilder listens. So in this case, you either initialize initialData to the latest data in the stream before creating the StreamBuilder; Or use RxDart to enhance the functionality of the flow.

Cultivate immortality

Flutter Dojo is open source and has been enjoyed by many Flutter learners and enthusiasts. More and more people are participating in Flutter learning. If you are interested in Flutter, you can leave a comment.

Project Address:

Github.com/xuyisheng/f…