Как использовать функцию `Provider.of` во Flutter для управления состоянием виджетов?
Как можно использовать функцию Provider.of во Flutter для управления состоянием виджетов?
Ответы (1 шт):
How to use Provider: Context.read, watch and select
Базовое управление состоянием во Flutter
Далее несколько вольный перевод хорошей статьи из источника:
Provider.of<T> возвращает класс типа T. Обычно это класс-наследник ChangeNotifier.
Пример с гипотетическим классом UserProvider.
User:
class User {
final String name;
final int age;
User({required this.name, required this.age});
}
UserProvider:
import 'package:flutter/foundation.dart';
class UserProvider extends ChangeNotifier {
User? _currentUser;
User? get currentUser => _currentUser;
void setCurrentUser(User newUser) {
_currentUser = newUser;
notifyListeners();
}
void removeCurrentUser() {
_currentUser = null;
notifyListeners();
}
}
Это позволяет получить доступ к управляющему состоянием классу. Можно задать текущего
пользователя через setCurrentUser(), затем пересобрать виджет, который слушает это состояние.
Provider.of может принимать параметр listen. Это означает, что он прослушивает изменение, заданное notifyListeners,
и
перестраивает виджет. По умолчанию listen имеет значение true. Вы можете переопределить это и установить его как
false, чтобы виджет не перестраивался без необходимости.
Пример удаления текущего пользователя:
ElevatedButton(
onPressed: () {
Provider.of<UserProvider>(context, listen: false )
.removeCurrentUser();
},
child: Text( 'Удалить текущего пользователя' ),
),
Не нужно вызывать
Provider.of(context, listen: false)внутриbuild.
Context.read
Context.read похож на Provider.of(context, listen: false), но с некоторыми ограничениями.
Поскольку это не приводит к перестройке виджета, вы, скорее всего, будете использовать это для вызова событий, которым не нужно перестраивать текущий виджет.
Предположим, у вас есть некая логика для входа пользователя в систему.
class UserProvider extends ChangeNotifier {
User? _loginUser;
User? get loginUser => _loginUser;
Future< void > loginUser(User newLoginUser) async {
_loginUser = newLoginUser;
await someMethodWithNetwork();
}
}
Вам не захочется перестраивать виджет с loginUser, так как вы в любом случае перенаправите пользователя после
успешного
входа.
ElevatedButton(
onPressed: () {
context.read<UserProvider>().loginUser(newLoginUser);
Navigator.pushReplacementNamed(context, '/' );
},
child: Text( 'Login' ),
),
В документации упоминается, что вы не должны вызывать это в
buildпоскольку это небезопасно. Если забыть об этом можно потратить часы на отладку. Вызывать можно к примеру вinitState().
@override
initState() {
final userProvider = context.read<UserProvider>();
userProvider.loginUser(widget.initialUser);
}
Context.watch
Context.watch аналогично Provider.of(context) перестраивает виджет.
К примеру, есть класс:
import 'package:flutter/foundation.dart';
class TemperatureProvider extends ChangeNotifier {
double _currentTemperature = 17.0;
double get currentTemperature => _currentTemperature;
void updateTemperature(double newTemperature) {
_currentTemperature = newTemperature;
notifyListeners();
}
}
То отображать текущую температуру можно так:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class TemperatureDisplayScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final temperatureProvider = context.watch<TemperatureProvider>();
return Scaffold(
appBar: AppBar(
title: Text('Temperature Display'),
),
body: Center(
child: Column(
children: [
Text('Current Temperature:'),
Text('${temperatureProvider.currentTemperature} °C'),
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
double newTemperature = temperatureProvider.currentTemperature + 1;
temperatureProvider.updateTemperature(newTemperature);
},
),
);
}
}
В отличие от
context.readможно использоватьcontext.watchвнутриbuild. Так как функцияcontext.watchнужна для пересборки виджета, то можно использовать её внутриbuild.
Context.select
Предположим что нужен доступ к значению, которое меняется. Но при этом нет необходимости пересобирать виджет каждый раз когда
изменяется состояние не значимых полей объекта. Функция Context.select позволяет пересобирать виджет при изменении
конкретного поля.
Допустим есть Provider, который содержит различные параметры погоды.
import 'package:flutter/foundation.dart';
class WeatherProvider extends ChangeNotifier {
double _temperature = 10.0;
double _humidity = 11.0;
double _windSpeed = 12.0;
double get temperature => _temperature;
double get humidity => _humidity;
double get windSpeed => _windSpeed;
void updateTemperature(double newTemperature) {
_temperature = newTemperature;
notifyListeners();
}
void updateHumidity(double newHumidity) {
_humidity = newHumidity;
notifyListeners();
}
void updateWindSpeed(double newWindSpeed) {
_windSpeed = newWindSpeed;
notifyListeners();
}
}
Допустим необходимо пересобирать виджет только при обновлении определенного поля класса. Скажем температуры или давления или скорости ветра.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class WeatherDisplayScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// используем context.select
final windSpeed = context.select((WeatherProvider i) => i.windSpeed);
return Scaffold(
appBar: AppBar(
title: Text('Weather Display'),
),
body: Center(
child: Column(
children: [
Text('Current Wind Speed:'),
Text('$windSpeed'),
// this will rebuild the widget
TextButton(
onPressed: () {
context.read<WeatherProvider>().updateWindSpeed(1.5);
},
child: Text('Update Wind Speed'),
),
],
),
),
floatingActionButton: FloatingActionButton(
child: Text('Update Humidity'),
// this wil not rebuild the widget
onPressed: () {
context.read<WeatherProvider>().updateHumidity(1.5);
},
),
);
}
}
Таким образом можно оптимизировать пересборку виджетов и предотвратить ненужные действия.
Context.select может быть вызван несколько раз для каждого нужного состояния.
Итог
context.read- используется для событий, которые не должны пересобирать виджет.context.watch- для отображения значений с пересборкой виджетов.context.select- для отображения значений и выборочной пересборкой виджета при изменении конкретного поля класса.