Effective BLoC Pattern

原文地址:https://medium.com/flutterpub/effective-bloc-pattern-45c36d76d5fe

這篇文章中列一些BLoC模式的經(jīng)驗(yàn),避免在開發(fā)過(guò)程中出現(xiàn)常用的錯(cuò)誤。下面是使用BLoC遵循的八個(gè)點(diǎn)。

每個(gè)界面都要有自己的BLoC

這是需要記住的最重要的一點(diǎn),如果你有很多界面,比如登錄、注冊(cè),而每個(gè)都需要處理數(shù)據(jù),如果你認(rèn)為一個(gè)共用的BLoC可以方便的讓他們共享數(shù)據(jù)的話,那樣其實(shí)并不好,更好的方式是數(shù)據(jù)存儲(chǔ)庫(kù)向BLoC提供數(shù)據(jù),然后BLoC獲取到數(shù)據(jù)之后提供給界面進(jìn)行顯示。

image.png

每個(gè)BLoC都要有dispose方法

每個(gè)BLoC都需要?jiǎng)?chuàng)建dispose方法,因?yàn)檫@是你用來(lái)清理和關(guān)閉所創(chuàng)建的那些流的地方。下面的代碼是一個(gè)簡(jiǎn)單的dispose方法的示例。

class MoviesBloc {
  final _repository = Repository();
  final _moviesFetcher = PublishSubject<ItemModel>();

  Observable<ItemModel> get allMovies => _moviesFetcher.stream;

  fetchAllMovies() async {
    ItemModel itemModel = await _repository.fetchAllMovies();
    _moviesFetcher.sink.add(itemModel);
  }

  dispose() {
    _moviesFetcher.close();
  }
}

不要將StatelessWidget和BLoC一起使用

當(dāng)你想向創(chuàng)建一個(gè)將數(shù)據(jù)傳遞到BLoC,然后從BLoC獲取數(shù)據(jù)并顯示的界面時(shí),一定要使用StatefulWidget。使用StatefulWidget而不是StatelessWidget最大的優(yōu)點(diǎn)就是StatefulWidget中有可以使用的聲明周期方法。StatelessWidget只適合用來(lái)構(gòu)建一個(gè)小型的簡(jiǎn)單的界面。

覆蓋didChangeDependencies初始化BLoC

如果你需要一個(gè)Context來(lái)初始化BLoC,那么這是最適合的一個(gè)方法,你可以把它當(dāng)成一個(gè)初始化方法(僅適用于BLoC),你可能會(huì)說(shuō)有initState,為什么要用didChangeDependencies,文檔清楚地提到,從didChangeDependencies()方法調(diào)用BuildContext.inheritFromWidgetOfExactType是安全的。下面是使用這個(gè)方法的示例:

@override
  void didChangeDependencies() {
    bloc = MovieDetailBlocProvider.of(context);
    bloc.fetchTrailersById(movieId);
    super.didChangeDependencies();
  }

覆蓋dispose方法

我們需要提供一個(gè)方法用來(lái)調(diào)用BLoC的dispose方法。這個(gè)方法用來(lái)在你離開該頁(yè)面的時(shí)候調(diào)用。

@override
  void dispose() {
    bloc.dispose();
    super.dispose();
  }

使用RxDart處理復(fù)雜的邏輯

RxDart是google的響應(yīng)式編程庫(kù),該庫(kù)只是Dart提供的Stream Api的包裝器,我建議你僅在鏈接多個(gè)網(wǎng)絡(luò)請(qǐng)求時(shí)才使用這個(gè)庫(kù),對(duì)于簡(jiǎn)單的功能,使用Dart提供的Stream Api就可以。下面就是使用Stream Api的例子。

import 'dart:async';

class Bloc {

  //Our pizza house
  final order = StreamController<String>();

  //Our order office
  Stream<String> get orderOffice => order.stream.transform(validateOrder);

  //Pizza house menu and quantity
  static final _pizzaList = {
    "Sushi": 2,
    "Neapolitan": 3,
    "California-style": 4,
    "Marinara": 2
  };

  //Different pizza images
  static final _pizzaImages = {
    "Sushi": "http://pngimg.com/uploads/pizza/pizza_PNG44077.png",
    "Neapolitan": "http://pngimg.com/uploads/pizza/pizza_PNG44078.png",
    "California-style": "http://pngimg.com/uploads/pizza/pizza_PNG44081.png",
    "Marinara": "http://pngimg.com/uploads/pizza/pizza_PNG44084.png"
  };


  //Validate if pizza can be baked or not. This is John
  final validateOrder =
      StreamTransformer<String, String>.fromHandlers(handleData: (order, sink) {
    if (_pizzaList[order] != null) {
      //pizza is available
      if (_pizzaList[order] != 0) {
        //pizza can be delivered
        sink.add(_pizzaImages[order]);
        final quantity = _pizzaList[order];
        _pizzaList[order] = quantity-1;
      } else {
        //out of stock
        sink.addError("Out of stock");
      }
    } else {
      //pizza is not in the menu
      sink.addError("Pizza not found");
    }
  });

  //This is Mia
  void orderItem(String pizza) {
    order.sink.add(pizza);
  }
}

Use PublishSubject over BehaviorSubject

BehaviorSubject是一個(gè)特殊的StreamController,它捕獲已添加到控制器的最新Subject,并作為新偵聽器的第一項(xiàng)發(fā)送出去。即使你調(diào)用BehaviorSubject的close和drain方法,它仍將保留最后一項(xiàng)在取消訂閱時(shí)發(fā)出。如果不了解該特性的話,將是一場(chǎng)噩夢(mèng)。而PublishSubject不存儲(chǔ)最后一項(xiàng),查看此項(xiàng)目以便了解BehaviorSubject的行為。

正確使用BLoC providers

import 'package:flutter/material.dart';
import 'ui/login.dart';
import 'blocs/goals_bloc_provider.dart';
import 'blocs/login_bloc_provider.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LoginBlocProvider(
      child: GoalsBlocProvider(
        child: MaterialApp(
          theme: ThemeData(
            accentColor: Colors.black,
            primaryColor: Colors.amber,
          ),
          home: Scaffold(
            appBar: AppBar(
              title: Text(
                "Goals",
                style: TextStyle(color: Colors.black),
              ),
              backgroundColor: Colors.amber,
              elevation: 0.0,
            ),
            body: LoginScreen(),
          ),
        ),
      ),
    );
  }
}

上面代碼中你可以看到多個(gè)Provider嵌套在一起,如果你繼續(xù)在同一個(gè)鏈中添加更多的BLoC,你將會(huì)發(fā)現(xiàn)代碼很難擴(kuò)展,BLoC僅保存應(yīng)用程序所需要的UI配置,但是如果你需要在widgets樹中訪問(wèn)多個(gè)BLoC時(shí),那么上面的示例是沒(méi)問(wèn)題的。但是建議你在大多數(shù)情況下不要這樣嵌套,只在實(shí)際需要的地方提供BLoC。向下面這樣使用:

openDetailPage(ItemModel data, int index) {
    final page = MovieDetailBlocProvider(
      child: MovieDetail(
        title: data.results[index].title,
        posterUrl: data.results[index].backdrop_path,
        description: data.results[index].overview,
        releaseDate: data.results[index].release_date,
        voteAverage: data.results[index].vote_average.toString(),
        movieId: data.results[index].id,
      ),
    );
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) {
        return page;
      }),
    );
  }

這樣MovieDetailBlocProvider只為MovieDetail提供BLoC,而不是整個(gè)組件樹,而且將MovieDetailScreen存儲(chǔ)在一個(gè)變量中,這樣不用每次都重新創(chuàng)建MovieDetailScreen。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,199評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,414評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容