'is this the right way to implement a custom architecture in flutter app using riverpod?

I am creating an App using riverpod, hooks and freezed. My aim is to remove the entire logic out of the widget tree. This article by @ttlg inspired me. In my app, I am trying to implement the following pattern. I am pretty new to riverpod so am i doing something wrong? You have my thanks.

what I am trying to achieve - image

my PasswordInputWidget

     Widget build(BuildContext context, WidgetRef ref) {
   
     final _authState = ref.watch(loginScreenStateProvider);
    
     final _authController = ref.watch(authControllerProvider);
     
     final _textController = useTextEditingController();
      
     bool _isPasswordObscured = _authState.isPasswordObscured;
    
    return TextField(
        controller: _textController,
        obscureText: _isPasswordObscured,
        decoration: InputDecoration(
            label: Text(AppLocalizations.of(context)!.password),
            border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
            suffixIcon: IconButton(
                onPressed: () => _authController.toggleObscurePassword(),
                icon: _isPasswordObscured
                    ? const Icon(Icons.visibility_off_rounded)
                    : const Icon(Icons.visibility_rounded))),
        onChanged: (value) =>
            _authController.inputPassword(textController: _textController));
         }

the logic is kept inside a file named auth_controller.dart as shown below the controller updates the auth_state using user inputs. The controller also handles the data coming from the repository which in turn updates the state.

  abstract class AuthController {
  void inputUserName({required TextEditingController textController});
  void inputPassword({required TextEditingController textController});
  void toggleObscurePassword();
  bool checkUserNameisValid(String userName);
  void login();
}

class AuthControllerImpl implements AuthController {
  final Reader _read;
  AuthControllerImpl(this._read);
  @override
  void inputUserName({required TextEditingController textController}) {
    final state = _read(loginScreenStateProvider);
    bool isValidUserName = checkUserNameisValid(textController.text);
    _read(loginScreenStateProvider.notifier).setLoginScreenState(state.copyWith(
        userName: textController.text, isValidUserName: isValidUserName));
  }

  @override
  void toggleObscurePassword() {
    final state = _read(loginScreenStateProvider);
    _read(loginScreenStateProvider.notifier).setLoginScreenState(
        state.copyWith(isPasswordObscured: !state.isPasswordObscured));
  }

  @override
  void inputPassword({required TextEditingController textController}) {
    final state = _read(loginScreenStateProvider);
    _read(loginScreenStateProvider.notifier)
        .setLoginScreenState(state.copyWith(password: textController.text));
  }

  @override
  bool checkUserNameisValid(String userName) {
    return TextUtils.isValidInput(
        text: userName, exp: AppConstants.EMAIL_VALIDATOR);
  }

  @override
  void login() {
    final state = _read(loginScreenStateProvider);
    final repository = _read(authRepositoryProvider);
    if (state.userName.isNotEmpty &&
        state.isValidUserName &&
        state.password.isNotEmpty) {
      repository.login(userName: state.userName, password: state.password);
    }
  }
}

final authControllerProvider = StateProvider.autoDispose<AuthControllerImpl>(
    (ref) => AuthControllerImpl(ref.read));

The state is kept in a separate file auth_state.dart and is exposed using a getter and setter.

class LoginScreenState extends StateNotifier<LoginScreenModel> {
  LoginScreenState() : super(const LoginScreenModel());
  LoginScreenModel getLoginScreenState() {
    return state;
  }

  void setLoginScreenState(LoginScreenModel newloginScreenState) {
    state = newloginScreenState;
  }
}

final loginScreenStateProvider =
    StateNotifierProvider.autoDispose<LoginScreenState, LoginScreenModel>(
        (ref) => LoginScreenState());

The model of the state is created using freezed as shown below

import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_model.freezed.dart';

@freezed
class LoginScreenModel with _$LoginScreenModel {
  const factory LoginScreenModel(
      {@Default('') String userName,
      @Default('') String password,
      @Default(true) bool isValidUserName,
      @Default(true) bool isPasswordObscured,
      @Default(false) bool showInvalidUserNameError}) = _LoginScreenModel;
}

API calls are handled in the repository as shown below.

//auth repository should be used to write data or fetch data from an api or local storage;
import 'package:dio/dio.dart';
import 'package:glowing/utils/api_utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

abstract class AuthRepository {
  Future<void> login({required String userName, required String password});
  Future<void> forgotPassword();
}

class AuthRepositoryImpl implements AuthRepository {
  @override
  Future<void> forgotPassword() {
    // TODO: implement forgotPassword
    throw UnimplementedError();
  }

  @override
  Future<void> login(
      {required String userName, required String password}) async {
    const String endpoint = "https://example.com/v1/login";
    Map<dynamic, dynamic> params = {'email': userName, 'password': password};
    Response response = await APIUtils.post(endpoint, params);
    print(response);
    if (response.statusCode == 200) {
      //just printing response now
      print(response.data);
    }
  }
}

final authRepositoryProvider =
    StateProvider.autoDispose<AuthRepository>((ref) => AuthRepositoryImpl());


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source