I'm quite familiar with Provider package and combine it with the ChangeNotifier
.
Let's say I have 3 getters and method with a different function :
import 'package:flutter/foundation.dart';
class GlobalChangeNotifier extends ChangeNotifier {
bool _isLoading = false;
bool _isImageLoading = false;
bool _isObsecurePassword = false;
bool get isLoading => _isLoading;
bool get isImageLoading => _isImageLoading;
bool get isObsecurePassword => _isObsecurePassword;
void setLoading(bool value) {
_isLoading = value;
notifyListeners();
}
void setImageLoading(bool value) {
_isImageLoading = value;
notifyListeners();
}
void setObsecurePassword(bool value) {
_isObsecurePassword = !value;
notifyListeners();
}
}
final globalChangeNotifier = GlobalChangeNotifier();
If I'm using ChangeNotifier
, I only need to create 1 file and just call a method like globalChangeNotifier.METHOD()
or value like globalChangeNotifier.value
.
But now, I've learned about Riverpod package, and in the documentation, it's using StateNotifier
.
I want to migrate my previous code from ChangeNotifier
to StateNotifier
.
But in my understanding, StateNotifier only can hold 1 type data, so if I want to migrate above code I should create 3 files, let's say:
provider_isloading.dart
,provider_isimageloading.dart
andprovider_obsecurepassword.dart
.// provider_isloading.dart
class IsImageLoading extends StateNotifier<bool> {
IsImageLoading() : super(false);
void toggleImageLoading(bool value) {
state = value;
}
}
final isImageLoadingProvider = StateNotifierProvider((ref) => IsImageLoading());
// provider_isimageloading.dart
class IsLoading extends StateNotifier<bool> {
IsLoading() : super(false);
void toggleLoading(bool value) => state = value;
}
final isLoadingProvider = StateNotifierProvider((ref) => IsLoading());
// provider_obsecurepassword.dart
class IsObsecurePassword extends StateNotifier<bool> {
IsObsecurePassword() : super(false);
void toggleObsecurePassword(bool value) {
state = !value;
}
}
final isObsecurePasswordProvider = StateNotifierProvider((ref) => IsObsecurePassword());
And I also need to create 1 file to export all of those files:
export './provider_loading.dart';
export './provider_imageloading.dart';
export './provider_obsecurepassword.dart';
My question is, is it the best practice to make it as I've explained earlier?
You may think of State Notifier as the Flutter independent state management component. State Notifier Provider is another Provider variant in Riverpod That is where Riverpod StateNotifierProvider enters into the scene. Of course, it takes help from StateNotifier to provide the state that to a bottom-most widget that needs the state.
Now StateNotifier can either operate with a single primitive data type, like integer; or, it can handle a very complex user-defined data type. Whatever, the nature of state, simple or complex, State Notifier Provider takes the child of State Notifier on its shoulder and carries the state to any widget that needs it.
But in my understanding, StateNotifier only can hold 1 type data, so if I want to migrate above code I should create 3 files, let's say: provider_obsecurepassword.dart.
ChangeNotifier might be the simplest and least boilerplate state management in flutter but it does have its own limitations. The ability to use immutable states is the one I would say lacks the most, ChangeNotifier also has slower time complexities such as O (N²) for notifylisteners () and O (N) for addListener ().
When using Riverpod, it makes a great deal of sense to create static providers on the class they are providing. From your example, you could refactor to:
class IsImageLoading extends StateNotifier<bool> {
IsImageLoading() : super(false);
static final provider = StateNotifierProvider((ref) => IsImageLoading());
void toggleImageLoading(bool value) {
state = value;
}
}
You should also consider whether or not you need your providers to be available outside of the class you are actually using them. Something tells me you probably won't be using your password provider anywhere but your login page. Consider creating a private provider in that class.
However, if your desire is to keep your current approach, you could create a class, A
, that contains the 3 bool values and a class that extends StateNotifier<A>
.
For example:
enum LoadingType { A, B, C }
class LoadingToggles {
bool A, B, C;
LoadingToggles({this.A = false, this.B = false, this.C = false});
static final provider = StateNotifierProvider.autoDispose((ref) => LoadingState(LoadingToggles()));
}
class LoadingState extends StateNotifier<LoadingToggles> {
LoadingState(LoadingToggles state) : super(state ?? LoadingToggles());
void toggle(LoadingType type) {
switch (type) {
case LoadingType.A:
state.A = !state.A;
break;
case LoadingType.B:
state.B = !state.B;
break;
case LoadingType.C:
state.C = !state.C;
break;
default:
// Handle error state
}
}
}
Finally, just want to add that there is likely a better way to handle loading overall. Consider if you can use a FutureProvider/StreamProvider with Riverpod's AsyncValue instead of manually toggling a loading state.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With