Как использовать функцию `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
- для отображения значений и выборочной пересборкой виджета при изменении конкретного поля класса.