原文地址: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)行顯示。
每個(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。