目錄
1. BLoC (狀態(tài)管理庫中Star最高)
2. GetIt
1. BLoC (狀態(tài)管理庫中Star最高)
更多的是一種設(shè)計模式,按照這種設(shè)計模式可以實現(xiàn)很多種狀態(tài)管理。
基于Stream / Observable范式。BLoC 依賴 Stream和 StreamController實現(xiàn),組件通過Sinks發(fā)送更新狀態(tài)的事件,然后再通過 Streams 通知其他組件更新。事件處理和通知刷新的業(yè)務(wù)邏輯都是由BLoC完成,從而實現(xiàn)業(yè)務(wù)邏輯與UI層的分離(有點類似 Redux),并且邏輯部分可以復(fù)用和可以單獨進(jìn)行單元測試。
添加依賴庫
bloc插件
flutter_bloc插件
提供了BlocProvider、BlocBuilder、BlocListener、BlocConsumer、RepositoryProvider等。
bloc_package插件
快速在Flutter/Dart中實現(xiàn)BLoC模式的插件。
- 3個重要概念
- Cubit(繼承自管理狀態(tài)數(shù)據(jù)的BlocBase)
可以管理任意類型的數(shù)據(jù),包括基本類型到復(fù)雜對象。
Cubit調(diào)用emit構(gòu)建新的狀態(tài)數(shù)據(jù)前需要給狀態(tài)數(shù)據(jù)一個初始值。
當(dāng)狀態(tài)數(shù)據(jù)發(fā)生改變時會觸發(fā)onChange回調(diào),出錯時會觸發(fā)onError回調(diào)。
UI界面可以通過調(diào)用 Cubit 對外暴露的更新狀態(tài)方法觸發(fā)狀態(tài)更新,而在 onChange 中會得到更新前后的狀態(tài),從而可以觸發(fā)界面刷新。
例:
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
- BlocObserver(可以同時監(jiān)聽所有的Cubit的變化)
例:
class CounterCubit extends Cubit<int> {
CounterCubit({initial = 0}) : super(initial);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
}
class MyBlocObserver extends BlocObserver {
@override
void onCreate(BlocBase bloc) {
print('BloC Observer onCreate: ${bloc.state}');
super.onCreate(bloc);
}
@override
void onChange(BlocBase bloc, Change change) {
print('BloC Observer onChange: $change');
super.onChange(bloc, change);
}
@override
void onClose(BlocBase bloc) {
print('BloC Observer onClose: ${bloc.state}');
super.onClose(bloc);
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('Bloc Observer onError: $error, $stackTrace');
super.onError(bloc, error, stackTrace);
}
@override
void onEvent(Bloc bloc, Object? event) {
print('Bloc Observer onEvent: $event, ${bloc.state}');
super.onEvent(bloc, event);
}
}
void main() {
Bloc.observer = MyBlocObserver();
final cubit = CounterCubit();
cubit.increment();
print('after increment: ${cubit.state}');
cubit.decrement();
print('after decrement: ${cubit.state}');
final anotherCubit = CounterCubit(10);
anotherCubit.increment();
cubit.close();
anotherCubit.close();
}
/*
控制臺輸出如下:
BloC Observer onCreate: 0
BloC Observer onChange: Change { currentState: 0, nextState: 1 }
BloC Observer onChange: Change { currentState: 1, nextState: 0 }
BloC Observer onCreate: 10
BloC Observer onChange: Change { currentState: 10, nextState: 11 }
BloC Observer onClose: 0
BloC Observer onClose: 11
*/
- BLoc(繼承自BlocBase,比Cubit更高級)
使用events而不是暴露的函數(shù)來更新狀態(tài)。
在Bloc內(nèi)部有一個onEvent方法,通過EventTransformer將event轉(zhuǎn)換為更新狀態(tài)的方法來刷新狀態(tài)數(shù)據(jù)。每個event都可以有對應(yīng)的 EventHandler來處理該 event,完成后再通過 emit 觸發(fā)通知狀態(tài)更新。當(dāng)狀態(tài)轉(zhuǎn)變前會調(diào)用 onTransition,有當(dāng)前的狀態(tài)、觸發(fā)更新的event、下一個狀態(tài)。
例:
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc(int initialState) : super(initialState) {
on<IncrementEvent>((event, emit) => emit(state + 1));
on<DecrementEvent>((event, emit) => emit(state - 1));
}
@override
void onTransition(Transition<CounterEvent, int> transition) {
print(
'Current: ${transition.currentState}, Next: ${transition.nextState}, Event: ${transition.event}');
super.onTransition(transition);
}
}
void main() {
Bloc.observer = MyBlocObserver();
final counterBloc = CounterBloc(5);
counterBloc.add(IncrementEvent());
counterBloc.add(DecrementEvent());
counterBloc.close();
}
/*
輸出如下:
Current: 5, Next: 6, Event: Instance of 'IncrementEvent'
BloC Observer onChange: Change { currentState: 5, nextState: 6 }
Current: 6, Next: 5, Event: Instance of 'DecrementEvent'
BloC Observer onChange: Change { currentState: 6, nextState: 5 }
BloC Observer onClose: 5
*/
- 實現(xiàn)一個簡單狀態(tài)管理類(SimpleBLocProvider)
需要放置在組件樹中,因此肯定是Widget。由于內(nèi)部還需要維護(hù)數(shù)據(jù),因此繼承自StatefulWidget。
需要一個builder屬性用來存放原先UI組件的構(gòu)建,且builder需要攜帶最新的state狀態(tài)數(shù)據(jù)用來更新UI組件。
還需要一個Bloc邏輯組件來獲取最新的狀態(tài)數(shù)據(jù)。
定義如下:
class SimpleBlocProvider<T> extends StatefulWidget {
// typedef StateBuilder<T> = Widget Function(T state);
final StateBuilder<T> builder;
final BlocBase<T> bloc;
const SimpleBlocProvider(
{Key? key, required this.builder, required this.bloc})
: super(key: key);
@override
_SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}
例:
SimpleBlocProvider<int> (builder: (count) => Text('$count'),)
要實現(xiàn)BLoC刷新,需要監(jiān)聽BLoC狀態(tài)數(shù)據(jù)的變化。BLoC 是基于Stream實現(xiàn)的,可以使用Stream的listen方法來監(jiān)聽Stream流數(shù)據(jù)的變化。
listen方法定義如下:
// 可以在 listen 的 onData 中調(diào)用 setState 刷新界面。組件銷毀時需要取消監(jiān)聽,在_SimpleBlocProviderState 中定義一個屬性_streamSubscription存儲 listen 方法的返回值,并在 dispose 中取消監(jiān)聽。
StreamSubscription<T> listen(void onData(T event)?, {Function? onError, void onDone()?, bool? cancelOnError});
例:
_streamSubscription = widget.bloc.stream.listen((data) {
setState(() {
_state = data;
});
});
@override
Widget build(BuildContext context) {
return widget.builder(_state);
}
@override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
完整代碼如下
typedef StateBuilder<T> = Widget Function(T state);
class SimpleBlocProvider<T> extends StatefulWidget {
final StateBuilder<T> builder;
final BlocBase<T> bloc;
const SimpleBlocProvider(
{Key? key, required this.builder, required this.bloc})
: super(key: key);
@override
_SimpleBlocProviderState<T> createState() => _SimpleBlocProviderState<T>();
}
class _SimpleBlocProviderState<T> extends State<SimpleBlocProvider<T>> {
late T _state;
late StreamSubscription<T> _streamSubscription;
@override
void initState() {
_state = widget.bloc.state;
super.initState();
_streamSubscription = widget.bloc.stream.listen((data) {
setState(() {
_state = data;
});
});
}
@override
Widget build(BuildContext context) {
return widget.builder(_state);
}
@override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
}
==================
使用
class CounterCubit extends Cubit<int> {
CounterCubit({initial = 0}) : super(initial);
void increment() => emit(state + 1);
void decrement() => emit(state - 1);
@override
void onChange(Change<int> change) {
super.onChange(change);
}
}
class SimpleBlocCounterPage extends StatelessWidget {
final counter = CounterCubit();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bloc 計數(shù)器'),
),
body: Center(
child: SimpleBlocProvider<int>(
builder: (count) => Text(
'$count',
style: TextStyle(
fontSize: 32,
color: Colors.blue,
),
),
bloc: counter,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: '點擊增加',
child: Icon(Icons.add),
),
);
}
}
===========================
使用2(Bloc)
class Person {
final String name;
final String gender;
const Person({required this.name, required this.gender});
}
abstract class PersonEvent {}
class UsingCnNameEvent extends PersonEvent {}
class UsingEnNameEvent extends PersonEvent {}
class PersonBloc extends Bloc<PersonEvent, Person> {
PersonBloc(Person person) : super(person) {
on<UsingCnNameEvent>(
(event, emit) => emit(Person(name: '狗', gender: '雄性')));
on<UsingEnNameEvent>(
(event, emit) => emit(Person(name: 'Dog', gender: 'male')));
}
}
class SimpleBlocCounterPage extends StatelessWidget {
final personBloc = PersonBloc(Person(name: '狗', gender: '雄性'));
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bloc 事件'),
),
body: Center(
child: SimpleBlocProvider<Person>(
builder: (person) => Text(
'姓名:${person.name},性別:${person.gender}',
style: TextStyle(
fontSize: 22,
color: Colors.blue,
),
),
bloc: personBloc,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
personBloc.add(UsingEnNameEvent());
},
tooltip: '點擊增加',
child: Icon(Icons.add),
),
);
}
}
- BlocProvider
和Provider用法類似,作者為同一人。從Provider遷移到Bloc會很簡單。
// 共享狀態(tài)
final counter = CounterCubit();
BlocProvider.value(
value: counter,
child: SomeWidget(),
);
// 監(jiān)聽狀態(tài)的部分變化
final isPositive = context.select((CounterBloc b) => b.state >= 0);
// 多狀態(tài)
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)
和Provider的區(qū)別
BlocProvider:通過Stream.listen方法監(jiān)聽狀態(tài)數(shù)據(jù)改變來更新UI。
Provider:狀態(tài)對象實現(xiàn)ChangeNotifier接口,并在狀態(tài)數(shù)據(jù)改變時調(diào)用notifyListeners來通知更新UI。
示例
class BlocCounterWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => CounterCubit(),
child: BlocCounterPage(),
);
}
}
class BlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Bloc 計數(shù)器'),
),
body: Center(
child: BlocBuilder<CounterCubit, int>(
builder: (context, count) => Text(
'$count',
style: TextStyle(
fontSize: 32,
color: Colors.blue,
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<CounterCubit>().increment();
},
tooltip: '點擊增加',
child: Icon(Icons.add),
),
);
}
}
- BlocBuilder
// BlocBuilder既可以配合BlocProvider在組件樹中使用Bloc對象,也可以單獨擁有自己的Bloc對象。
class BlocBuilder<B extends BlocBase<S>, S> extends BlocBuilderBase<B, S> {
const BlocBuilder({
Key? key,
required this.builder,
B? bloc,
// 按條件刷新。返回bool值的回調(diào)方法,根據(jù)前后狀態(tài)來決定是否要刷新界面。
// typedef BlocBuilderCondition<S> = bool Function(S previous, S current);
BlocBuilderCondition<S>? buildWhen,
}) : super(key: key, bloc: bloc, buildWhen: buildWhen);
final BlocWidgetBuilder<S> builder;
@override
Widget build(BuildContext context, S state) => builder(context, state);
}
綁定狀態(tài)對象有兩種方式:
1. 沒有指定bloc參數(shù)時,它會通過BlocProvider和context自動向上尋找匹配的狀態(tài)對象。這部分代碼在其父類BlocBuilderBase(StatefulWidget類型)的State對象中實現(xiàn),實際上使用的還是context.read來完成的:
@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_state = _bloc.state;
}
2. 指定了bloc參數(shù)時,則使用指定的bloc對象而無需BlocProvider提供。用法類似GetX的GetBuilder。
示例 (BlocBuilder 按條件刷新)
abstract class PersonalEvent {}
// 獲取數(shù)據(jù)事件
class FetchEvent extends PersonalEvent {}
// 成功事件
class FetchSucessEvent extends PersonalEvent {}
// 失敗事件
class FetchFailedEvent extends PersonalEvent {}
enum LoadingStatus {
loading, //加載
success, //加載成功
failed, //加載失敗
}
class PersonalResponse {
PersonalEntity? personalProfile;
LoadingStatus status = LoadingStatus.loading;
PersonalResponse({this.personalProfile, required this.status});
}
class PersonalBloc extends Bloc<PersonalEvent, PersonalResponse> {
final String userId;
PersonalEntity? _personalProfile;
PersonalBloc(PersonalResponse initial, {required this.userId})
: super(initial) {
on<FetchEvent>((event, emit) { // 請求網(wǎng)絡(luò)數(shù)據(jù)
getPersonalProfile(userId);
});
on<FetchSucessEvent>((event, emit) { // 加載成功后,用請求得到的個人信息對象和加載狀態(tài)構(gòu)建新的 PersonalResponse 對象,使用 emit 通知界面刷新。
emit(PersonalResponse(
personalProfile: _personalProfile,
status: LoadingStatus.success,
));
});
on<FetchFailedEvent>((event, emit) { // 加載失敗,置空PersonalResponse的個人信息對象,并且標(biāo)記加載狀態(tài)為失敗。
emit(PersonalResponse(
personalProfile: null,
status: LoadingStatus.failed,
));
});
on<RefreshEvent>((event, emit) {
getPersonalProfile(userId);
});
// 請求數(shù)據(jù)
add(FetchEvent());
}
void getPersonalProfile(String userId) async {
_personalProfile = await JuejinService().getPersonalProfile(userId);
if (_personalProfile != null) {
add(FetchSucessEvent());
} else {
add(FetchFailedEvent());
}
}
}
class PersonalHomePage extends StatelessWidget {
PersonalHomePage({Key? key}) : super(key: key);
final personalBloc = PersonalBloc(
PersonalResponse(
personalProfile: null,
status: LoadingStatus.loading,
),
userId: '70787819648695');
@override
Widget build(BuildContext context) {
return BlocBuilder<PersonalBloc, PersonalResponse>(
bloc: personalBloc,
builder: (_, personalResponse) {
print('build PersonalHomePage');
if (personalResponse.status == LoadingStatus.loading) {
return Center(
child: Text('加載中...'),
);
}
if (personalResponse.status == LoadingStatus.failed) {
return Center(
child: Text('請求失敗'),
);
}
PersonalEntity personalProfile = personalResponse.personalProfile!;
return Stack(
children: [
CustomScrollView(
slivers: [
_getBannerWithAvatar(context, personalProfile),
_getPersonalProfile(personalProfile),
_getPersonalStatistic(personalProfile),
],
),
Positioned(
top: 40,
right: 10,
child: IconButton(
onPressed: () {
personalBloc.add(FetchEvent());
},
icon: Icon(
Icons.refresh,
color: Colors.white,
),
),
),
],
);
},
buildWhen: (previous, next) { // 按條件刷新
if (previous.personalProfile == null || next.personalProfile == null) {
return true;
}
return previous.personalProfile!.userId != next.personalProfile!.userId;
},
);
}
// 其他代碼略
}
- BlocListener 狀態(tài)監(jiān)聽組件
當(dāng)狀態(tài)改變后會調(diào)用listener參數(shù)給定的回調(diào)函數(shù)(沒有返回值),做一些后置處理(顯示彈窗提醒或確認(rèn)、顯示狀態(tài)信息、后置攔截器效果、數(shù)據(jù)上傳、離線存儲)。
示例(App退出登錄前的二次確認(rèn))
// 登錄狀態(tài)(已登陸、已退出、退出確認(rèn))
enum LoginStatus { logon, logout, logoutConfirm }
class LoginCubit extends Cubit<LoginStatus> {
LoginCubit({initial = LoginStatus.logout}) : super(initial);
void login() => emit(LoginStatus.logon);
void logout() => emit(LoginStatus.logout);
void logoutConfirm() => emit(LoginStatus.logoutConfirm);
}
按鈕和BlocListener都需要使用狀態(tài)數(shù)據(jù),因此使用BlocProvider放置在上層為BlocListener和 BlocBuilder同時提供狀態(tài)數(shù)據(jù)。
class BlocListenerWrapper extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => LoginCubit(),
child: BlocListenerDemo(),
);
}
}
BlocListener部分代碼如下:
class BlocListenerDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BlocListener 示例'),
),
body: Center(
child: BlocListener<LoginCubit, LoginStatus>(
listener: (context, loginSatus) async {
if (loginSatus == LoginStatus.logout ||
loginSatus == LoginStatus.logon) {
ScaffoldMessenger.of(context)
..hideCurrentSnackBar()
..showSnackBar(SnackBar(
content:
Text(loginSatus == LoginStatus.logout ? '已退出登錄' : '登錄成功'),
duration: Duration(seconds: 1),
));
} else {
var confirmed = await _confirmLogout(context);
if (confirmed == true) {
context.read<LoginCubit>().logout();
}
}
},
child: BlocBuilder<LoginCubit, LoginStatus>(
builder: (context, loginSatus) => TextButton(
child: Text(
loginSatus == LoginStatus.logon ? '退出登錄' : '登錄',
style: TextStyle(
fontSize: 24.0,
),
),
onPressed: () {
if (loginSatus == LoginStatus.logon) {
context.read<LoginCubit>().logoutConfirm();
} else {
context.read<LoginCubit>().login();
}
},
),
),
),
),
);
}
- BlocConsumer(組合BlocBuilder和BlocListener)
BlocBuilder構(gòu)建的頁面按說不應(yīng)該是 BlocListener 的子組件,而是同級結(jié)構(gòu),可使用BlocConsumer來解決。
// 支持構(gòu)建響應(yīng)式組件的同時,監(jiān)聽狀態(tài)變化。
// 支持按條件調(diào)用builder刷新組件、按條件調(diào)用listener狀態(tài)監(jiān)聽回調(diào)。
const BlocConsumer({
Key? key,
required this.builder, //
required this.listener, // 狀態(tài)改變后會調(diào)用
this.bloc, // 會自動從當(dāng)前的BuildContext 中查找對應(yīng)類型的狀態(tài)對象
this.buildWhen, // 接收前后的狀態(tài)對象,返回 bool 值,若為 true 才會刷新組件
this.listenWhen, // 接收前后的狀態(tài)對象,返回 bool 值,若為 true 才會調(diào)用listener回調(diào)
}) : super(key: key);
看一下BlocConsumer的build方法:
// 基于BlocBuilder實現(xiàn)的,在BlocBuilder的builderWhen中根據(jù)listenWhen的返回值來決定是否調(diào)用listener回調(diào)方法,從而實現(xiàn)了BlocBuilder和BlocListener的聚合。
@override
Widget build(BuildContext context) {
if (widget.bloc == null) context.select<B, int>(identityHashCode);
return BlocBuilder<B, S>(
bloc: _bloc,
builder: widget.builder,
buildWhen: (previous, current) {
if (widget.listenWhen?.call(previous, current) ?? true) {
widget.listener(context, current);
}
return widget.buildWhen?.call(previous, current) ?? true;
},
);
}
- RepositoryProvider(共享狀態(tài))
父子組件傳值的方式:
1. 構(gòu)造函數(shù)傳值
父組件將子組件需要的對象通過構(gòu)造函數(shù)傳遞給子組件;
如果組件嵌套很深,傳遞數(shù)據(jù)對象需要層層傳遞,將導(dǎo)致代碼很難維護(hù)。
2. 單例對象
構(gòu)建單例對象,使得父子組件使用的是同一個對象;
需要自己構(gòu)建單例類,而實際上要傳遞的對象可能存在很多個實例。
3. 容器
將對象存入容器中,父子組件使用的時候直接從容器中獲取。
如果往容器存儲不定數(shù)量的實例對象是不合適的。
flutter_bloc插件提供了一種基于組件的依賴注入方式解決這類問題,通過使用 RepositoryProvider,可以為組件樹的子組件提供共享對象,這個共享對象只限在組件樹中使用,可以通過Provider的方式訪問該對象。
RepositoryProvider實際上是Provider的一個子類,通過注冊單例的方式實現(xiàn)組件樹對象共享,因此其注冊的對象會隨著Provider的注銷而銷毀,而且這個對象無需是Bloc子類。因此在無法使用Bloc傳輸共享對象時,可以使用RepositoryProvider來完成。
看一下RepositoryProvider類的實現(xiàn):
class RepositoryProvider<T> extends Provider<T>
with RepositoryProviderSingleChildWidget {
RepositoryProvider({
Key? key,
required Create<T> create,
Widget? child,
bool? lazy,
}) : super(
key: key,
create: create,
dispose: (_, __) {},
child: child,
lazy: lazy,
);
RepositoryProvider.value({
Key? key,
required T value,
Widget? child,
}) : super.value(
key: key,
value: value,
child: child,
);
static T of<T>(BuildContext context, {bool listen = false}) {
try {
// RepositoryProvider相比Provider只是將靜態(tài)方法of的listen參數(shù)默認(rèn)設(shè)置為false了,也就是不監(jiān)聽狀態(tài)對象的變化。
return Provider.of<T>(context, listen: listen);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
'''
RepositoryProvider.of() called with a context that does not contain a repository of type $T.
No ancestor could be found starting from the context that was passed to RepositoryProvider.of<$T>().
This can happen if the context you used comes from a widget above the RepositoryProvider.
The context used was: $context
''',
);
}
}
}
再看一下RepositoryProviderSingleChildWidget:
// 一個空的Mixin,僅為了方便MultiRepositoryProvider推斷RepositoryProvider的類型。
mixin RepositoryProviderSingleChildWidget on SingleChildWidget {}
從上面可以知道,創(chuàng)建對象共享有2種方式:
1. create方式(通過調(diào)用一個方法創(chuàng)建新的對象)。
2. value方式(共享一個已有的對象)。
從子組件中訪問共享對象(2種方式)
1. 方式1
context.read<T>()
2. 方式2
RepositoryProvider.of<T>(context)
如果有多個對象需要共享,可以使用MultiRepositoryProvider
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
),
],
child: ChildA(),
)
實際上 RepositoryProvider 借用Provider 實現(xiàn)了一個組件樹上的局部共享對象容器。通過這個容器,為RepositoryProvider的子組件樹注入了共享對象,使得子組件可以從 context 中或使用RepositoryProvider.of 靜態(tài)方法獲取共享對象。通過這種方式避免了組件樹的層層傳值,使得代碼更為簡潔和易于維護(hù)。
RepositoryProvider.value(
child: CustomScrollView(
slivers: [
const BannerWithAvatar(),
const PersonalProfile(),
const PersonalStatistic(),
],
),
value: personalProfile,
),
使用context.read<PersonalEntity>()就可以從 RepositoryProvider 中取出personalProfile對象了
2. GetIt 狀態(tài)管理(優(yōu)點:不需要BuildContext)
最初的設(shè)計是用于完成依賴注入DI和IOC容器的功能,有點類似JavaSpring的Bean容器。
GetIt容器(全局Map對象):可先往里存入對象,需要時直接取出。容器中的對象是全局的,可用來做數(shù)據(jù)同步。
注冊(存入)
// 存入的對象一般是單例
GetIt.instance.registerSingleton<T>(T object);
// 懶加載方式注冊
GetIt.instance.registerLazySingleton<T>(FactoryFunc<T> func)
獲取
// 獲取容器中的對象
GetIt.instance.get<T>();
依賴庫
1. git_it
// 對Flutter的最低SDK版本有要求(如版本7.1.2,需要SDK2.12.x以上版本)
基礎(chǔ)的服務(wù)管理工具,提供了容器幫助代碼找到對應(yīng)的服務(wù)提供對象
2. git_it_mixin
GetIt 的擴展,使得GetIt可以完全應(yīng)用于狀態(tài)管理
3. git_it_hooks
GetIt 的擴展,用于flutter_hooks的場景
示例
// dynamic_listener.dart文件
import 'package:home_framework/models/dynamic_entity.dart';
abstract class DynamicListener { // 抽離邏輯需求
// 更新新聞后
void dynamicUpdated(String id, DynamicEntity updatedDynamic);
// 添加新聞后
void dynamicAdded(DynamicEntity newDynamic);
}
// dynamic_page.dart文件
class _DynamicPageState extends State<DynamicPage> implements DynamicListener {
@override
void initState() {
super.initState();
// 注冊到GetIt容器。在其他頁面可獲取_DynamicPageState對象后調(diào)用更新或添加方法來更新本頁面。
GetIt.instance.registerSingleton<DynamicListener>(this);
}
void dynamicUpdated(String id, DynamicEntity updatedDynamic) { // 更新新聞的具體實現(xiàn)
int index = _listItems.indexWhere((element) => element.id == id);
if (index != -1) {
setState(() {
_listItems[index] = updatedDynamic;
});
}
}
void dynamicAdded(DynamicEntity newDynamic) { // 添加新聞的具體實現(xiàn)
setState(() {
_listItems.insert(0, newDynamic);
});
}
}
// 新增頁面
var response = await DynamicService.post(newFormData);
if (response.statusCode == 200) {
Dialogs.showInfo(context, '添加成功');
GetIt.instance
.get<DynamicListener>()
.dynamicAdded(DynamicEntity.fromJson(response.data));
Navigator.of(context).pop();
}
// 編輯頁面
if (response.statusCode == 200) {
Dialogs.showInfo(context, '保存成功');
// 處理成功更新后的業(yè)務(wù)
_handleUpdated(newFormData);
Navigator.of(context).pop();
}
// 處理更新,如果圖片更新了才更新動態(tài)圖片內(nèi)容
void _handleUpdated(Map<String, String> newFormData) {
_dynamicEntity.title = newFormData['title'];
_dynamicEntity.content = newFormData['content'];
if (newFormData.containsKey('imageUrl')) {
_dynamicEntity.imageUrl = newFormData['imageUrl'];
}
GetIt.instance.get<DynamicListener>().dynamicUpdated(
_dynamicEntity.id,
_dynamicEntity,
);
}
// 詳情頁面
void _updateViewCount() async {
try {
var response = await DynamicService.updateViewCount(_dynamicEntity.id);
if (response.statusCode == 200) {
setState(() {
_dynamicEntity.viewCount = response.data['viewCount'];
GetIt.instance.get<DynamicListener>().dynamicUpdated(
_dynamicEntity.id,
_dynamicEntity,
);
});
}
} catch (e) {
print(e.toString());
}
}