'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 |
|---|
