原文地址:https://medium.com/flutterpub/effective-bloc-pattern-45c36d76d5fe
这篇文章中列一些BLoC模式的经验,避免在开发过程中出现常用的错误。下面是使用BLoC遵循的八个点。
每个界面都要有自己的BLoC
这是需要记住的最重要的一点,如果你有很多界面,比如登录、注册,而每个都需要处理数据,如果你认为一个共用的BLoC可以方便的让他们共享数据的话,那样其实并不好,更好的方式是数据存储库向BLoC提供数据,然后BLoC获取到数据之后提供给界面进行显示。
每个BLoC都要有dispose方法
每个BLoC都需要创建dispose方法,因为这是你用来清理和关闭所创建的那些流的地方。下面的代码是一个简单的dispose方法的示例。
class MoviesBloc {
final _repository = Repository();
final _moviesFetcher = PublishSubject();
Observable get allMovies => _moviesFetcher.stream;
fetchAllMovies() async {
ItemModel itemModel = await _repository.fetchAllMovies();
_moviesFetcher.sink.add(itemModel);
}
dispose() {
_moviesFetcher.close();
}
}
不要将StatelessWidget和BLoC一起使用
当你想向创建一个将数据传递到BLoC,然后从BLoC获取数据并显示的界面时,一定要使用StatefulWidget。使用StatefulWidget而不是StatelessWidget最大的优点就是StatefulWidget中有可以使用的声明周期方法。StatelessWidget只适合用来构建一个小型的简单的界面。
覆盖didChangeDependencies初始化BLoC
如果你需要一个Context来初始化BLoC,那么这是最适合的一个方法,你可以把它当成一个初始化方法(仅适用于BLoC),你可能会说有initState,为什么要用didChangeDependencies,文档清楚地提到,从didChangeDependencies()方法调用BuildContext.inheritFromWidgetOfExactType是安全的。下面是使用这个方法的示例:
@override
void didChangeDependencies() {
bloc = MovieDetailBlocProvider.of(context);
bloc.fetchTrailersById(movieId);
super.didChangeDependencies();
}
覆盖dispose方法
我们需要提供一个方法用来调用BLoC的dispose方法。这个方法用来在你离开该页面的时候调用。
@override
void dispose() {
bloc.dispose();
super.dispose();
}
使用RxDart处理复杂的逻辑
RxDart是google的响应式编程库,该库只是Dart提供的Stream Api的包装器,我建议你仅在链接多个网络请求时才使用这个库,对于简单的功能,使用Dart提供的Stream Api就可以。下面就是使用Stream Api的例子。
import 'dart:async';
class Bloc {
//Our pizza house
final order = StreamController();
//Our order office
Stream 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.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是一个特殊的StreamController,它捕获已添加到控制器的最新Subject,并作为新侦听器的第一项发送出去。即使你调用BehaviorSubject的close和drain方法,它仍将保留最后一项在取消订阅时发出。如果不了解该特性的话,将是一场噩梦。而PublishSubject不存储最后一项,查看此项目以便了解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(),
),
),
),
);
}
}
上面代码中你可以看到多个Provider嵌套在一起,如果你继续在同一个链中添加更多的BLoC,你将会发现代码很难扩展,BLoC仅保存应用程序所需要的UI配置,但是如果你需要在widgets树中访问多个BLoC时,那么上面的示例是没问题的。但是建议你在大多数情况下不要这样嵌套,只在实际需要的地方提供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,而不是整个组件树,而且将MovieDetailScreen存储在一个变量中,这样不用每次都重新创建MovieDetailScreen。