Article structure:

  • Several advantages of the Bloc + RxDart
  • The sample code simply invokes the network interface and then refreshes the page display
  • Bloc + RxDART implementation principle
  • The Bloc + RxDART classic use scenario solves two problems in business scenarios.

1. Several advantages of bloc+ RxDart

1). Implement the MVC pattern

Widget only does UI presentation, Bloc implements control logic, and Model does data encapsulation. In the new demand development of the operation line, I actually used Bloc to make a new page, which is the business failure page. This page has three levels of failure reason selection, failure reason is cascaded, and each time after selecting equipment to re-request inventory, submit failure and other operations.

2). Implement cross-level Model access and method calls

Fetching data and operations across widget data on the same page can be a lot. For example, when submitting a defeat, you need to fetch values from multiple sub-widgets, or if bloc is not used, you need to pass model or callback methods in the sub-widget’s constructor.

If there are many page hierarchies, the model and callback methods need to be passed level by level. For example, on the left side of the figure below, the submiModel or callback method needs to be passed from the top to the bottom to collect the information from all the child widgets, and finally gather the parameters from all the widgets into the submitModel to submit the failed request.

The code on the right below is a way to pass parameters layer by layer, which can be a headache if there are many layers, not only declaring variables repeatedly, but changing them everywhere if you change them.

With Bloc, the constructor is particularly clean and does not need to pass model or callback methods layer by layer.

The following two pieces of code are the lowest level widgets in the page widget structure. They don’t need to pass any parameters or callback methods in the constructor, but they can also call the model and call the callback methods. How to do that? Read on. 🙂

3). Do not directly use setState method (StreamController)

In the past, using setState directly in a project may cause some problems. For example, if the current State is unmounted, calling setState directly will throw an exception.

Bloc is used to refresh the page in the following way. The key code is _requestController.add

4). Cleaner local refresh (StreamBuilder)

We have some very complex pages in our existing project, but only one file is used, and all the business logic and widget components are in this page.

Here is an example of code for this hybrid network request and widget display.

This eliminates the need to pass data and callback methods layer by layer compared to the multi-widget approach. But destroying the basic principles of object-oriented development, information hiding, such as modifying a widget a small function, because in a large file modification, in which the complexity of business logic to change a small function may affect other logic, have unexpected consequences, this piece of code is very difficult to maintain.

At the same time, calling setState directly to refresh the entire page also has performance problems, and will produce the problem of throwing exceptions.


Bloc is used more freely, with multiple sub-widgets that can be refreshed in the sub-widgets as shown in the second point.

You can also modify the code above directly to implement a partial refresh in a widget.

The fourth advantage is actually implemented in conjunction with the third, both through streamBuidler and streamController, which are used together. Bloc is actually streamBuilder + streamController + ancestorWidgetOfExactType, ajax and web side is more similar, several existing technology integration out of new things.

The StreamBuilder must pass in a Stream object to refresh, and that stream must be retrieved from the streamController.

When _requestController.add(deviceModel) is called in the above code, the following StreamBuilder automatically invokes the Builder method to partially refresh the StreamBuilder instead of refreshing the outer InkWell.

StreamBuilder is a Widget that can be inserted anywhere, making local refreshes very simple.

2. Simple sample code

To implement a BlocProvider that is already integrated in an existing project, you only need one BlocProvider for the entire project, which can be placed in the Utils directory.

Bloc is implemented using only this class and the methods that come with Flutter. The code is very simple and there is no need to introduce a third party library into the YAML file.

All blocs need to inherit BlocBase, and usually one bloc corresponds to a business logic that refreshes the page (e.g., network request, TAB switch).

Any parent page, grandpa page

Below is the code for Bloc creation and blocProvider.

3. Implementation principle of Bloc + RxDART

1). Bloc implementation principle

Bloc triggers the refresh by using StreamBuilder+StreamController, but using StreamController directly has a big disadvantage of only one-to-one listen and very single mode. So you need to use RxDart, which is covered below.

Another important feature of Bloc is to obtain bloc across different levels, from which model can be obtained or methods can be called.

Which USES the ancestorWidgetOfExactType, because flutter widget is a tree structure, stored in BuildContext structure levels, from a child node to the parent node in turn to conform to the type of widgets, So all widgets that use the BlocProvider package can be found using the OF method, which returns the Bloc in the widget. Bloc is eventually preserved in state on the outer floor.

final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
Copy the code

In the figure below, the lowermost widget can directly obtain the submitBloc of the top page after calling the of method. Because the flutter widget is a tree structure, the structure hierarchy is stored in BuildContext, and the widgets that match the type can be found from the child nodes to the parent nodes. So all widgets that use the BlocProvider package can be found using the OF method, which returns the Bloc in the widget. Bloc is eventually preserved in state on the outer floor.

2). Rxdart implementation principle

StreamController implements observer mode. Instead of being called directly, listeners are observed and receive asynchronous callbacks when events are added to the StreamController.

Var obs = Observable(stream.fromiterable ([1,2,3,4,5])).map((item)=>++item); obs.listen(print); Output :2 3 4 5 6Copy the code

Subject is an extension of StreamController and is commonly used in the following ways.

PublishSubject: StreamController broadcast version, StreamController can only have one listener, PublishSubject can listen multiple times. The other subjects below are also broadcast versions.

BehaviorSubject: Caches the latest event. If an event occurs first and then listen, the cached event can also be received.

ReplaySubject: Caches all events. All events added before will be sent sequentially after listen.

4. Classic usage scenarios

1). Multiple network image components share one HTTP download.

It was found that this was caused by creating a new bloc each time, which was solved by using a Bloc List.

static ZNImageBLocList _getInstance() {
  if (_instance == null) {
    _instance = new ZNImageBLocList._internal();
  }
  return _instance;
}

List<ZNImageBloc> blocList;
 
ZNImageBloc getBloc(String url,ZNImageConfig config){
  for(ZNImageBloc bloc in blocList){
    if(bloc.imageUrl==url){
      return bloc;
    }
  }
  ZNImageBloc bloc = new ZNImageBloc(url, config);
  blocList.add(bloc);
  return bloc;
}
Copy the code
class ZnWebImageState extends State<ZnWebImage> {
  ZNImageBloc bloc;

  @override
  void dispose() {
    // TODO: implement dispose
    bloc.dispose();
    super.dispose();
  }

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    bloc = ZNImageBLocList.instance.getBloc(widget.url, widget.config);
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    returnBlocProvider<ZNImageBloc>( bloc: bloc, child: ZNImageContainer(), ); }}Copy the code

In this way, multiple Zn_web_image widgets can share the same bloc, display the progress bar synchronously during the download process, and receive notification to display the downloaded image at the same time after the download is completed.

2). Multiple pages in tabBarView share the same authentication attribute.

Bloc also solved a headache problem in the credit investigation project, that is, whether it has passed the certification status. This status is shared globally and there are multiple change entries. After the certification status is changed, all pages are required to update the display.

1. Top of the home page in the first TAB.

2. The order confirmation page can be entered from the first TAB home page or the order list in the second TAB.

3. The personal center page of the fourth TAB.

At the beginning, when bloc was not used, multiple network requests were written to obtain the authentication status, and the isAuth attribute was saved separately on each page.

1. The home page uses requestAuth to obtain the isAuth attribute from the server.

2. The confirm order page also needs to get the isAuth attribute from requestAuth. Although the home page gets the isAuth attribute by request, entering the order confirmation page from the home page can pass the isAuth attribute. This property is not available when entering from the order list, which is initialized at the same time as the home page or requires a network request.

3. The personal-center page also needs to obtain the isAuth attribute from requestAuth. Although the home page gets the isAuth attribute by request, the personal-center page is initialized in the tabbarView at the same time as the home page and cannot be passed the isAuth attribute through the constructor.

This is very troublesome, and a lot of work.

After bloc is used, a globally shared property like isAuth only needs to add a BlocProvider layer to the outside of the MaterialApp. Any sub-page can directly obtain isAuth, synchronize isAuth status updates, and refresh the UI display.

// CreditAuthBloc authBloc; @override voiddispose() {
  authBloc.dispose();
  super.dispose();
}

@override
void initState() {
  super.initState();
  authBloc = CreditAuthBloc();
}


@override
Widget build(BuildContext context) {
  return BlocProvider<CreditAuthBloc>(bloc: authBloc,child: MaterialApp(
    title: ' ',
    initialRoute:'/', home: Scaffold( body: Column( children: <Widget>[ Expanded( child: TabBarView(physics: NeverScrollableScrollPhysics(),controller: tabController, children: [ZNMarketPage (), / / homepage ZNOrderList (), / / order list ZNBill (), ZNMyCenter (), / / personal center]),),,)))); }Copy the code

Sub-page to get Bloc

// Use auth in bloc // int userAuthStatus = 0; // Personal authentication 0: unauthenticated, 1: authenticated CreditAuthBloc authBloc; @override voiddidChangeDependencies() {
    // TODO: implement didChangeDependencies
    super.didChangeDependencies();
    authBloc = BlocProvider.of<CreditAuthBloc>(context);
  }
Copy the code

Use Bloc via streamBuilder