使用一種語言編寫各類應用的時候,橫亙在開發者面前的第一個問題就是如何進行狀態管理。在前端領域,咱們習慣使用框架或者各類輔助庫來進行狀態管理。例如,開發者常用react自帶的context,或者mobx/redux等工具來管理組件間狀態。在大熱的跨端框架flutter中,筆者將對社區中使用普遍的provider框架進行介紹。前端
provider pub連接
官方文檔宣稱(本文基於4.0版本),provider是一個依賴注入和狀態管理的混合工具,經過組件來構建組件。
provider有如下三個特色:react
在pubspec.yaml文件中加入以下內容:git
dependencies:
provider: ^4.0.0
複製代碼
而後執行命令flutter pub get
,安裝到本地。 使用時只需在文件頭部加上以下內容:github
import 'package:provider/provider.dart';
複製代碼
若是咱們想讓某個變量可以被一個widget及其子widget所引用,咱們須要將其暴露出來,典型寫法以下:redux
Provider(
create: (_) => new MyModel(),
child: ...
)
複製代碼
若是要使用先前暴露的對象,能夠這樣操做api
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
MyModel yourValue = Provider.of<MyModel>(context)
return ...
}
}
複製代碼
Provider的構造方法能夠嵌套使用bash
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
複製代碼
上述代碼看起來過於繁瑣,走入了嵌套地獄,好在provider給了更加優雅的實現app
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
複製代碼
在3.0版本以後,有一種新的代理provider可供使用,ProxyProvider
可以將不一樣provider中的多個值整合成一個對象,並將其發送給外層provider,當所依賴的多個provider中的任意一個發生變化時,這個新的對象都會更新。下面的例子使用ProxyProvider
來構建了一個依賴其餘provider提供的計數器的例子框架
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
create: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
複製代碼
能夠經過各類不一樣的provider來應對具體的需求less
Provider
最基礎的provider,它會獲取一個值並將它暴露出來ListenableProvider
用來暴露可監聽的對象,該provider將會監聽對象的改變以便及時更新組件狀態ChangeNotifierProvider
ListerableProvider依託於ChangeNotifier的一個實現,它將會在須要的時候自動調用ChangeNotifier.dispose
方法ValueListenableProvider
監聽一個可被監聽的值,而且只暴露ValueListenable.value
方法StreamProvider
監聽一個流,而且暴露出其最近發送的值FutureProvider
接受一個Future
做爲參數,在這個Future
完成的時候更新依賴接下來筆者將以本身項目來舉例provider的用法
首先定義一個基類,完成一些UI更新等通用工做
import 'package:provider/provider.dart';
class ProfileChangeNotifier extends ChangeNotifier {
Profile get _profile => Global.profile;
@override
void notifyListeners() {
Global.saveProfile(); //保存Profile變動
super.notifyListeners();
}
}
複製代碼
以後定義本身的數據類
class UserModle extends ProfileChangeNotifier {
String get user => _profile.user;
set user(String user) {
_profile.user = user;
notifyListeners();
}
bool get isLogin => _profile.isLogin;
set isLogin(bool value) {
_profile.isLogin = value;
notifyListeners();
}
String get avatar => _profile.avatar;
set avatar(String value) {
_profile.avatar = value;
notifyListeners();
}
複製代碼
這裏經過set
和get
方法劫持對數據的獲取和修改,在有相關改動發生時通知組件樹同步狀態。
在主文件中,使用provider
class MyApp extends StatelessWidget with CommonInterface {
MyApp({Key key, this.info}) : super(key: key);
final info;
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
UserModle newUserModel = new UserModle();
return MultiProvider(
providers: [
// 用戶信息
ListenableProvider<UserModle>.value(value: newUserModel),
],
child: ListenContainer(),
);
}
}
複製代碼
接下來,在全部的子組件中,若是須要使用用戶的名字,只需Provider.of<UserModle>(context).user
便可,可是這樣的寫法看上去不夠精簡,每次調用時都須要寫很長的一段開頭Provider.of<xxx>(context).XXX
非常繁瑣,故而這裏咱們能夠簡單封裝一個抽象類:
abstract class CommonInterface {
String cUser(BuildContext context) {
return Provider.of<UserModle>(context).user;
}
}
複製代碼
在子組件聲明時,使用with
,來簡化代碼
class MyApp extends StatelessWidget with CommonInterface {
......
}
複製代碼
在使用時只需cUser(context)
便可。
class _FriendListState extends State<FriendList> with CommonInterface {
@override
Widget build(BuildContext context) {
return Text(cUser(context));
}
}
複製代碼
項目完整代碼詳見本人倉庫
initState
中獲取Provider會報錯?initState() {
super.initState();
print(Provider.of<Foo>(context).value);
}
複製代碼
要解決這個問題,要麼使用其餘生命週期方法(didChangeDependencies/build)
didChangeDependencies() {
super.didChangeDependencies();
final value = Provider.of<Foo>(context).value;
if (value != this.value) {
this.value = value;
print(value);
}
}
複製代碼
或者指明你不在乎這個值的更新,好比
initState() {
super.initState();
print(Provider.of<Foo>(context, listen: false).value);
}
複製代碼
ChangeNotifier
的過程當中,若是更新變量的值就會報出異常?ChangeNotifier
時,整個渲染樹還處在建立過程當中。initState() {
super.initState();
Provider.of<Foo>(context).fetchSomething();
}
複製代碼
這是不容許的,由於組件的更新是即時生效的。
換句話來講若是某些組件在異步過程以前構建,某些組件在異步過程以後構建,這頗有可能觸發你應用中的UI表現不一致,這是不容許的。
爲了解決這個問題,須要把你的異步過程放在可以等效的影響組件樹的地方
class MyNotifier with ChangeNotifier {
MyNotifier() {
_fetchSomething();
}
Future<void> _fetchSomething() async {}
}
複製代碼
initState() {
super.initState();
Future.microtask(() =>
Provider.of<Foo>(context).fetchSomething(someValue);
);
}
複製代碼
ChangeNotifier
嗎?Provider.value()
和StatefulWidget
結合起來使用,達到即刷新狀態又同步UI的目的.class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
@override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
複製代碼
當須要讀取狀態時:
return Text(Provider.of<int>(context).toString());
複製代碼
當須要改變狀態時:
return FloatingActionButton(
onPressed: Provider.of<ExampleState>(context).increment,
child: Icon(Icons.plus_one),
);
複製代碼
provider
暴露了許多細節api以便使用者封裝本身的provider,它們包括:SingleChildCloneableWidget
、InheritedProvider
、DelegateWidget
、BuilderDelegate
、ValueDelegate
等Provider.of
來替代Consumer/Selector
.child
參數來保證組件樹只會重建某個特定的部分Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
複製代碼
在以上例子中,當A
改變時,只有Bar
會從新渲染,Foo
和Baz
並不會進行沒必要要的重建。
爲了更精細地控制,咱們還可使用Selector
來忽略某些不會影響組件數的改變。
Selector<List, int>(
selector: (_, list) => list.length,
builder: (_, length, __) {
return Text('$length');
}
);
複製代碼
在這個例子中,組件只會在list的長度發生改變時纔會從新渲染,其內部元素改變時並不會觸發重繪。