предисловие
При написании различных приложений на одном языке первая проблема, с которой сталкиваются разработчики, — как управлять состоянием. Во фронтенде мы привыкли использовать фреймворки или различные вспомогательные библиотеки для управления состоянием. Например, разработчики часто используют собственный контекст React или такие инструменты, как mobx/redux, для управления состоянием между компонентами. В популярном кросс-энд фреймворке флаттер автор представит широко используемый в сообществе фреймворк провайдера.
Готов к работе
установка и знакомство
ссылка на паб провайдера
Официальная документация заявляет (эта статья основана на версии 4.0), провайдер — это гибридный инструмент для внедрения зависимостей и управления состоянием, а компоненты создаются через компоненты.
Провайдер имеет следующие три характеристики:
- Ремонтопригодность, провайдер обеспечивает однонаправленный поток данных
- Возможность тестирования/компоновки, поставщики могут легко имитировать или реплицировать данные
- Надежность, поставщик будет обновлять состояние компонента или модели, когда это необходимо, снижая частоту ошибок.
Добавьте в файл pubspec.yaml следующее:
dependencies:
provider: ^4.0.0
затем выполните командуflutter pub get
, установите его локально.
При использовании просто добавьте в заголовок файла следующее:
import 'package:provider/provider.dart';
выставить значение
Если мы хотим, чтобы виджет и его дочерние виджеты ссылались на переменную, нам нужно предоставить ее, как правило, следующим образом:
Provider(
create: (_) => new MyModel(),
child: ...
)
прочитать значение
Если вы хотите использовать ранее выставленный объект, вы можете сделать это так:
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
MyModel yourValue = Provider.of<MyModel>(context)
return ...
}
}
Выставляйте и используйте несколько значений (MultiProvider)
Конструктор провайдера может быть вложенным
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
Вышеприведенный код выглядит слишком громоздко и уходит в ад вложенности, благо провайдер дает более элегантную реализацию
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
Прокси-провайдер (ProxyProvider)
После версии 3.0 доступен новый прокси-провайдер,ProxyProvider
Он может интегрировать несколько значений от разных поставщиков в один объект и отправлять его внешнему поставщику.При изменении любого из нескольких зависимых поставщиков этот новый объект будет обновлен. В следующем примере используетсяProxyProvider
построить пример, который опирается на счетчики, предоставленные другими поставщиками
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
Самый простой провайдер, который принимает значение и предоставляет его -
ListenableProvider
Используется для предоставления доступного для прослушивания объекта, провайдер будет прослушивать изменения в объекте, чтобы вовремя обновлять состояние компонента. -
ChangeNotifierProvider
ListerableProvider опирается на реализацию ChangeNotifier, которая будет вызываться автоматически при необходимости.ChangeNotifier.dispose
метод -
ValueListenableProvider
Слушайте значение, которое можно слушать, и только раскрываетValueListenable.value
метод -
StreamProvider
Слушайте поток и выставляйте его последнее отправленное значение -
FutureProvider
принять одинFuture
в качестве параметра в этомFuture
Обновите зависимости, когда закончите
Боевой проект
Далее я буду использовать свой собственный проект, чтобы проиллюстрировать использование провайдера.
Сначала определите базовый класс для выполнения некоторых общих задач, таких как обновления пользовательского интерфейса.
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
Этот метод перехватывает получение и изменение данных и уведомляет состояние синхронизации дерева компонентов, когда происходят соответствующие изменения.
В основном файле используйте провайдер
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));
}
}
Посмотреть полный код проектамой склад
Это имитация приложения WeChat, реализованная мной с использованием флаттера:Приложение для имитации чата WeChat на основе Flutter
Другие важные детали и часто задаваемые вопросы (из официальной документации)
- почему в
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
, все дерево рендеринга все еще находится в процессе создания.
Типичный сценарий использования - в нотификаторе есть http запрос
initState() {
super.initState();
Provider.of<Foo>(context).fetchSomething();
}
Это недопустимо, так как обновления компонентов происходят немедленно.
Другими словами, если некоторые компоненты создаются до асинхронного процесса, а некоторые компоненты создаются после асинхронного процесса, это, вероятно, вызовет несогласованное поведение пользовательского интерфейса в вашем приложении, что недопустимо.
Чтобы решить эту проблему, вам нужно разместить ваш асинхронный процесс в месте, которое может эквивалентно повлиять на дерево компонентов.
- Выполните асинхронный процесс непосредственно в конструкторе вашей модели провайдера.
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
Используется в сочетании для достижения цели обновления состояния и синхронизации пользовательского интерфейса.
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-интерфейсов для инкапсуляции своих собственных поставщиков, в том числе:SingleChildCloneableWidget
,InheritedProvider
,DelegateWidget
,BuilderDelegate
,ValueDelegate
Ждать - Мои компоненты перестраиваются слишком часто, почему?
можно использоватьConsumer/Selector
заменитьProvider.of
.
необязательный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');
}
);
В этом примере компонент будет выполнять повторную визуализацию только при изменении длины списка и не будет запускать повторную визуализацию при изменении его внутренних элементов.
- Можно ли использовать двух разных провайдеров для получения значений одного типа?
Нет, даже если вы определяете один и тот же тип для нескольких поставщиков, компонент может получить значение поставщика только в ближайшем родительском компоненте.