使用 ReorderableList 解決 Bloc 在 ListView 中頻繁的關閉與創建問題
最近在開發 Flutter app 時遇到了一個問題:當 ListView
中,每個 child 都使用 BlocProvider
,例如:
class MyListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocSelector<RootCubit, RootState, List<SomeObj>>(
selector: (state) => state.items,
builder: (_, items) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (_, index) {
return BlocProvider(
create: (_) => MyCubit(),
child: ListTile(
key: ValueKey(items[index]),
title: Text(items[index].title),
),
);
},
);
},
);
}
}
如果列表的順序變更,child cubit 將會頻繁地被關閉與重新創建。此時會導致所有的 child 都不能再 emit state,否則會發生錯誤:
[ERROR: flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: Bad state: Cannot emit new states after calling close
#0 BlocBase.emit (package:bloc/src/bloc_base.dart:97:9)
#1 MyCubit.fetchData.<anonymous closure> (package: myapp/features/hello/presentation/blocs/cubit.dart:203:13)
#2 GreetingRepository.fetchData(package:myapp/features/hello/infrastructure/repositories.dart:167:20)
‹asynchronous suspension>
#3 GreetingRepository.fetch.<anonymous closure (package:myapp/features/hello/infrastructure/repositories.dart:60:9)
<asynchronous suspension>
即便在每個 child 上增加了唯一的 key
,也無法解決這個問題。
解決方案
為了解決這個問題,可以將 ListView
替換為 ReorderableListView
。
ReorderableListView
提供了一個可以自由拖曳調整順序的功能,並且在指定每個 child 的 key
後,它不會隨便重新創建或關閉 cubit。
這樣,就能保持 cubit 狀態的穩定性,不再受到順序變更的影響。
以下是修正後的程式碼:
class MyReorderableListView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocSelector<RootCubit, RootState, List<SomeObj>>(
selector: (state) => state.items,
builder: (ctx, items) {
return ReorderableListView.builder(
onReorder: (oldIndex, newIndex) {
if (newIndex > oldIndex) {
newIndex -= 1;
}
ctx.read<RootCubit>().moveItem(oldIndex, newIndex);
},
itemCount: items.length,
itemBuilder: (_, index) {
return BlocProvider(
key: ValueKey(items[index]), // 保證每個 child 有唯一的 key
create: (_) => MyCubit(),
child: ListTile(
title: Text(items[index].title),
),
);
},
);
},
);
}
}
後記
我不太確定如果今天狀態管理是使用 Provider
或 Riverpod
,是否也會有相同的問題。
因為一般 flutter_bloc
的 state 基本都是 immutable,是否是因為這個原因,讓 ListView
在順序變更時,判定 child 需要重新創建。
但是,如果有的話,這個解決方案也能夠解決。