Flutter api login using riverpod

Viewed 4718

I'm trying to use riverpod for login with a laravel backend. Right now I'm just returning true or false from the repository. I've set a form that accepts email and password. The isLoading variable is just to show a circle indicator. I've run the code and it works but not sure if I'm using riverpod correctly. Is there a better way to do it ?

auth_provider.dart

class Auth{
  final bool isLogin;
  Auth(this.isLogin);
}
class AuthNotifier extends StateNotifier<Auth>{
  AuthNotifier() : super(Auth(false));
  void isLogin(bool data){
    state = new Auth(data);
  }
}
final authProvider = StateNotifierProvider((ref) => new AuthNotifier());

auth_repository.dart

class AuthRepository{
  static String url = "http://10.0.2.2:8000/api/";
  final Dio _dio = Dio();
  Future<bool> login(data) async {
    try {
        Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
        return true;
    } catch (error) {
        return false;
    }
  }
}

login_screen.dart

void login() async{
  if(formKey.currentState.validate()){
    setState((){this.isLoading = true;});
    var data = {
      'email':this.email,
      'password':this.password,
      'device_name':'mobile_phone'
    };
    var result = await AuthRepository().login(data);
    if(result){
        context.read(authProvider).isLogin(true);
        setState((){this.isLoading = false;});
    }else
      setState((){this.isLoading = false;});
  }
}
2 Answers

Since I'm not coming from mobile background and just recently use flutter+riverpod in my recent project, I cannot say this is the best practice. But there are some points I'd like to note:

  • Use interface such IAuthRepository for repository. Riverpod can act as a dependency injection.
final authRepository = Provider<IAuthRepository>((ref) => AuthRepository());
  • Build data to send in repository. You should separate presentation, business logic, and explicit implementation for external resource if possible.
  Future<bool> login(String email, String password) async {
    try {
        var data = {
          'email': email,
          'password': password,
          'device_name':'mobile_phone'
        };
        Response response = await _dio.post(url+'sanctum/token',data:json.encode(data));
        return true;
    } catch (error) {
        return false;
    }
  }
  • Do not call repository directly from presentation/screen. You can use the provider for your logic, which call the repository
class AuthNotifier extends StateNotifier<Auth>{
  final ProviderReference ref;
  IAuthRepository _authRepository;

  AuthNotifier(this.ref) : super(Auth(false)) {
    _authRepository = ref.watch(authRepository);
  }

  Future<void> login(String email, String password) async {
    final loginResult = await_authRepository.login(email, password);
    state = Auth(loginResult);
  }
}

final authProvider = StateNotifierProvider((ref) => new AuthNotifier(ref));

  • On screen, you can call provider's login method
login() {
  context.read(authProvider).login(this.email, this.password);
}
  • Use Consumer or ConsumerWidget to watch the state and decide what to build. It also helps that instead of Auth with isLogin for the state, you can create some other state. At the very least, I usually create an abstract BaseAuthState, which derives to AuthInitialState, AuthLoadingState, AuthLoginState, AuthErrorState, etc.
class AuthNotifier extends StateNotifier<BaseAuthState>{
  ...
  AuthNotifier(this.ref) : super(AuthInitialState()) { ... }
  ...
}
Consumer(builder: (context, watch, child) {
  final state = watch(authProvider.state);
  if (state is AuthLoginState) ...
  else if (state is AuthLoadingState) ...
  ...
})

Instead of using a bool, I like to use enums or class for auth state

enum AuthState { initialize, authenticated, unauthenticated }

and for login state

enum LoginStatus { initialize, loading, success, failed }
Related