[Flutter, Программирование, Разработка мобильных приложений] Flutter.dev: Простое управление состоянием приложения (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет. В сентябре OTUS запускает новый курс «Flutter Mobile Developer». В преддверии старта курса мы традиционно подготовили для вас полезный перевод.
Теперь, когда вы знаете о декларативном программировании пользовательского интерфейса и разнице между эфемерным состоянием и состоянием приложения, вы готовы узнать о простом управлении состоянием приложения.
Мы будем использовать пакет provider. Если вы новичок во Flutter и у вас нет веских причин для выбора другого подхода (Redux, Rx, хуков и т. д.), это, вероятно, самый лучший подход для старта. Пакет provider прост в освоении и не требует большого количества кода. Он также оперирует концепциями, которые применимы во всех других подходах.
Тем не менее, если у вас уже есть большой опыт в управлении состоянием из других реактивных фреймворков, вы можете поискать другие пакеты и руководства, перечисленные на странице опций.
Пример
В качестве примера рассмотрим следующее простое приложение.
В приложении есть два отдельных экрана: каталог и корзина (представленные виджетами MyCatalog и MyCart соответственно). В данном случае это приложение для покупок, но вы можете представить ту же структуру в простом приложении социальной сети (замените каталог на «стену», а корзину на «избранное»).
Экран каталога включает настраиваемую панель приложения (MyAppBar) и прокручиваемое представление множества элементов списка (MyListItems).
Вот это приложение в виде дерева виджетов:
Итак, у нас есть как минимум 5 подклассов Widget. Многим из них нужен доступ к состоянию, которому они не принадлежат. Например, каждый MyListItem должен иметь возможность добавить себя в корзину. Они также могут нуждаться в проверке, находится ли уже отображаемый в настоящее время товар в корзине.
Это подводит нас к нашему первому вопросу: куда мы должны поместить текущее состояние корзины?
Повышение состояния
Во Flutter целесообразно позиционировать состояние над виджетами, которые его используют.
Зачем? В декларативных фреймворках, таких как Flutter, если вы хотите изменить пользовательский интерфейс, вам придется его перестроить. Нельзя просто взять и написать MyCart.updateWith(somethingNew). Другими словами, сложно принудительно изменить виджет извне, вызвав на нем метод. И даже если бы вы могли заставить это работать, вы бы боролись с фреймворком вместо того, чтобы позволить ему помочь вам.
// ПЛОХО: НЕ ДЕЛАЙТЕ ТАК
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
Даже если вы заставите приведенный выше код работать, вам придется иметь дело в виджете MyCart со следующим:
// ПЛОХО: НЕ ДЕЛАЙТЕ ТАК
Widget build(BuildContext context) {
return SomeWidget(
// Изначальное состояние корзины.
);
}
void updateWith(Item item) {
// Каким-то образом отсюда вам нужно изменить UI.
}
Вам нужно будет принять во внимание текущее состояние пользовательского интерфейса и применить к нему новые данные. Здесь будет сложно избежать ошибок.
Во Flutter вы создаете новый виджет каждый раз, когда его содержимое изменяется. Вместо MyCart.updateWith(somethingNew) (вызова метода) вы используете MyCart(contents) (конструктор). Поскольку вы можете создавать новые виджеты только в методах сборки их родителей, если вы хотите изменить contents, он должен находиться в родительском элементе MyCart или выше.
// ХОРОШО
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
Теперь MyCart имеет только один путь выполнения кода для создания любой версии пользовательского интерфейса.
// ХОРОШО
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// Просто создайте пользовательский интерфейс единожды, используя текущее состояние корзины.
// ···
);
}
В нашем примере contents должно находиться в MyApp. Каждый раз, когда он изменяется, он перестраивает MyCart сверху (подробнее об этом позже). Благодаря этому MyCart не нужно беспокоиться о жизненном цикле — он просто объявляет, что нужно показывать для любого заданного contents. Когда он изменится, старый виджет MyCart исчезнет и будет полностью заменен новым.
Это то, что мы имеем в виду, когда говорим, что виджеты неизменяемы (immutable). Они не меняются — их заменяют.
Теперь, когда мы знаем, куда поместить состояние корзины, давайте посмотрим, как получить к ней доступ.
Доступ к состоянию
Когда пользователь нажимает на один из элементов в каталоге, он добавляется в корзину. Но поскольку тележка находится над MyListItem, как нам это сделать?
Простой вариант — предоставить колбек, который MyListItem может вызывать при нажатии. Dart функции являются объектами первого класса, поэтому вы можете передавать их любым способом. Итак, внутри MyCatalog вы можете определить следующее:
@override
Widget build(BuildContext context) {
return SomeWidget(
// Создаем виджет, передавая ему ссылку на метод выше.
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
Это нормально работает, но для состояния приложения, которое вам нужно изменить из множества разных мест, вам придется передавать множество колбеков, что довольно быстро приедается.
К счастью, у Flutter есть механизмы, позволяющие виджетам предоставлять данные и сервисы своим потомкам (другими словами, не только своим потомкам, но и любым нижестоящим виджетам). Как и следовало ожидать от Flutter, где Everything is a Widget, эти механизмы представляют собой просто особые виды виджетов: InheritedWidget, InheritedNotifier, InheritedModel и другие. Мы не будем описывать их здесь, потому что они немного не соответствуют тому, что мы пытаемся сделать.
Вместо этого мы собираемся использовать пакет, который работает с низкоуровневыми виджетами, но прост в использовании. Он называется provider.
С provider вам не нужно беспокоиться о колбеках или InheritedWidgets. Но вам нужно понимать 3 концепции:
- ChangeNotifier
- ChangeNotifierProvider
- Consumer
ChangeNotifier
ChangeNotifier — это простой класс, включенный в Flutter SDK, который предоставляет своим слушателям уведомление об изменении состояния. Другими словами, если что-то является ChangeNotifier, вы можете подписаться на его изменения. (Это некая форма Observable — для тех, кто знаком с этим термином.)
ChangeNotifier в provider это один из способов инкапсулировать состояние приложения. Для очень простых приложений вы можете обойтись одним ChangeNotifier. В более сложных у вас будет несколько моделей и, следовательно, несколько ChangeNotifiers. (Вам вообще не нужно использовать ChangeNotifier с provider, но с этим классом легко работать.)
В нашем примере приложения для покупок мы хотим управлять состоянием корзины в ChangeNotifier. Мы создаем новый класс, который расширяет его, например:
class CartModel extends ChangeNotifier {
/// Внутреннее приватное состояние корзины.
final List<Item> _items = [];
/// Неизменяемое представление товаров в корзине.
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// Текущая общая цена всех предметов (при условии, что все предметы стоят по 42 доллара).
int get totalPrice => _items.length * 42;
/// Добавляет [item] в корзину. Это и [removeAll] - единственные способы изменить корзину снаружи.
void add(Item item) {
_items.add(item);
// Этот вызов сообщает виджетам, которые слушают эту модель, о необходимости перестроения.
notifyListeners();
}
/// Удаляет все товары из корзины.
void removeAll() {
_items.clear();
// Этот вызов сообщает виджетам, которые слушают эту модель, о необходимости перестроения.
notifyListeners();
}
}
Единственный фрагмент кода, специфичный для ChangeNotifier, — это вызов notifyListeners(). Вызывайте этот метод каждый раз, когда модель изменяется таким образом, чтобы это может отразиться в UI вашего приложения. Все остальное в CartModel — это сама модель и ее бизнес-логика.
ChangeNotifier является частью flutter:foundation и не зависит от каких-либо классов более высокого уровня во Flutter. Его легко тестировать (для этого даже не нужно использовать тестирование виджетов ). Например, вот простой модульный тест CartModel:
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
});
cart.add(Item('Dash'));
});
ChangeNotifierProvider
ChangeNotifierProvider — это виджет, который предоставляет экземпляр ChangeNotifier своим потомкам. Он поставляется в пакете provider.
Мы уже знаем, где разместить ChangeNotifierProvider: над виджетами, которым нужен доступ к нему. В случае CartModel это подразумевает что-то выше MyCart и MyCatalog.
Вы не хотите размещать ChangeNotifierProvider выше, чем необходимо (потому что вы не хотите загрязнять область действия). Но в нашем случае единственный виджет, который находится поверх MyCart и MyCatalog — это MyApp.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
),
);
}
Обратите внимание, что мы определяем конструктор, который создает новый экземпляр CartModel. ChangeNotifierProvider достаточно умен, чтобы не перестраивать CartModel без крайней необходимости. Он также автоматически вызывает dispose() в CartModel, когда экземпляр больше не нужен.
Если вы хотите предоставить более одного класса, вы можете использовать MultiProvider:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: MyApp(),
),
);
}
Consumer
Теперь, когда CartModel предоставляется виджетам в нашем приложении через объявление ChangeNotifierProvider вверху, мы можем начать его использовать.
Это делается через виджет Consumer.
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
Мы должны указать тип модели, к которой мы хотим получить доступ. В данном случае нам нужна CartModel, поэтому мы пишем Consumer<CartModel>. Если вы не укажете универсальный (<CartModel>), пакет provider не сможет вам помочь. provider основан на типах, и без типа он не поймет, что вы хотите.
Единственный обязательный аргумент виджета Consumer — это builder. Builder — это функция, которая вызывается при изменении ChangeNotifier. (Другими словами, когда вы вызываете notifyListeners() в своей модели, вызываются все методы builder всех соответствующих виджетов Consumer.)
Конструктор вызывается с тремя аргументами. Первый — context, который вы также получаете в каждом билд методе.
Второй аргумент функции builder — это экземпляр ChangeNotifier. Это то, о чем мы просили с самого начала. Вы можете использовать данные модели, чтобы определить, как пользовательский интерфейс должен выглядеть в любой заданной точке.
Третий аргумент — child, он нужен для оптимизации. Если у вас есть большое поддерево виджетов под вашим Consumer, которое не изменяется при изменении модели, вы можете построить его один раз и получить через builder.
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// Здесь используется SomeExhibitedWidget, без перестраивания каждый раз.
child,
Text("Total price: ${cart.totalPrice}"),
],
),
// Здесь создаем дорогой виджет.
child: SomeExpensiveWidget(),
);
Лучше всего размещать свои виджеты Consumer как можно глубже в дереве. Вы не хотите перестраивать большие части пользовательского интерфейса только потому, что где-то изменились какие-то детали.
// НЕ ДЕЛАЙТЕ ТАК
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
Вместо этого:
// ДЕЛАЙТЕ ТАК
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
Provider.of
Иногда вам не очень нужны данные в модели для изменения пользовательского интерфейса, но вам все равно нужен доступ к ним. Например, кнопка ClearCart позволяет пользователю удалить все из корзины. Нет необходимости отображать содержимое корзины, достаточно вызвать метод clear().
Мы могли бы использовать Consumer<CartModel> для этого, но это было бы расточительно. Мы бы попросили фреймворк перестроить виджет, который не нужно перестраивать.
Для этого варианта использования мы можем использовать Provider.of с параметром listen, установленным на false.
Provider.of<CartModel>(context, listen: false).removeAll();
Использование указанной выше строки в билд методе не приведет к перестройке этого виджета при вызове notifyListeners .
Собираем все вместе
Вы можете ознакомиться с примером, рассмотренным в этой статье. Если вам нужно что-то попроще, посмотрите, как выглядит простое приложение Counter, созданное с помощью provider.
Когда вы будете готовы поиграть с provider самостоятельно, не забудьте сначала добавить его зависимость в свой pubspec.yaml.
name: my_name
description: Blah blah blah.
# ...
dependencies:
flutter:
sdk: flutter
provider: ^3.0.0
dev_dependencies:
# ...
Теперь вы можете 'package:provider/provider.dart'; и запускать построение…
оригинал
===========
Источник:
habr.com
===========
===========
Автор оригинала: flutter.dev
===========Похожие новости:
- [Искусственный интеллект, Машинное обучение] Распознавание мелодии путем изучения языка тела музыканта (перевод)
- [C++, Визуализация данных, Программирование, Учебный процесс в IT] Красиво? Очень! Как мы написали приложение для визуализации аттракторов
- [Искусственный интеллект, Машинное обучение] Развеиваем мифы о Deep Learning – Как учатся нейронные сети? (перевод)
- [DIY или Сделай сам, Программирование, Программирование микроконтроллеров, Разработка робототехники, Робототехника] Разработка hexapod с нуля (часть 9) — завершение версии 1.00
- [Законодательство в IT, Разработка мобильных приложений, Разработка под iOS] В Госдуме пожаловались в ФАС на то, что российские разработчики покупают аккаунты за рубежом для доступа в App Store
- [JavaScript, Программирование, Разработка веб-сайтов] JavaScript: делегирование событий простыми словами (перевод)
- [Разработка веб-сайтов, JavaScript, Программирование, Отладка] Обработка ошибок в JavaScript
- [Разработка мобильных приложений, Разработка для интернета вещей] Fuchsia OS: Возможности и перспективы развития
- [Программирование, C++] std::atomic. Модель памяти C++ в примерах
- [Программирование, Управление персоналом] Вредные советы: как заставить программиста работать лучше
Теги для поиска: #_flutter, #_programmirovanie (Программирование), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_flutter, #_programmirovanie (программирование), #_razrabotka_mobilnyh_prilozhenij (разработка мобильных приложений), #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_flutter, #_programmirovanie (
Программирование
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:17
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет. В сентябре OTUS запускает новый курс «Flutter Mobile Developer». В преддверии старта курса мы традиционно подготовили для вас полезный перевод. Теперь, когда вы знаете о декларативном программировании пользовательского интерфейса и разнице между эфемерным состоянием и состоянием приложения, вы готовы узнать о простом управлении состоянием приложения. Мы будем использовать пакет provider. Если вы новичок во Flutter и у вас нет веских причин для выбора другого подхода (Redux, Rx, хуков и т. д.), это, вероятно, самый лучший подход для старта. Пакет provider прост в освоении и не требует большого количества кода. Он также оперирует концепциями, которые применимы во всех других подходах. Тем не менее, если у вас уже есть большой опыт в управлении состоянием из других реактивных фреймворков, вы можете поискать другие пакеты и руководства, перечисленные на странице опций. Пример В качестве примера рассмотрим следующее простое приложение. В приложении есть два отдельных экрана: каталог и корзина (представленные виджетами MyCatalog и MyCart соответственно). В данном случае это приложение для покупок, но вы можете представить ту же структуру в простом приложении социальной сети (замените каталог на «стену», а корзину на «избранное»). Экран каталога включает настраиваемую панель приложения (MyAppBar) и прокручиваемое представление множества элементов списка (MyListItems). Вот это приложение в виде дерева виджетов: Итак, у нас есть как минимум 5 подклассов Widget. Многим из них нужен доступ к состоянию, которому они не принадлежат. Например, каждый MyListItem должен иметь возможность добавить себя в корзину. Они также могут нуждаться в проверке, находится ли уже отображаемый в настоящее время товар в корзине. Это подводит нас к нашему первому вопросу: куда мы должны поместить текущее состояние корзины? Повышение состояния Во Flutter целесообразно позиционировать состояние над виджетами, которые его используют. Зачем? В декларативных фреймворках, таких как Flutter, если вы хотите изменить пользовательский интерфейс, вам придется его перестроить. Нельзя просто взять и написать MyCart.updateWith(somethingNew). Другими словами, сложно принудительно изменить виджет извне, вызвав на нем метод. И даже если бы вы могли заставить это работать, вы бы боролись с фреймворком вместо того, чтобы позволить ему помочь вам. // ПЛОХО: НЕ ДЕЛАЙТЕ ТАК
void myTapHandler() { var cartWidget = somehowGetMyCartWidget(); cartWidget.updateWith(item); } Даже если вы заставите приведенный выше код работать, вам придется иметь дело в виджете MyCart со следующим: // ПЛОХО: НЕ ДЕЛАЙТЕ ТАК
Widget build(BuildContext context) { return SomeWidget( // Изначальное состояние корзины. ); } void updateWith(Item item) { // Каким-то образом отсюда вам нужно изменить UI. } Вам нужно будет принять во внимание текущее состояние пользовательского интерфейса и применить к нему новые данные. Здесь будет сложно избежать ошибок. Во Flutter вы создаете новый виджет каждый раз, когда его содержимое изменяется. Вместо MyCart.updateWith(somethingNew) (вызова метода) вы используете MyCart(contents) (конструктор). Поскольку вы можете создавать новые виджеты только в методах сборки их родителей, если вы хотите изменить contents, он должен находиться в родительском элементе MyCart или выше. // ХОРОШО
void myTapHandler(BuildContext context) { var cartModel = somehowGetMyCartModel(context); cartModel.add(item); } Теперь MyCart имеет только один путь выполнения кода для создания любой версии пользовательского интерфейса. // ХОРОШО
Widget build(BuildContext context) { var cartModel = somehowGetMyCartModel(context); return SomeWidget( // Просто создайте пользовательский интерфейс единожды, используя текущее состояние корзины. // ··· ); } В нашем примере contents должно находиться в MyApp. Каждый раз, когда он изменяется, он перестраивает MyCart сверху (подробнее об этом позже). Благодаря этому MyCart не нужно беспокоиться о жизненном цикле — он просто объявляет, что нужно показывать для любого заданного contents. Когда он изменится, старый виджет MyCart исчезнет и будет полностью заменен новым. Это то, что мы имеем в виду, когда говорим, что виджеты неизменяемы (immutable). Они не меняются — их заменяют. Теперь, когда мы знаем, куда поместить состояние корзины, давайте посмотрим, как получить к ней доступ. Доступ к состоянию Когда пользователь нажимает на один из элементов в каталоге, он добавляется в корзину. Но поскольку тележка находится над MyListItem, как нам это сделать? Простой вариант — предоставить колбек, который MyListItem может вызывать при нажатии. Dart функции являются объектами первого класса, поэтому вы можете передавать их любым способом. Итак, внутри MyCatalog вы можете определить следующее: @override
Widget build(BuildContext context) { return SomeWidget( // Создаем виджет, передавая ему ссылку на метод выше. MyListItem(myTapCallback), ); } void myTapCallback(Item item) { print('user tapped on $item'); } Это нормально работает, но для состояния приложения, которое вам нужно изменить из множества разных мест, вам придется передавать множество колбеков, что довольно быстро приедается. К счастью, у Flutter есть механизмы, позволяющие виджетам предоставлять данные и сервисы своим потомкам (другими словами, не только своим потомкам, но и любым нижестоящим виджетам). Как и следовало ожидать от Flutter, где Everything is a Widget, эти механизмы представляют собой просто особые виды виджетов: InheritedWidget, InheritedNotifier, InheritedModel и другие. Мы не будем описывать их здесь, потому что они немного не соответствуют тому, что мы пытаемся сделать. Вместо этого мы собираемся использовать пакет, который работает с низкоуровневыми виджетами, но прост в использовании. Он называется provider. С provider вам не нужно беспокоиться о колбеках или InheritedWidgets. Но вам нужно понимать 3 концепции:
ChangeNotifier ChangeNotifier — это простой класс, включенный в Flutter SDK, который предоставляет своим слушателям уведомление об изменении состояния. Другими словами, если что-то является ChangeNotifier, вы можете подписаться на его изменения. (Это некая форма Observable — для тех, кто знаком с этим термином.) ChangeNotifier в provider это один из способов инкапсулировать состояние приложения. Для очень простых приложений вы можете обойтись одним ChangeNotifier. В более сложных у вас будет несколько моделей и, следовательно, несколько ChangeNotifiers. (Вам вообще не нужно использовать ChangeNotifier с provider, но с этим классом легко работать.) В нашем примере приложения для покупок мы хотим управлять состоянием корзины в ChangeNotifier. Мы создаем новый класс, который расширяет его, например: class CartModel extends ChangeNotifier {
/// Внутреннее приватное состояние корзины. final List<Item> _items = []; /// Неизменяемое представление товаров в корзине. UnmodifiableListView<Item> get items => UnmodifiableListView(_items); /// Текущая общая цена всех предметов (при условии, что все предметы стоят по 42 доллара). int get totalPrice => _items.length * 42; /// Добавляет [item] в корзину. Это и [removeAll] - единственные способы изменить корзину снаружи. void add(Item item) { _items.add(item); // Этот вызов сообщает виджетам, которые слушают эту модель, о необходимости перестроения. notifyListeners(); } /// Удаляет все товары из корзины. void removeAll() { _items.clear(); // Этот вызов сообщает виджетам, которые слушают эту модель, о необходимости перестроения. notifyListeners(); } } Единственный фрагмент кода, специфичный для ChangeNotifier, — это вызов notifyListeners(). Вызывайте этот метод каждый раз, когда модель изменяется таким образом, чтобы это может отразиться в UI вашего приложения. Все остальное в CartModel — это сама модель и ее бизнес-логика. ChangeNotifier является частью flutter:foundation и не зависит от каких-либо классов более высокого уровня во Flutter. Его легко тестировать (для этого даже не нужно использовать тестирование виджетов ). Например, вот простой модульный тест CartModel: test('adding item increases total cost', () {
final cart = CartModel(); final startingPrice = cart.totalPrice; cart.addListener(() { expect(cart.totalPrice, greaterThan(startingPrice)); }); cart.add(Item('Dash')); }); ChangeNotifierProvider ChangeNotifierProvider — это виджет, который предоставляет экземпляр ChangeNotifier своим потомкам. Он поставляется в пакете provider. Мы уже знаем, где разместить ChangeNotifierProvider: над виджетами, которым нужен доступ к нему. В случае CartModel это подразумевает что-то выше MyCart и MyCatalog. Вы не хотите размещать ChangeNotifierProvider выше, чем необходимо (потому что вы не хотите загрязнять область действия). Но в нашем случае единственный виджет, который находится поверх MyCart и MyCatalog — это MyApp. void main() {
runApp( ChangeNotifierProvider( create: (context) => CartModel(), child: MyApp(), ), ); } Обратите внимание, что мы определяем конструктор, который создает новый экземпляр CartModel. ChangeNotifierProvider достаточно умен, чтобы не перестраивать CartModel без крайней необходимости. Он также автоматически вызывает dispose() в CartModel, когда экземпляр больше не нужен. Если вы хотите предоставить более одного класса, вы можете использовать MultiProvider: void main() {
runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => CartModel()), Provider(create: (context) => SomeOtherClass()), ], child: MyApp(), ), ); } Consumer Теперь, когда CartModel предоставляется виджетам в нашем приложении через объявление ChangeNotifierProvider вверху, мы можем начать его использовать. Это делается через виджет Consumer. return Consumer<CartModel>(
builder: (context, cart, child) { return Text("Total price: ${cart.totalPrice}"); }, ); Мы должны указать тип модели, к которой мы хотим получить доступ. В данном случае нам нужна CartModel, поэтому мы пишем Consumer<CartModel>. Если вы не укажете универсальный (<CartModel>), пакет provider не сможет вам помочь. provider основан на типах, и без типа он не поймет, что вы хотите. Единственный обязательный аргумент виджета Consumer — это builder. Builder — это функция, которая вызывается при изменении ChangeNotifier. (Другими словами, когда вы вызываете notifyListeners() в своей модели, вызываются все методы builder всех соответствующих виджетов Consumer.) Конструктор вызывается с тремя аргументами. Первый — context, который вы также получаете в каждом билд методе. Второй аргумент функции builder — это экземпляр ChangeNotifier. Это то, о чем мы просили с самого начала. Вы можете использовать данные модели, чтобы определить, как пользовательский интерфейс должен выглядеть в любой заданной точке. Третий аргумент — child, он нужен для оптимизации. Если у вас есть большое поддерево виджетов под вашим Consumer, которое не изменяется при изменении модели, вы можете построить его один раз и получить через builder. return Consumer<CartModel>(
builder: (context, cart, child) => Stack( children: [ // Здесь используется SomeExhibitedWidget, без перестраивания каждый раз. child, Text("Total price: ${cart.totalPrice}"), ], ), // Здесь создаем дорогой виджет. child: SomeExpensiveWidget(), ); Лучше всего размещать свои виджеты Consumer как можно глубже в дереве. Вы не хотите перестраивать большие части пользовательского интерфейса только потому, что где-то изменились какие-то детали. // НЕ ДЕЛАЙТЕ ТАК
return Consumer<CartModel>( builder: (context, cart, child) { return HumongousWidget( // ... child: AnotherMonstrousWidget( // ... child: Text('Total price: ${cart.totalPrice}'), ), ); }, ); Вместо этого: // ДЕЛАЙТЕ ТАК
return HumongousWidget( // ... child: AnotherMonstrousWidget( // ... child: Consumer<CartModel>( builder: (context, cart, child) { return Text('Total price: ${cart.totalPrice}'); }, ), ), ); Provider.of Иногда вам не очень нужны данные в модели для изменения пользовательского интерфейса, но вам все равно нужен доступ к ним. Например, кнопка ClearCart позволяет пользователю удалить все из корзины. Нет необходимости отображать содержимое корзины, достаточно вызвать метод clear(). Мы могли бы использовать Consumer<CartModel> для этого, но это было бы расточительно. Мы бы попросили фреймворк перестроить виджет, который не нужно перестраивать. Для этого варианта использования мы можем использовать Provider.of с параметром listen, установленным на false. Provider.of<CartModel>(context, listen: false).removeAll();
Использование указанной выше строки в билд методе не приведет к перестройке этого виджета при вызове notifyListeners . Собираем все вместе Вы можете ознакомиться с примером, рассмотренным в этой статье. Если вам нужно что-то попроще, посмотрите, как выглядит простое приложение Counter, созданное с помощью provider. Когда вы будете готовы поиграть с provider самостоятельно, не забудьте сначала добавить его зависимость в свой pubspec.yaml. name: my_name
description: Blah blah blah. # ... dependencies: flutter: sdk: flutter provider: ^3.0.0 dev_dependencies: # ... Теперь вы можете 'package:provider/provider.dart'; и запускать построение… оригинал =========== Источник: habr.com =========== =========== Автор оригинала: flutter.dev ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_flutter, #_programmirovanie ( Программирование ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:17
Часовой пояс: UTC + 5