Introduction
Managing state in Flutter apps can be tricky — especially when the app starts growing.
If you’ve tried setState or even Provider, you might have faced unnecessary rebuilds or limitations in accessing data outside widgets.
That’s where Riverpod comes in.
It’s a modern, safe, and testable state management solution built by the same creator of Provider, but without its limitations.
In this article, we’ll explore Riverpod step-by-step, and build a simple Authentication ViewModel as a real-world example.
Why Riverpod?
No BuildContext needed
Works perfectly with async data
Easy testing & maintainability
Less rebuilds = better performance
Clean separation between UI and logic
Setting up Riverpod
Add the package to your project
flutter pub add flutter_riverpod
Wrap your app with ProviderScope in main.dart
void main() {
runApp(const ProviderScope(child: MyApp()));
}
Basic Providers
Let’s look at a few types of providers Riverpod offers
- Provider
Used for read-only values.
final greetingProvider = Provider((ref) => 'Hello Riverpod!');
Usage:
Widget build(BuildContext context, WidgetRef ref) {
final message = ref.watch(greetingProvider);
return Text(message);
}
- StateProvider
Used for mutable state (like counters)
final counterProvider = StateProvider<int>((ref) => 0);
Usage:
final count = ref.watch(counterProvider);
ref.read(counterProvider.notifier).state++;
- FutureProvider
For asynchronous data (e.g., fetching from an API)
final userProvider = FutureProvider((ref) async {
await Future.delayed(const Duration(seconds: 2));
return 'User Loaded!';
});
Building an Authentication ViewModel
Let’s build a small example showing how Riverpod can manage real app logic — user login/logout.
- User Model
class User {
final String id;
final String email;
final String name;
User({required this.id, required this.email, required this.name});
}
- Repository
class AuthRepository {
Future<User> login(String email, String password) async {
await Future.delayed(const Duration(seconds: 2));
if (email == 'joe@test.com' && password == '123456') {
return User(id: '1', email: email, name: 'Joe');
} else {
throw Exception('Invalid credentials');
}
}
Future<void> logout() async => Future.delayed(const Duration(milliseconds: 500));
}
- ViewModel
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AuthState {
final bool isLoading;
final User? user;
final String? error;
const AuthState({this.isLoading = false, this.user, this.error});
}
class AuthViewModel extends StateNotifier<AuthState> {
final AuthRepository _repo;
AuthViewModel(this._repo) : super(const AuthState());
Future<void> login(String email, String password) async {
try {
state = AuthState(isLoading: true);
final user = await _repo.login(email, password);
state = AuthState(user: user);
} catch (e) {
state = AuthState(error: e.toString());
}
}
Future<void> logout() async {
await _repo.logout();
state = const AuthState();
}
}
- Providers
final authRepositoryProvider = Provider((ref) => AuthRepository());
final authViewModelProvider =
StateNotifierProvider<AuthViewModel, AuthState>((ref) {
return AuthViewModel(ref.watch(authRepositoryProvider));
});
- Login Screen
class LoginScreen extends ConsumerWidget {
const LoginScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(authViewModelProvider);
final viewModel = ref.read(authViewModelProvider.notifier);
final email = TextEditingController();
final pass = TextEditingController();
return Scaffold(
appBar: AppBar(title: const Text('Login')),
body: Center(
child: state.isLoading
? const CircularProgressIndicator()
: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: email, decoration: const InputDecoration(labelText: 'Email')),
TextField(controller: pass, decoration: const InputDecoration(labelText: 'Password'), obscureText: true),
ElevatedButton(
onPressed: () => viewModel.login(email.text, pass.text),
child: const Text('Login'),
),
if (state.error != null)
Text(state.error!, style: const TextStyle(color: Colors.red)),
if (state.user != null)
Text('Welcome, ${state.user!.name}!'),
],
),
),
);
}
}
Conclusion
Riverpod gives you the power of scalable and testable state management — perfect for both small and enterprise Flutter apps.
If you’re building something serious, Riverpod is definitely worth mastering.
Extra
Official Docs: link
Top comments (0)