Flutter—BLoC的介紹使用與封裝

BLoC

其全稱爲 Business Logic Component,表示爲業務邏輯組件,簡稱 BLoC。從其名字來看感受和 業務邏輯有關係。由下圖json

看出,BLoC 是獨立處理業務邏輯,網絡數據請求等等邏輯的一個模塊,經過流的 Sinks, Streams 發佈監聽業務處理後的數據結果,其只關心業務處理,而 Widget 着重看中業務處理後的結果顯示。可見,Bloc 使得業務邏輯和 UI 相分離,這有幾點好處:api

  • 當業務邏輯內部改動時,儘量的減小 UI 程序的改動。bash

  • 當咱們改動 UI 時不會對業務邏輯產生影響markdown

  • 方便咱們測試業務邏輯功能網絡

Stream 簡單實現的 BLoC

若是你不知道什麼是 Stream,那能夠點此 什麼是 Stream? Dart 學習。app

咱們簡單地實現一個記錄按鈕點擊次數的 demo。less

BLoC 的實現

counter_bloc 類
import 'dart:async';

class CounterBLoC{

  //記錄按鈕點擊的次數
  //被流包裹的數據(能夠是任何類型)
  int _counter =0;

  //流控制
  final _counterStreamController =new StreamController<int>();

  //流
  Stream<int> get stream_counter=> _counterStreamController.stream;


   // 經過sink.add發佈一個流事件
  void addCount(){
    _counterStreamController.sink.add(++_counter);
  }


   //釋放流
   void dispose(){
     _counterStreamController.close();
   }

}
複製代碼

這個 CounterBLoC 的業務邏輯就是維護_counter 的值,除了 addCount 方法,你還能夠寫其餘改變_counter 值的方法,但要想改變事後的值被監聽到,則要調用 sink.add。async

BLoC 的使用

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';


import 'counter_bloc.dart';

class CountPage extends StatefulWidget {
  @override
  _CountPageState createState() => _CountPageState();
}

class _CountPageState extends State<CountPage> {

  //把一些相關的數據請求,實體類變換抽到CounterBLoC這個類裏
  //實例化CounterBLoC
  final _bloc = new CounterBLoC();


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("CountBloc"),),
      body: StreamBuilder(
        //監聽流,當流中的數據發生變化(調用過sink.add時,此處會接收到數據的變化而且刷新UI)
        stream: _bloc.stream_counter,
        initialData: 0,
        builder: (BuildContext context,AsyncSnapshot<int> snapshot){
          return Center(
            child: Text(snapshot.data.toString(),style: TextStyle(fontSize: 40,fontWeight: FontWeight.w300),),
          );
        },
      ),
      floatingActionButton: _getButton(),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    //關閉流
    _bloc.dispose();
  }




  Widget _getButton(){
    return FloatingActionButton(child: Icon(Icons.add),
        onPressed: (){
          // 點擊添加;其實也是發佈一個流事件
          _bloc.addCount();
        });
  }
}

複製代碼

當咱們點擊 floatActionButton 的時候,bloc 中的_counter 會增長,而後 sink.add 會通知監聽者(CountPage) _counter 數據發生變化,CountPage 刷新 UI。ide

這個案例雖然小,可是也能說明以上的好處,就是:性能

  • 業務與邏輯分離

咱們能夠修改業務類 CounterBLoC 邏輯,而儘量的減小 UI 的改動;反之改動 UI 界面,也不會影響到業務層代碼。

  • 業務邏輯測試更加容易(不用在寫測試界面)
  • 相比 setState() 刷新,能大大減小 build 的次數

RxDart 實現的 BLoC

除了 Stream 以外,rxdart 也能幫助咱們實現 Bloc 模式。

RxDart 擴展了原始的 Stream,它和 Stream 之間的對應關係以下:

Dart RxDart
Stream Observable
StreamController Subject

在使用 RxDart 時先將其加入依賴:

dependencies:
  flutter:
    sdk: flutter

  rxdart: ^0.22.0
複製代碼

網絡請求層

  • BeautyModel 實體類
class BeautyModel {
  String sId;
  String createdAt;
  String desc;
  List<String> images;
  String publishedAt;
  String source;
  String type;
  String url;
  bool used;
  String who;

  BeautyModel(
      {this.sId,
        this.createdAt,
        this.desc,
        this.images,
        this.publishedAt,
        this.source,
        this.type,
        this.url,
        this.used,
        this.who});

  BeautyModel.fromJson(Map<String, dynamic> json) {
    sId = json['_id'];
    createdAt = json['createdAt'];
    desc = json['desc'];
    images =json['images']==null?null:json['images'].cast<String>();
    publishedAt = json['publishedAt'];
    source = json['source'];
    type = json['type'];
    url = json['url'];
    used = json['used'];
    who = json['who'];
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['_id'] = this.sId;
    data['createdAt'] = this.createdAt;
    data['desc'] = this.desc;
    data['images'] = this.images;
    data['publishedAt'] = this.publishedAt;
    data['source'] = this.source;
    data['type'] = this.type;
    data['url'] = this.url;
    data['used'] = this.used;
    data['who'] = this.who;
    return data;
  }


  @override
  String toString() {
    // TODO: implement toString
    return toJson().toString();
  }
}
複製代碼
  • NetApi 類
import 'dart:convert';

import 'package:http/http.dart' show Client;

import 'beauty_model.dart';

class NetApi {
  Client client = new Client();

  Future<List<BeautyModel>> fetchBeauties() async {
    print("Starting get beauties ..");
    List models = List();
    final response =
        await client.get("http://gank.io/api/data/福利/15/1");
    if (response.statusCode == 200) {
      models = json.decode(response.body)["results"];
      return models.map((model){
        return BeautyModel.fromJson(model);
      }).toList();
    } else {
      throw Exception('Failed to load dog');
    }
  }
}

複製代碼

BLoC 類的實現

import 'package:rxdart/rxdart.dart';

import '../bloc_provider.dart';
import 'beauty_model.dart';
import 'net_api.dart';

class BeautyBloc {

  //網絡請求的實例
  NetApi _netApi =new NetApi();

  final _beautyFetcher = PublishSubject<List<BeautyModel>>();

  //提供被觀察的List<BeautyModel
  Observable<List<BeautyModel>> get beauties =>_beautyFetcher.stream;

  //獲取網絡數據
  fetchBeauties() async{

    List models = await _netApi.fetchBeauties();

    if(_beautyFetcher.isClosed)return;

    _beautyFetcher.sink.add(models);
  }

  //釋放
  dispose(){
    _beautyFetcher?.close();
  }
}
複製代碼

BLoC 的使用

import 'package:flutter/material.dart';

import 'beauty_bloc.dart';
import 'beauty_model.dart';

class BeautyPage extends StatefulWidget {
  @override
  _BeautyPageState createState() => _BeautyPageState();
}

class _BeautyPageState extends State<BeautyPage> {
  final _beautyBloc = BeautyBloc();

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("BeautyPage"),
      ),
      body: Container(
          child: StreamBuilder(
              //監聽流
              stream: _beautyBloc.beauties,
              builder: (context, AsyncSnapshot<List<BeautyModel>> snapshot) {
                if (snapshot.hasData) {
                  return ListView.builder(
                    itemBuilder: (BuildContext context, int index) {
                      return Card(
                          elevation: 8,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(20),
                          ),
                          child: Image.network(
                            snapshot.data[index].url,
                            fit: BoxFit.fill,
                          ));
                    },
                    itemCount: snapshot.data.length,
                  );
                } else if (snapshot.hasError) {
                  return Text('Beauty snapshot error!');
                }
                return Text('Loading Beauties..');
              })),
    );
  }
}

複製代碼

效果圖

以上就是 Bloc 的簡單使用,可是我發現每次都要 dispose()釋放,感受有點麻煩,因此急需一個通用的 Bloc 管理類,咱們就用 StatefulWidget 把 Bloc 包起來。其代碼以下:

BlocProvider

import 'package:flutter/material.dart';

import 'beauty_bloc_example/net_api.dart';

//獲類型
Type _typeOf<T>() {
  return T;
}

abstract class BlocBase {
  //一些網絡接口API
  NetApi netApi = new NetApi();

  //釋放
  void dispose();
}

//更方便的管理Bloc
class BlocProvider<T extends BlocBase> extends StatefulWidget {
  final T bloc; //bloc

  final Widget child; //子Widget

  //構造
  const BlocProvider({Key key,@required this.bloc, @required this.child}) : super(key: key);

  //經過ancestorInheritedElementForWidgetOfExactType獲取
  //bloc 實例
  static T of<T extends BlocBase>(BuildContext context) {
    final type = _typeOf<_BlocProviderInherited<T>>();
    _BlocProviderInherited<T> provider =
        context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;
    return provider?.bloc;
  }
  @override
  _BlocProviderState<T> createState() {
    // TODO: implement createState
    return _BlocProviderState();
  }
}

class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<BlocBase>> {
  @override

  /// 便於資源的釋放
  void dispose() {
    widget.bloc?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new _BlocProviderInherited<T>(
      child: widget.child,
      bloc: widget.bloc,
    );
  }
}

class _BlocProviderInherited<T> extends InheritedWidget {
  _BlocProviderInherited({
    Key key,
    @required Widget child,
    @required this.bloc,
  }) : super(key: key, child: child);

  final T bloc;

  @override
  bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}


複製代碼

對 BlocProvider 的一些相關說明

因爲 InheritedWidget 中並無 dispose() 方法,因此僅僅只在 InheritedWidget 中沒法及時的釋放資源,因此引用了 StatefulWidget。那爲何不簡簡單單的只用 StatefulWidget,還要套一層 InheritedWidget 呢?這是因爲 InheritedWidget 使得性能更好,若是你不瞭解 InheritedWidget,那麼InheritedWidget 的運用與源碼解析將會幫你更好的認識 InheritedWidget。

固然,若是咱們不用 InheritedWidget 也行,只要將 of 方法中的實現改成:

static T of<T extends BlocBase>(BuildContext context){
    final type = _typeOf<BlocProvider<T>>();
    BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  }
複製代碼

,而後 build 方法直接返回 widget.bloc 便可。

可是咱們看 context.ancestorWidgetOfExactType()的方法以下:

@override
  Widget ancestorWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    Element ancestor = _parent;
    while (ancestor != null && ancestor.widget.runtimeType != targetType)
      ancestor = ancestor._parent;
    return ancestor?.widget;
  }
複製代碼

這個方法會不斷地循環往上找,若是嵌套太深,效率上會受到影響,而 InheritedWidget 不會,具體說明看上文。既然咱們用了 InheritedWidget,那爲何咱們用的是 context.ancestorInheritedElementForWidgetOfExactType()方法而不是咱們在 InheritedWidget 文中使用的 context.inheritFromWidgetOfExactType()方法呢?看一下源碼你就知道了:

  • inheritFromWidgetOfExactType
@override
  InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    if (ancestor != null) {
      assert(ancestor is InheritedElement);
      //多出了刷新依賴Widget的邏輯
      return inheritFromElement(ancestor, aspect: aspect);
    }
    _hadUnsatisfiedDependencies = true;
    return null;
  }



 @override
  InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
    assert(ancestor != null);
    _dependencies ??= HashSet<InheritedElement>();
    _dependencies.add(ancestor);
    ancestor.updateDependencies(this, aspect);
    return ancestor.widget;
  }
複製代碼
  • ancestorInheritedElementForWidgetOfExactType
@override
  InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType) {
    assert(_debugCheckStateIsActiveForAncestorLookup());
    final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
    return ancestor;
  }
複製代碼

可見 inheritFromWidgetOfExactType 除了獲取最近的 InheritedWidget 還刷新了以來的控件,而咱們這裏並不須要刷新,因此改用 ancestorInheritedElementForWidgetOfExactType。

BlocProvider 的使用

  • BeautyBaseBloc 類
import 'package:rxdart/rxdart.dart';

import '../bloc_provider.dart';
import 'beauty_model.dart';
import 'net_api.dart';

class BeautyBaseBloc extends BlocBase {


  final _beautyFetcher = PublishSubject<List<BeautyModel>>();

  //提供被觀察的List<BeautyModel
  Observable<List<BeautyModel>> get beauties =>_beautyFetcher.stream;

  //獲取網絡數據
  fetchBeauties() async{

    List models = await netApi.fetchBeauties();

    if(_beautyFetcher.isClosed)return;

    _beautyFetcher.sink.add(models);
  }



 @override
  void dispose() {
    // TODO: implement dispose
   _beautyFetcher.close();
  }


複製代碼
  • BeautyBasePage 類
import 'package:flutter/material.dart';
import 'package:flutter_bloc_example/bloc_provider.dart';

import 'beauty_base_bloc.dart';
import 'beauty_bloc.dart';
import 'beauty_model.dart';

class BeautyBasePage extends StatefulWidget {
  @override
  _BeautyBasePageState createState() => _BeautyBasePageState();
}

class _BeautyBasePageState extends State<BeautyBasePage> {
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    //拿到 BeautyBaseBloc 實例
    BeautyBaseBloc _bloc= BlocProvider.of<BeautyBaseBloc>(context);
    //獲取網絡數據
    _bloc.fetchBeauties();

    return Scaffold(
      appBar: AppBar(
        title: Text("BeautyPage"),
      ),
      body: Container(
          child: StreamBuilder(
              stream: _bloc.beauties,
              builder: (context, AsyncSnapshot<List<BeautyModel>> snapshot) {
                if (snapshot.hasData) {
                  print('has data');
                  return ListView.builder(
                    itemBuilder: (BuildContext context, int index) {
                      return Card(
                          elevation: 8,
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(20),
                          ),
                          child: Image.network(
                            snapshot.data[index].url,
                            fit: BoxFit.fill,
                          ));
                    },
                    itemCount: snapshot.data.length,
                  );
                } else if (snapshot.hasError) {
                  return Text('Beauty snapshot error!');
                }
                return Text('Loading Beauty..');
              })),
    );
  }
}

複製代碼
  • 同 InheritedWidget 同樣,在父節點插入 BlocProvider
class BeautyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "BeautyBloc Demo",
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      //插入 BlocProvider
      home: BlocProvider<BeautyBaseBloc>(
          child: BeautyBasePage(), bloc: new BeautyBaseBloc()),
    );
  }
}
複製代碼

至此咱們通用的 BlocProvider 管理類也寫完了,其中它有 InheritedWidget 所擁有的特色,也能在 widget 銷燬的時候自動的關閉流。

相關文章
相關標籤/搜索