還記得我們很早之前說過Flutter有一個問題就是嵌套太多層時我們要從下層拿到上層的東西時會變得十分麻煩。
換句話說也就是下層依賴於上層的物件/實例,但什麼時候會發生這種狀況呢?其實在用MobX不知道大家有沒有一個疑問「要怎麼共用store的資料」,畢竟我們不可能在另外一個widget重新實例化這個store 這樣子就只是「兩個同樣類型的store,而不是同一個store」
對於這類問題 Flutter 的提供了 InheritedWidget
。
我們宣告一個 widget 繼承 InheritedWidget
然後宣告data以及child 來放我們的資料以及widget。
class DataWidget extends InheritedWidget {
const DataWidget({
Key? key,
required this.data,
required Widget child,
}) : super(child: child, key: key);
final int data;
static DataWidget? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<DataWidget>();
}
@override
bool updateShouldNotify(DataWidget old) {
return old.data != data;
}
}
// main.dart
// 省略
DataWidget(
data: count,
child: Column(
children: [
_Foo(),
GestureDetector(
child: Text('add 1'),
onTap: () {
setState(() {
count++;
});
},
),
],
),
),
// 省略
class _Foo extends StatefulWidget {
_Foo({Key? key}) : super(key: key);
@override
__FooState createState() => __FooState();
}
class __FooState extends State<_Foo> {
@override
Widget build(BuildContext context) {
return Text(DataWidget.of(context)!.data.toString());
}
}
然後在宣告一個子widget來取得 DataWidget
的資料。這裡會發現我只要在 _Foo
裡面用 DataWidget.of(context)!.data
就好,不必從參數傳入任何資料,而在要顯示_Foo
的地方記得要wrap DataWidget
並且將count
傳入 data
。
從這個例子我們就能看出如果我們要共享一些資料可以不用傳遞參數給子widget 而是能利用InheritedWidget
減少widget與widget之間的耦合關係。但InheritedWidget
也有很明顯的缺點:用起來很麻煩,除了需要實作InheritedWidget
的靜態方法,使用時還要wrap InheritedWidget
。
所以大多數時候都會使用其他套件來解決這件事情。
get_it 是一個實現 service locator pattern 的套件,service locator可以將我們一些實例或者該說服務與我們的widget實作切開來,等我們真正需要使用這些服務時再將其取出。
首先我們先來安裝get_it
dependencies:
get_it: ^7.2.0
宣告 inject
,這裡順便註冊了一個Singleton實例 UsersViewModel
final GetIt inject = GetIt.I;
Future<void> setupInjection() async {
inject.registerLazySingleton<UsersViewModel>(() => UsersViewModel());
}
要到我們的widget使用時,先在 main
裡 setupInjection
void main() {
setupInjection();
runApp(MyApp());
}
然後到我們真正要取出服務的地方
class UserCard extends StatelessWidget {
UserCard({Key? key, required this.userInfo}) : super(key: key);
final Users userInfo;
final usersViewModel = inject<UsersViewModel>();
//省略不重要得 widget宣告
//省略不重要得 widget宣告
//省略不重要得 widget宣告
GestureDetector(
onTap: () {
usersViewModel.fetchSeledtedUser(userInfo.id.toString());
AutoRouter.of(context)
.push(UserDetailRoute(userId: userInfo.id.toString()));
},
//省略不重要得 widget宣告
//省略不重要得 widget宣告
//省略不重要得 widget宣告
}
class UserDetailPage extends StatefulWidget {
const UserDetailPage({Key? key, @PathParam('id') required this.userId})
: super(key: key);
final String userId;
@override
State<UserDetailPage> createState() => _UserDetailPageState();
}
class _UserDetailPageState extends State<UserDetailPage> {
final usersViewModel = inject<UsersViewModel>();
// @override
// void initState() {
// usersViewModel.fetchSeledtedUser(widget.userId);
// super.initState();
// }
// 省略以下
}
在昨天的 UserDetailPage
我們是使用 url param 來達成路由切換時也能拿到正確 userid
,今天我們將 UserCard
切換路由時順便做一個 action 去 fetch 使用者資料。
並在 UserDetailPage
中使用一樣是 inject
來的 usersViewModel
的資料,會發現我們一樣能取得正確的使用者資料。
這樣比較下來就能看出 get_it 比InheritedWidget
使用上方便很多,關於get_it其他用法這篇文章就不另外說明了。
今天的程式碼
https://github.com/zxc469469/flutter_rest_api_playground/tree/Day26
我們之前在說「狀態管理」時提到的一個名詞「依賴注入」(dependency injection ,DI),DI 與service locator都是實作 IoC (Inversion of Control,控制反轉) 的pattern 。
在大部分的解釋上「依賴注入」是指我們利用一些方法將我們的依賴不是在我們要使用的類別中實例化而是從外部注入進來,進而讓各類別解耦。
但在 Flutter 的相關文章或討論時有看過有人是這麼解釋的:
上層的「依賴」「注入」到很深很深的下層
某種程度上也算符合原本定義因為我的確也不是在子層widget去實例化我們的依賴,但可能是因為 Flutter 我們更聚焦在解決底層widget取用上層資料時所遇到的麻煩。
所以會導致很多東西會被誤為是DI 但其實是 service locator ,詳細的討論可以閱讀參考資料中的第三篇。
參考資料