Если говорить совсем просто, базировано и понятно, то любой виджет во Flutter имеет свой уникальный идентификатор, которым подписываются все действия, исполняемые в нём. Provider присутствует и работает постоянно, но обращаться к нем мы можем, если место использования и место прослушивания имеют некоторый общий родительский виджет Provider. По умолчанию Provider позволяет прослушивать один объект, в котором могут быть как функции, так и переменные, таким образом ChangeNotifier уведомляет о любых изменениях объекта, в то время как NotifierValue прослушивает ровно одну переменную, и уведомляет всех слушателей при изменении ТОЛЬКО этой переменной, это сильно упрощает взаимодействие сложных сценариев приложения.
Перейдём от разговоров, к применению , рассмотрим на примере настроек пользователя:
class UserProfileProvider with ChangeNotifier {
String _username;
String _email;
bool _isDarkMode;
UserProfileProvider(this._username, this._email, this._isDarkMode);
String get username => _username;
String get email => _email;
bool get isDarkMode => _isDarkMode;
void setUsername(String username) {
_username = username;
notifyListeners();
}
void setEmail(String email) {
_email = email;
notifyListeners();
}
void toggleTheme() {
_isDarkMode = !_isDarkMode;
notifyListeners();
}
}
Это пример кода, использующего ChangeNotifier, он прослушивает все поля - _username, _email, _isDarkMode
class DoNotDisturbController {
ValueNotifier<bool> isDoNotDisturbEnabled = ValueNotifier(false);
void toggleDoNotDisturb() {
isDoNotDisturbEnabled.value = !isDoNotDisturbEnabled.value;
}
}
Допустим есть функция "не беспокоить", влияющая на всё приложение целиком.
Код в котором мы будем это использовать:
class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var userProfileProvider = Provider.of<UserProfileProvider>(context);
var doNotDisturbController = Provider.of<DoNotDisturbController>(context);
return Column(
children: [
// Для ChangeNotifier
Text(userProfileProvider.username),
Switch(
value: userProfileProvider.isDarkMode,
onChanged: (value) {
userProfileProvider.toggleTheme();
},
),
// Для ValueNotifier
ValueListenableBuilder<bool>(
valueListenable: doNotDisturbController.isDoNotDisturbEnabled,
builder: (context, value, child) {
return Switch(
value: value,
onChanged: (newVal) {
doNotDisturbController.toggleDoNotDisturb();
},
);
},
),
],
);
}
}
Тут мы можем увидеть, что при изменении ChangeNotifier обновляются сразу все поля и состояния, которые прослушивают КЛАСС, использующий провайдер. В случае с ValueNotifier мы прослушиваем только одну переменную/функцию, что полезно для скорости и простоты использования, НО ограничивает возможности отзывчивости приложения, т.к. мы не обновим целый объект. ChangeNotifier полезен для использования в разветвлении виджетов, применения изменения ряда параметров и работы с объектами в целом, ValueListener полезен для изменения одного конкретного параметра для всего кода. Часто можно избежать применения ValueListener через изменение состояния, т.к. и Provider и setState влияют на данные в BuildContext, но если первый протягивает изменение до слушателя, и меняет состояние из него, то второй применяет изменения на месте.
Таким образом, ChangeNotifier и ValueNotifier обеспечивают разные уровни абстракции и удобства для различных сценариев управления состоянием в Flutter, помогая вам поддерживать чистую и управляемую архитектуру состояния.