13

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 :

  1. Toggle Loading
  2. Toggle Image Loading
  3. Toggle ObsecurePassword

Using ChangeNotifer

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:

  1. provider_isloading.dart,
  2. provider_isimageloading.dart and
  3. provider_obsecurepassword.dart.

Using StateNotifier

// 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:

GlobalStateNotifer.dart

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?

My Folder's Structure

My Folder's Structure

anticafe
  • 6,816
  • 9
  • 43
  • 74
Zeffry Reynando
  • 3,445
  • 12
  • 49
  • 89

3 Answers3

7

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.

Alex Hartford
  • 5,110
  • 2
  • 19
  • 36
  • and how to call the static method on UI ? i try to `isImageLoading().provider` it's not defined. – Zeffry Reynando Jul 22 '20 at 00:16
  • @ZeffryReynando Well, that's because it isn't! Static methods are defined for the class itself, and can not be called from class instances. `IsImageLoading.provider` is what you're looking for. While this concept is not unique to dart, here is a [short tutorial](https://www.w3adda.com/dart-tutorial/dart-static-keyword) on the static keyword in dart. – Alex Hartford Jul 22 '20 at 15:01
  • your code make sense to me. But there is one thing that I want to confirm , can you analyze my code it's already good practice or not. https://gist.github.com/zgramming/212cd583ac20e4fb8fdd8b7a9150522e . Already follow you suggest to make another class to stored all property in single file. – Zeffry Reynando Jul 24 '20 at 03:41
  • Commented on your gist with my suggestions. – Alex Hartford Jul 24 '20 at 15:46
  • your suggest code it's not work , i must backforward to old code – Zeffry Reynando Jul 25 '20 at 02:27
0

I think you should use immutable classes like following code

@immutable
abstract class GlobalState {
  const GlobalState();
}

class IsImageLoading extends GlobalState {
  const IsImageLoading();
}

class IsLoading extends GlobalState {
  const IsLoading();
}

class IsObsecurePassword extends GlobalState {
  const IsObsecurePassword();
}

and your StateNotifier will be like following

class GlobalStateNotifier extends StateNotifier<GlobalState> {
  GlobalStateNotifier(GlobalState state) : super(state);

  void changeState(GlobalState newState) {
    state = newState;
  }
}
Mohamed Kamel
  • 841
  • 10
  • 15
-1

very late response, but I'm search the same things now and I don't find a correct way to use riverpod or StateNotifier, but probably you should use like you use ChangeNotifier, or for flutter_bloc users they should to use like flutter_bloc logic... or both.

For this case and from what I've read, I would use only one dart file to put all that code, like your changenotifier class, and make simple stateproviders for IsImageLoading, isLoading, isObscurePassword

In another MyViewProvider class you can change the state of those statesProvider, or don't create this and change the state directly in the view.


final isImageLoadingProvider = StateProvider((ref) => false);

final isLoadingProvider = StateProvider((ref) => false);

final isObscurePasswordProvider = StateProvider((ref) => false);

final myViewProvider = Provider<MyViewProvider>((ref) {
  return MyViewProvider(ref.read);
});

class MyViewProvider {
  MyViewProvider(this._read);

  final Reader _read;

  void setLoading(bool value) {
    _read(isLoadingProvider).state = value;
  }

  void setImageLoading(bool value) {
    _read(isImageLoadingProvider).state = value;
  }

  void setObscurePassword() {
    final isObscure = _read(isLoadingProvider).state;
    _read(isObscurePasswordProvider).state = !isObscure;
  }
}

I would appreciate any comments or improvements

Hector Aguero
  • 369
  • 3
  • 8