Руководство по использованию поставщика Flutter

Flutter

предисловие

   При написании различных приложений на одном языке первая проблема, с которой сталкиваются разработчики, — как управлять состоянием. Во фронтенде мы привыкли использовать фреймворки или различные вспомогательные библиотеки для управления состоянием. Например, разработчики часто используют собственный контекст React или такие инструменты, как mobx/redux, для управления состоянием между компонентами. В популярном кросс-энд фреймворке флаттер автор представит широко используемый в сообществе фреймворк провайдера.

Готов к работе

установка и знакомство

ссылка на паб провайдера
Официальная документация заявляет (эта статья основана на версии 4.0), провайдер — это гибридный инструмент для внедрения зависимостей и управления состоянием, а компоненты создаются через компоненты.
Провайдер имеет следующие три характеристики:

  1. Ремонтопригодность, провайдер обеспечивает однонаправленный поток данных
  2. Возможность тестирования/компоновки, поставщики могут легко имитировать или реплицировать данные
  3. Надежность, поставщик будет обновлять состояние компонента или модели, когда это необходимо, снижая частоту ошибок.

Добавьте в файл 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Используется для предоставления доступного для прослушивания объекта, провайдер будет прослушивать изменения в объекте, чтобы вовремя обновлять состояние компонента.
  • ChangeNotifierProviderListerableProvider опирается на реализацию 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

Другие важные детали и часто задаваемые вопросы (из официальной документации)

  1. почему в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);
}
  1. я использую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);
  );
}
  1. Чтобы синхронизировать сложное состояние, я должен использовать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),
);
  1. Могу ли я обернуть свой собственный провайдер?
    Могу,providerПользователям предоставляется множество подробных API-интерфейсов для инкапсуляции своих собственных поставщиков, в том числе:SingleChildCloneableWidget,InheritedProvider,DelegateWidget,BuilderDelegate,ValueDelegateЖдать
  2. Мои компоненты перестраиваются слишком часто, почему?
    можно использовать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');
  }
);

В этом примере компонент будет выполнять повторную визуализацию только при изменении длины списка и не будет запускать повторную визуализацию при изменении его внутренних элементов.

  1. Можно ли использовать двух разных провайдеров для получения значений одного типа?
    Нет, даже если вы определяете один и тот же тип для нескольких поставщиков, компонент может получить значение поставщика только в ближайшем родительском компоненте.