Flutter Onboarding with Riverpod, Shared Preferences, and GoRouter!
Master Flutter Onboarding: Streamline Your User Experience with Riverpod, Shared Preferences, and GoRouter
Flutter Onboarding with Riverpod, Shared Preferences, and GoRouter
In this article, we will explore how to implement an onboarding flow in a Flutter application using GoRouter, Riverpod, and SharedPreferences. This approach helps maintain a cleaner and more modular codebase. We will walk through setting up the necessary dependencies, creating providers with Riverpod, and configuring the router.
Prerequisites
Ensure you have the following dependencies added to your pubspec.yaml
:
dependencies:
flutter:
sdk: flutter
go_router:
shared_preferences:
flutter_riverpod:
riverpod_annotation:
dev_dependencies:
build_runner:
riverpod_generator:
Setting Up Riverpod Provider
First, create a Riverpod provider for managing the onboarding state. Use the @riverpod
annotation to generate the necessary code.
Create
onboarding_provider.dart
:
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'onboarding_provider.g.dart';
@riverpod
class OnboardingNotifier extends _$OnboardingNotifier {
@override
bool build() {
_loadOnboardingStatus();
return false;
}
Future<void> _loadOnboardingStatus() async {
final prefs = await SharedPreferences.getInstance();
state = prefs.getBool('onboardingCompleted') ?? false;
}
Future<void> completeOnboarding() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('onboardingCompleted', true);
state = true;
}
}
Generate the necessary files by running the build runner:
flutter pub run build_runner build
Creating the Router Configuration
Next, move the router configuration into a separate app_routes.dart
file.
Create
app_routes.dart
:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'onboarding_screen.dart';
import 'home_screen.dart';
import 'onboarding_provider.dart';
class AppRoutes {
static GoRouter createRouter(WidgetRef ref) {
final isOnboardingCompleted = ref.watch(onboardingNotifierProvider);
return GoRouter(
initialLocation: isOnboardingCompleted ? '/home' : '/onboarding',
routes: [
GoRoute(
path: '/onboarding',
builder: (context, state) => OnboardingScreen(),
),
GoRoute(
path: '/home',
builder: (context, state) => HomeScreen(),
),
],
);
}
}
Main Application File
Finally, update your main application file to use the router configuration.
Update
main.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'app_routes.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = AppRoutes.createRouter(ref);
return MaterialApp.router(
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
routeInformationProvider: router.routeInformationProvider,
);
}
}
Onboarding Screen
Ensure your onboarding screen interacts correctly with the provider.
Create
onboarding_screen.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'onboarding_provider.dart';
class OnboardingScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
appBar: AppBar(
title: Text('Onboarding'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
await ref.read(onboardingNotifierProvider.notifier).completeOnboarding();
context.go('/home');
},
child: Text('Complete Onboarding'),
),
),
);
}
}
Home Screen
Create a simple home screen for the app.
Create
home_screen.dart
:
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: Text('Welcome to the Home Screen!'),
),
);
}
}
Conclusion
By organizing the router configuration into a separate file (app_routes.dart
), you maintain a cleaner and more modular codebase. This approach ensures that your main application file (main.dart
) remains concise and focused, improving maintainability and readability. Using Riverpod for state management and SharedPreferences for persistent state ensures a robust and scalable solution for handling onboarding flows in your Flutter application.