
User authentication is one of the most critical flows in any app. Whether you’re building a social media platform, an e-commerce app, or a chat tool, a clean and responsive login/register experience can make or break your appโs first impression. Integrating a login register flow in Flutter using Riverpod can streamline this process, providing robust state management.
In this guide, youโll learn how to build a simple login and register flow in Flutter using Riverpod, complete with async state handling, error management, and a modular folder structure. Weโll build the whole thing in a scalable and testable way โ perfect for production-ready apps in 2025.
๐ค Why Choose Riverpod Over Provider?
Riverpod is more than just a replacement for Provider โ itโs a complete rethink of how to handle state in Flutter. In the context of authentication, Riverpod provides several benefits:
- No
BuildContext
needed to access providers - Testable by default โ no need for widget tests to test logic
- Robust async handling with
AsyncValue
- Fine-grained control over scopes and listeners
- Cleaner architecture for larger apps
๐ Deep dive: Why I Use Riverpod (Not Provider) in 2025
๐ฆ Packages Youโll Need
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.4.0
flutter_hooks: ^0.20.5
๐ ๏ธ Folder Structure
/lib
/features
/auth
auth_screen.dart
auth_form.dart
auth_controller.dart
auth_repository.dart
We’re using a feature-first folder structure for scalability.
๐ See why this is recommended
๐ Step 1: Create the Auth Repository
class AuthRepository {
Future login(String email, String password) async {
await Future.delayed(const Duration(seconds: 1));
if (email != 'test@example.com' || password != 'password') {
throw Exception('Invalid credentials');
}
}
Future register(String email, String password) async {
await Future.delayed(const Duration(seconds: 1));
// Simulate success
}
}
final authRepositoryProvider = Provider((ref) => AuthRepository());
๐ง Step 2: Create the Auth Controller
final authControllerProvider = StateNotifierProvider>(
(ref) => AuthController(ref.read),
);
class AuthController extends StateNotifier> {
AuthController(this._read) : super(const AsyncData(null));
final Reader _read;
Future login(String email, String password) async {
state = const AsyncLoading();
try {
await _read(authRepositoryProvider).login(email, password);
state = const AsyncData(null);
} catch (e) {
state = AsyncError(e);
}
}
Future register(String email, String password) async {
state = const AsyncLoading();
try {
await _read(authRepositoryProvider).register(email, password);
state = const AsyncData(null);
} catch (e) {
state = AsyncError(e);
}
}
}
๐ฏ Step 3: Build the Login/Register Form
class AuthForm extends HookConsumerWidget {
final bool isLogin;
const AuthForm({super.key, required this.isLogin});
@override
Widget build(BuildContext context, WidgetRef ref) {
final email = useTextEditingController();
final password = useTextEditingController();
final state = ref.watch(authControllerProvider);
return Column(
children: [
TextField(controller: email, decoration: const InputDecoration(labelText: 'Email')),
TextField(controller: password, decoration: const InputDecoration(labelText: 'Password'), obscureText: true),
const SizedBox(height: 20),
state.isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () {
isLogin
? ref.read(authControllerProvider.notifier).login(email.text, password.text)
: ref.read(authControllerProvider.notifier).register(email.text, password.text);
},
child: Text(isLogin ? 'Login' : 'Register'),
),
if (state.hasError) Text(state.error.toString(), style: const TextStyle(color: Colors.red)),
],
);
}
}
๐ผ๏ธ Step 4: Use the Auth Screen
class AuthScreen extends StatelessWidget {
const AuthScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Login / Register')),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: AuthForm(isLogin: true), // Change to false for register screen
),
);
}
}
โ Final Touches
- Add form validation
- Navigate to the home screen after login
- Replace mock logic with real API calls or Firebase auth
๐ Looking for integration with Firebase?
Check out: Building a Chat App in Flutter with Firebase
๐ง Final Thought
Using Riverpod for your login/register flow gives you asynchronous control, better testing, and clean separation of concerns. It’s perfect for modern Flutter apps in 2025 โ and scales beautifully with your app.