đ Authentication Feature Example
FLX CLI āĻĻāĻŋāϝāĻŧā§ āϤā§āϰāĻŋ āĻšāĻāϝāĻŧāĻž āĻāĻāĻāĻŋ complete authentication feature-āĻāϰ boilerplate code exampleāĨ¤
đ Command to Generateâ
flx gen feature auth
đ Generated Structureâ
lib/features/auth/
âââ data/
â âââ datasources/
â â âââ auth_remote_data_source.dart
â âââ models/
â â âââ auth_model.dart
â âââ repositories/
â âââ auth_repository_impl.dart
âââ domain/
â âââ entities/
â â âââ auth_entity.dart
â âââ repositories/
â â âââ auth_repository.dart
â âââ usecases/
â âââ auth_usecase.dart
âââ presentation/
âââ bindings/
â âââ auth_binding.dart
âââ controllers/ # GetX
â âââ auth_controller.dart
âââ bloc/ # BLoC (alternative)
â âââ auth_bloc.dart
â âââ auth_event.dart
â âââ auth_state.dart
âââ pages/
âââ auth_page.dart
đī¸ Domain Layerâ
Auth Entityâ
class AuthEntity {
final String id;
final String name;
final String email;
final String? profilePicture;
final bool isEmailVerified;
final DateTime createdAt;
final DateTime lastLoginAt;
const AuthEntity({
required this.id,
required this.name,
required this.email,
this.profilePicture,
required this.isEmailVerified,
required this.createdAt,
required this.lastLoginAt,
});
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AuthEntity &&
other.id == id &&
other.name == name &&
other.email == email &&
other.profilePicture == profilePicture &&
other.isEmailVerified == isEmailVerified &&
other.createdAt == createdAt &&
other.lastLoginAt == lastLoginAt;
}
@override
int get hashCode {
return id.hashCode ^
name.hashCode ^
email.hashCode ^
profilePicture.hashCode ^
isEmailVerified.hashCode ^
createdAt.hashCode ^
lastLoginAt.hashCode;
}
@override
String toString() {
return 'AuthEntity(id: $id, name: $name, email: $email, profilePicture: $profilePicture, isEmailVerified: $isEmailVerified, createdAt: $createdAt, lastLoginAt: $lastLoginAt)';
}
}
Auth Repository Interfaceâ
import '../entities/auth_entity.dart';
abstract class AuthRepository {
/// User login with email and password
Future<AuthEntity> login({
required String email,
required String password,
});
/// User registration
Future<AuthEntity> register({
required String name,
required String email,
required String password,
});
/// Logout current user
Future<void> logout();
/// Get current user from local storage
Future<AuthEntity?> getCurrentUser();
/// Check if user is logged in
Future<bool> isLoggedIn();
/// Refresh access token
Future<String> refreshToken();
/// Send password reset email
Future<void> sendPasswordResetEmail(String email);
/// Verify email with OTP
Future<void> verifyEmail(String otp);
/// Update user profile
Future<AuthEntity> updateProfile({
String? name,
String? profilePicture,
});
/// Change password
Future<void> changePassword({
required String currentPassword,
required String newPassword,
});
/// Delete account
Future<void> deleteAccount();
}
Auth Use Caseâ
import '../entities/auth_entity.dart';
import '../repositories/auth_repository.dart';
class AuthUseCase {
final AuthRepository _repository;
AuthUseCase(this._repository);
/// Login use case
Future<AuthEntity> login({
required String email,
required String password,
}) async {
// Email validation
if (!_isValidEmail(email)) {
throw Exception('Invalid email format');
}
// Password validation
if (password.length < 6) {
throw Exception('Password must be at least 6 characters');
}
try {
final user = await _repository.login(
email: email.trim().toLowerCase(),
password: password,
);
return user;
} catch (e) {
throw Exception('Login failed: ${e.toString()}');
}
}
/// Register use case
Future<AuthEntity> register({
required String name,
required String email,
required String password,
required String confirmPassword,
}) async {
// Name validation
if (name.trim().isEmpty) {
throw Exception('Name is required');
}
// Email validation
if (!_isValidEmail(email)) {
throw Exception('Invalid email format');
}
// Password validation
if (password.length < 6) {
throw Exception('Password must be at least 6 characters');
}
// Confirm password validation
if (password != confirmPassword) {
throw Exception('Passwords do not match');
}
try {
final user = await _repository.register(
name: name.trim(),
email: email.trim().toLowerCase(),
password: password,
);
return user;
} catch (e) {
throw Exception('Registration failed: ${e.toString()}');
}
}
/// Logout use case
Future<void> logout() async {
try {
await _repository.logout();
} catch (e) {
throw Exception('Logout failed: ${e.toString()}');
}
}
/// Get current user use case
Future<AuthEntity?> getCurrentUser() async {
try {
return await _repository.getCurrentUser();
} catch (e) {
return null;
}
}
/// Check if logged in use case
Future<bool> isLoggedIn() async {
try {
return await _repository.isLoggedIn();
} catch (e) {
return false;
}
}
/// Send password reset email use case
Future<void> sendPasswordResetEmail(String email) async {
if (!_isValidEmail(email)) {
throw Exception('Invalid email format');
}
try {
await _repository.sendPasswordResetEmail(email.trim().toLowerCase());
} catch (e) {
throw Exception('Failed to send reset email: ${e.toString()}');
}
}
/// Verify email use case
Future<void> verifyEmail(String otp) async {
if (otp.trim().isEmpty) {
throw Exception('OTP is required');
}
if (otp.length != 6) {
throw Exception('OTP must be 6 digits');
}
try {
await _repository.verifyEmail(otp);
} catch (e) {
throw Exception('Email verification failed: ${e.toString()}');
}
}
/// Private method to validate email
bool _isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
}
đ Data Layerâ
Auth Modelâ
import '../../domain/entities/auth_entity.dart';
class AuthModel extends AuthEntity {
const AuthModel({
required super.id,
required super.name,
required super.email,
super.profilePicture,
required super.isEmailVerified,
required super.createdAt,
required super.lastLoginAt,
});
/// Create AuthModel from JSON
factory AuthModel.fromJson(Map<String, dynamic> json) {
return AuthModel(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
profilePicture: json['profile_picture'] as String?,
isEmailVerified: json['is_email_verified'] as bool? ?? false,
createdAt: DateTime.parse(json['created_at'] as String),
lastLoginAt: json['last_login_at'] != null
? DateTime.parse(json['last_login_at'] as String)
: DateTime.now(),
);
}
/// Convert AuthModel to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'profile_picture': profilePicture,
'is_email_verified': isEmailVerified,
'created_at': createdAt.toIso8601String(),
'last_login_at': lastLoginAt.toIso8601String(),
};
}
/// Create a copy with updated fields
AuthModel copyWith({
String? id,
String? name,
String? email,
String? profilePicture,
bool? isEmailVerified,
DateTime? createdAt,
DateTime? lastLoginAt,
}) {
return AuthModel(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
profilePicture: profilePicture ?? this.profilePicture,
isEmailVerified: isEmailVerified ?? this.isEmailVerified,
createdAt: createdAt ?? this.createdAt,
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
);
}
@override
String toString() {
return 'AuthModel(id: $id, name: $name, email: $email, profilePicture: $profilePicture, isEmailVerified: $isEmailVerified, createdAt: $createdAt, lastLoginAt: $lastLoginAt)';
}
}
Auth Remote Data Sourceâ
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/auth_model.dart';
abstract class AuthRemoteDataSource {
Future<AuthModel> login({
required String email,
required String password,
});
Future<AuthModel> register({
required String name,
required String email,
required String password,
});
Future<void> logout();
Future<String> refreshToken();
Future<void> sendPasswordResetEmail(String email);
Future<void> verifyEmail(String otp);
Future<AuthModel> updateProfile({
String? name,
String? profilePicture,
});
Future<void> changePassword({
required String currentPassword,
required String newPassword,
});
Future<void> deleteAccount();
}
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
final http.Client client;
final String baseUrl;
AuthRemoteDataSourceImpl({
required this.client,
required this.baseUrl,
});
@override
Future<AuthModel> login({
required String email,
required String password,
}) async {
final response = await client.post(
Uri.parse('$baseUrl/auth/login'),
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'email': email,
'password': password,
}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return AuthModel.fromJson(data['user']);
} else if (response.statusCode == 401) {
throw Exception('Invalid credentials');
} else if (response.statusCode == 404) {
throw Exception('User not found');
} else {
throw Exception('Login failed: ${response.body}');
}
}
@override
Future<AuthModel> register({
required String name,
required String email,
required String password,
}) async {
final response = await client.post(
Uri.parse('$baseUrl/auth/register'),
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'name': name,
'email': email,
'password': password,
}),
);
if (response.statusCode == 201) {
final data = json.decode(response.body);
return AuthModel.fromJson(data['user']);
} else if (response.statusCode == 409) {
throw Exception('Email already exists');
} else {
throw Exception('Registration failed: ${response.body}');
}
}
@override
Future<void> logout() async {
final response = await client.post(
Uri.parse('$baseUrl/auth/logout'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getAccessToken()}',
},
);
if (response.statusCode != 200) {
throw Exception('Logout failed: ${response.body}');
}
}
@override
Future<String> refreshToken() async {
final response = await client.post(
Uri.parse('$baseUrl/auth/refresh'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getRefreshToken()}',
},
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return data['access_token'];
} else {
throw Exception('Token refresh failed: ${response.body}');
}
}
@override
Future<void> sendPasswordResetEmail(String email) async {
final response = await client.post(
Uri.parse('$baseUrl/auth/password-reset'),
headers: {
'Content-Type': 'application/json',
},
body: json.encode({
'email': email,
}),
);
if (response.statusCode != 200) {
throw Exception('Failed to send reset email: ${response.body}');
}
}
@override
Future<void> verifyEmail(String otp) async {
final response = await client.post(
Uri.parse('$baseUrl/auth/verify-email'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getAccessToken()}',
},
body: json.encode({
'otp': otp,
}),
);
if (response.statusCode != 200) {
throw Exception('Email verification failed: ${response.body}');
}
}
@override
Future<AuthModel> updateProfile({
String? name,
String? profilePicture,
}) async {
final response = await client.patch(
Uri.parse('$baseUrl/auth/profile'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getAccessToken()}',
},
body: json.encode({
if (name != null) 'name': name,
if (profilePicture != null) 'profile_picture': profilePicture,
}),
);
if (response.statusCode == 200) {
final data = json.decode(response.body);
return AuthModel.fromJson(data['user']);
} else {
throw Exception('Profile update failed: ${response.body}');
}
}
@override
Future<void> changePassword({
required String currentPassword,
required String newPassword,
}) async {
final response = await client.patch(
Uri.parse('$baseUrl/auth/change-password'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getAccessToken()}',
},
body: json.encode({
'current_password': currentPassword,
'new_password': newPassword,
}),
);
if (response.statusCode != 200) {
throw Exception('Password change failed: ${response.body}');
}
}
@override
Future<void> deleteAccount() async {
final response = await client.delete(
Uri.parse('$baseUrl/auth/account'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${await _getAccessToken()}',
},
);
if (response.statusCode != 200) {
throw Exception('Account deletion failed: ${response.body}');
}
}
// Helper methods to get tokens (implement based on your storage strategy)
Future<String> _getAccessToken() async {
// Get access token from secure storage
// This is a placeholder implementation
return 'access_token_here';
}
Future<String> _getRefreshToken() async {
// Get refresh token from secure storage
// This is a placeholder implementation
return 'refresh_token_here';
}
}
Auth Repository Implementationâ
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import '../../domain/entities/auth_entity.dart';
import '../../domain/repositories/auth_repository.dart';
import '../datasources/auth_remote_data_source.dart';
import '../models/auth_model.dart';
class AuthRepositoryImpl implements AuthRepository {
final AuthRemoteDataSource remoteDataSource;
final SharedPreferences sharedPreferences;
AuthRepositoryImpl({
required this.remoteDataSource,
required this.sharedPreferences,
});
static const String _currentUserKey = 'current_user';
static const String _accessTokenKey = 'access_token';
static const String _refreshTokenKey = 'refresh_token';
@override
Future<AuthEntity> login({
required String email,
required String password,
}) async {
try {
final authModel = await remoteDataSource.login(
email: email,
password: password,
);
// Save user data locally
await _saveUserLocally(authModel);
return authModel;
} catch (e) {
throw Exception('Login failed: ${e.toString()}');
}
}
@override
Future<AuthEntity> register({
required String name,
required String email,
required String password,
}) async {
try {
final authModel = await remoteDataSource.register(
name: name,
email: email,
password: password,
);
// Save user data locally
await _saveUserLocally(authModel);
return authModel;
} catch (e) {
throw Exception('Registration failed: ${e.toString()}');
}
}
@override
Future<void> logout() async {
try {
// Call remote logout
await remoteDataSource.logout();
} catch (e) {
// Even if remote logout fails, clear local data
print('Remote logout failed: $e');
} finally {
// Clear local data
await _clearUserData();
}
}
@override
Future<AuthEntity?> getCurrentUser() async {
try {
final userJson = sharedPreferences.getString(_currentUserKey);
if (userJson != null) {
final userMap = json.decode(userJson) as Map<String, dynamic>;
return AuthModel.fromJson(userMap);
}
return null;
} catch (e) {
return null;
}
}
@override
Future<bool> isLoggedIn() async {
try {
final user = await getCurrentUser();
final accessToken = sharedPreferences.getString(_accessTokenKey);
return user != null && accessToken != null;
} catch (e) {
return false;
}
}
@override
Future<String> refreshToken() async {
try {
final newAccessToken = await remoteDataSource.refreshToken();
await sharedPreferences.setString(_accessTokenKey, newAccessToken);
return newAccessToken;
} catch (e) {
throw Exception('Token refresh failed: ${e.toString()}');
}
}
@override
Future<void> sendPasswordResetEmail(String email) async {
try {
await remoteDataSource.sendPasswordResetEmail(email);
} catch (e) {
throw Exception('Failed to send reset email: ${e.toString()}');
}
}
@override
Future<void> verifyEmail(String otp) async {
try {
await remoteDataSource.verifyEmail(otp);
// Update local user data if available
final currentUser = await getCurrentUser();
if (currentUser != null && currentUser is AuthModel) {
final updatedUser = currentUser.copyWith(isEmailVerified: true);
await _saveUserLocally(updatedUser);
}
} catch (e) {
throw Exception('Email verification failed: ${e.toString()}');
}
}
@override
Future<AuthEntity> updateProfile({
String? name,
String? profilePicture,
}) async {
try {
final updatedUser = await remoteDataSource.updateProfile(
name: name,
profilePicture: profilePicture,
);
// Save updated user data locally
await _saveUserLocally(updatedUser);
return updatedUser;
} catch (e) {
throw Exception('Profile update failed: ${e.toString()}');
}
}
@override
Future<void> changePassword({
required String currentPassword,
required String newPassword,
}) async {
try {
await remoteDataSource.changePassword(
currentPassword: currentPassword,
newPassword: newPassword,
);
} catch (e) {
throw Exception('Password change failed: ${e.toString()}');
}
}
@override
Future<void> deleteAccount() async {
try {
await remoteDataSource.deleteAccount();
await _clearUserData();
} catch (e) {
throw Exception('Account deletion failed: ${e.toString()}');
}
}
// Private helper methods
Future<void> _saveUserLocally(AuthModel user) async {
await sharedPreferences.setString(
_currentUserKey,
json.encode(user.toJson()),
);
}
Future<void> _clearUserData() async {
await sharedPreferences.remove(_currentUserKey);
await sharedPreferences.remove(_accessTokenKey);
await sharedPreferences.remove(_refreshTokenKey);
}
}
đ¨ Presentation Layer (GetX)â
Auth Controllerâ
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import '../../domain/entities/auth_entity.dart';
import '../../domain/usecases/auth_usecase.dart';
class AuthController extends GetxController {
final AuthUseCase _authUseCase;
AuthController(this._authUseCase);
// Observable states
final _isLoading = false.obs;
final _currentUser = Rxn<AuthEntity>();
final _errorMessage = ''.obs;
// Form controllers
final emailController = TextEditingController();
final passwordController = TextEditingController();
final nameController = TextEditingController();
final confirmPasswordController = TextEditingController();
final otpController = TextEditingController();
// Form keys
final loginFormKey = GlobalKey<FormState>();
final registerFormKey = GlobalKey<FormState>();
// Getters
bool get isLoading => _isLoading.value;
AuthEntity? get currentUser => _currentUser.value;
String get errorMessage => _errorMessage.value;
bool get isLoggedIn => _currentUser.value != null;
@override
void onInit() {
super.onInit();
_checkCurrentUser();
}
@override
void onClose() {
emailController.dispose();
passwordController.dispose();
nameController.dispose();
confirmPasswordController.dispose();
otpController.dispose();
super.onClose();
}
/// Check if user is already logged in
Future<void> _checkCurrentUser() async {
try {
_isLoading.value = true;
final user = await _authUseCase.getCurrentUser();
_currentUser.value = user;
} catch (e) {
_errorMessage.value = e.toString();
} finally {
_isLoading.value = false;
}
}
/// Login functionality
Future<void> login() async {
if (!loginFormKey.currentState!.validate()) return;
try {
_isLoading.value = true;
_errorMessage.value = '';
final user = await _authUseCase.login(
email: emailController.text,
password: passwordController.text,
);
_currentUser.value = user;
_clearLoginForm();
Get.snackbar(
'āϏāĻĢāϞ!',
'āϏāĻĢāϞāĻāĻžāĻŦā§ āϞāĻāĻāύ āĻšāϝāĻŧā§āĻā§',
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Navigate to home or dashboard
Get.offAllNamed('/dashboard');
} catch (e) {
_errorMessage.value = e.toString();
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
_isLoading.value = false;
}
}
/// Register functionality
Future<void> register() async {
if (!registerFormKey.currentState!.validate()) return;
try {
_isLoading.value = true;
_errorMessage.value = '';
final user = await _authUseCase.register(
name: nameController.text,
email: emailController.text,
password: passwordController.text,
confirmPassword: confirmPasswordController.text,
);
_currentUser.value = user;
_clearRegisterForm();
Get.snackbar(
'āϏāĻĢāϞ!',
'āϏāĻĢāϞāĻāĻžāĻŦā§ āϰā§āĻāĻŋāϏā§āĻāĻžāϰ āĻšāϝāĻŧā§āĻā§',
backgroundColor: Colors.green,
colorText: Colors.white,
);
// Navigate to email verification or dashboard
if (!user.isEmailVerified) {
Get.toNamed('/verify-email');
} else {
Get.offAllNamed('/dashboard');
}
} catch (e) {
_errorMessage.value = e.toString();
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
_isLoading.value = false;
}
}
/// Logout functionality
Future<void> logout() async {
try {
_isLoading.value = true;
await _authUseCase.logout();
_currentUser.value = null;
Get.snackbar(
'āϏāĻĢāϞ!',
'āϏāĻĢāϞāĻāĻžāĻŦā§ āϞāĻāĻāĻāĻ āĻšāϝāĻŧā§āĻā§',
backgroundColor: Colors.green,
colorText: Colors.white,
);
Get.offAllNamed('/login');
} catch (e) {
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
_isLoading.value = false;
}
}
/// Send password reset email
Future<void> sendPasswordResetEmail() async {
if (emailController.text.isEmpty) {
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
'āĻāĻŽā§āĻāϞ āĻĒā§āϰāϝāĻŧā§āĻāύ',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
try {
_isLoading.value = true;
await _authUseCase.sendPasswordResetEmail(emailController.text);
Get.snackbar(
'āϏāĻĢāϞ!',
'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āϰāĻŋāϏā§āĻ āĻāĻŽā§āĻāϞ āĻĒāĻžāĻ āĻžāύ⧠āĻšāϝāĻŧā§āĻā§',
backgroundColor: Colors.green,
colorText: Colors.white,
);
} catch (e) {
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
_isLoading.value = false;
}
}
/// Verify email with OTP
Future<void> verifyEmail() async {
if (otpController.text.isEmpty) {
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
'OTP āĻĒā§āϰāϝāĻŧā§āĻāύ',
backgroundColor: Colors.red,
colorText: Colors.white,
);
return;
}
try {
_isLoading.value = true;
await _authUseCase.verifyEmail(otpController.text);
// Update current user
if (_currentUser.value != null) {
_currentUser.refresh();
}
Get.snackbar(
'āϏāĻĢāϞ!',
'āĻāĻŽā§āĻāϞ āϏāĻĢāϞāĻāĻžāĻŦā§ āϝāĻžāĻāĻžāĻ āĻšāϝāĻŧā§āĻā§',
backgroundColor: Colors.green,
colorText: Colors.white,
);
Get.offAllNamed('/dashboard');
} catch (e) {
Get.snackbar(
'āϤā§āϰā§āĻāĻŋ!',
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
} finally {
_isLoading.value = false;
}
}
/// Clear login form
void _clearLoginForm() {
emailController.clear();
passwordController.clear();
}
/// Clear register form
void _clearRegisterForm() {
nameController.clear();
emailController.clear();
passwordController.clear();
confirmPasswordController.clear();
}
/// Validators
String? validateEmail(String? value) {
if (value == null || value.isEmpty) {
return 'āĻāĻŽā§āĻāϞ āĻĒā§āϰāϝāĻŧā§āĻāύ';
}
if (!GetUtils.isEmail(value)) {
return 'āϏāĻ āĻŋāĻ āĻāĻŽā§āĻāϞ āĻĢāϰāĻŽā§āϝāĻžāĻ āĻĒā§āϰāϝāĻŧā§āĻāύ';
}
return null;
}
String? validatePassword(String? value) {
if (value == null || value.isEmpty) {
return 'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻĒā§āϰāϝāĻŧā§āĻāύ';
}
if (value.length < 6) {
return 'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻāĻŽāĻĒāĻā§āώ⧠ā§Ŧ āĻ
āĻā§āώāϰā§āϰ āĻšāϤ⧠āĻšāĻŦā§';
}
return null;
}
String? validateName(String? value) {
if (value == null || value.isEmpty) {
return 'āύāĻžāĻŽ āĻĒā§āϰāϝāĻŧā§āĻāύ';
}
if (value.length < 2) {
return 'āύāĻžāĻŽ āĻāĻŽāĻĒāĻā§āώ⧠⧍ āĻ
āĻā§āώāϰā§āϰ āĻšāϤ⧠āĻšāĻŦā§';
}
return null;
}
String? validateConfirmPassword(String? value) {
if (value == null || value.isEmpty) {
return 'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āύāĻŋāĻļā§āĻāĻŋāϤāĻāϰāĻŖ āĻĒā§āϰāϝāĻŧā§āĻāύ';
}
if (value != passwordController.text) {
return 'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ āĻŽāĻŋāϞāĻā§ āύāĻž';
}
return null;
}
}
Auth Binding (GetX)â
import 'package:get/get.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../../data/datasources/auth_remote_data_source.dart';
import '../../data/repositories/auth_repository_impl.dart';
import '../../domain/repositories/auth_repository.dart';
import '../../domain/usecases/auth_usecase.dart';
import '../controllers/auth_controller.dart';
class AuthBinding extends Bindings {
@override
void dependencies() {
// HTTP Client
Get.lazyPut<http.Client>(() => http.Client());
// Shared Preferences
Get.lazyPutAsync<SharedPreferences>(
() => SharedPreferences.getInstance(),
);
// Data Source
Get.lazyPut<AuthRemoteDataSource>(
() => AuthRemoteDataSourceImpl(
client: Get.find<http.Client>(),
baseUrl: 'https://api.example.com', // Replace with your API base URL
),
);
// Repository
Get.lazyPut<AuthRepository>(
() => AuthRepositoryImpl(
remoteDataSource: Get.find<AuthRemoteDataSource>(),
sharedPreferences: Get.find<SharedPreferences>(),
),
);
// Use Case
Get.lazyPut<AuthUseCase>(
() => AuthUseCase(Get.find<AuthRepository>()),
);
// Controller
Get.lazyPut<AuthController>(
() => AuthController(Get.find<AuthUseCase>()),
);
}
}
đ¯ Key Features Includedâ
- Complete Authentication Flow: Login, Register, Logout
- Email Verification: OTP-based email verification
- Password Management: Reset password functionality
- Profile Management: Update user profile
- Local Storage: Cache user data locally
- Token Management: Access and refresh token handling
- Error Handling: Comprehensive error handling
- Form Validation: Input validation for all forms
- State Management: Reactive UI with GetX observables
- Security: Secure token storage and API communication
đ Usage Exampleâ
// In your main.dart or route configuration
GetPage(
name: '/login',
page: () => const AuthPage(),
binding: AuthBinding(),
),
// In your UI
class LoginForm extends StatelessWidget {
Widget build(BuildContext context) {
final controller = Get.find<AuthController>();
return Form(
key: controller.loginFormKey,
child: Column(
children: [
TextFormField(
controller: controller.emailController,
validator: controller.validateEmail,
decoration: InputDecoration(labelText: 'āĻāĻŽā§āĻāϞ'),
),
TextFormField(
controller: controller.passwordController,
validator: controller.validatePassword,
obscureText: true,
decoration: InputDecoration(labelText: 'āĻĒāĻžāϏāĻāϝāĻŧāĻžāϰā§āĻĄ'),
),
Obx(() => ElevatedButton(
onPressed: controller.isLoading ? null : controller.login,
child: controller.isLoading
? CircularProgressIndicator()
: Text('āϞāĻāĻāύ'),
)),
],
),
);
}
}
āĻāĻ example-āĻ āϰāϝāĻŧā§āĻā§: āϏāĻŽā§āĻĒā§āϰā§āĻŖ authentication feature āϝāĻž production-ready āĻāĻŦāĻ scalable! đ