pod_router 0.0.1+3
pod_router: ^0.0.1+3 copied to clipboard
A Flutter navigation package that combines go_router with Riverpod for clean, testable routing with authentication support.
pod_router #
A Flutter package that simplifies integration between Go Router and Riverpod state management with authentication handling. This package helps you manage routing based on authentication state and other app conditions, reducing boilerplate and providing a standardized approach.
Features #
- π Authentication-aware routing - Automatically redirect users based on auth state
- π State-driven navigation - Route changes respond to Riverpod state changes
- π Simplified navigation flow - Handle onboarding, authentication flow, and protected routes
- π Declarative route definition - Define protected and public routes clearly
- πͺ Easy integration - Works with your existing Go Router and Riverpod setup
- π Initial data loading - Wait for required data before navigation starts
Installation #
dependencies:
pod_router : ^0.0.1
Run:
flutter pub get
Quick Start #
import 'package:pod_router/pod_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
// 1. Create your auth notifier
class MyAuthNotifier extends BaseAuthNotifier {
MyAuthNotifier(Ref ref) : super(ref);
@override
void initialize() {
// Your auth initialization logic here
}
Future<void> login() async {
setLoading();
// Login implementation
setAuthenticated();
}
Future<void> logout() async {
setLoading();
// Logout implementation
setUnauthenticated();
}
}
// 2. Create the auth provider
final authProvider = createAuthNotifierProvider<MyAuthNotifier>(
(ref) => MyAuthNotifier(ref)
);
// 3. Define your routes manager
class AppRoutesManager extends RoutesManager {
AppRoutesManager(Ref ref) : super(ref);
@override
List<String> get protectedRoutes => ['/profile', '/settings'];
@override
List<String> get publicRoutes => ['/login', '/register'];
@override
String get splashRoute => '/splash';
@override
String get defaultAuthenticatedRoute => '/home';
@override
String get defaultUnauthenticatedRoute => '/login';
// 4. Define initial data providers (NEW!)
@override
List<ProviderListenable> get initialDataProviders => [
userDataProvider,
settingsProvider,
];
@override
void initializeListeners() {
super.initializeListeners(); // Don't forget this for initial data loading!
// Connect auth state to router
ref.listen(
authProvider.select((value) => value.status),
(prev, next) {
authState.value = next;
},
fireImmediately: true,
);
// Connect loading state
ref.listen(authProvider.select((value) => value.isLoading),
(prev, next) {
isLoading.value = next;
},
fireImmediately: true,
);
}
}
// 5. Create the router provider
final routesProvider = routesManagerProvider<AppRoutesManager>(
(ref) => AppRoutesManager(ref)
);
// 6. Set up the Go Router
final routerProvider = Provider<GoRouter>((ref) {
final routesManager = ref.watch(routesProvider);
return GoRouter(
redirect: routesManager.onRedirect,
refreshListenable: CombinedListen(routesManager.refreshables),
routes: [
// Your routes here
],
);
});
Detailed Usage #
Authentication State Management #
The package provides a BaseAuthNotifier
class that handles authentication state. Extend this class to implement your specific authentication logic:
class FirebaseAuthNotifier extends BaseAuthNotifier {
FirebaseAuthNotifier(Ref ref) : super(ref);
@override
void initialize() {
// Listen to Firebase auth changes
FirebaseAuth.instance.authStateChanges().listen((user) {
if (user != null) {
setAuthenticated();
} else {
setUnauthenticated();
}
});
}
Future<void> signIn(String email, String password) async {
setLoading();
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} catch (e) {
setUnauthenticated();
rethrow;
}
}
Future<void> signOut() async {
setLoading();
await FirebaseAuth.instance.signOut();
}
}
Route Management #
The RoutesManager
class handles route redirects based on authentication state:
class MyRoutesManager extends RoutesManager {
MyRoutesManager(Ref ref) : super(ref);
@override
List<String> get protectedRoutes => [
'/profile',
'/settings',
'/dashboard'
];
@override
List<String> get publicRoutes => [
'/login',
'/register',
'/forgot-password'
];
@override
String get splashRoute => '/splash';
@override
String get defaultAuthenticatedRoute => '/home';
@override
String get defaultUnauthenticatedRoute => '/login';
@override
String? get initialAppFlowRoute => '/onboarding';
@override
void initializeListeners() {
super.initializeListeners(); // Important for initial data loading!
// Listen to auth state
ref.listen(
authProvider.select((value) => value.status),
(prev, next) {
authState.value = next;
},
fireImmediately: true,
);
// Optional: Listen to onboarding completion
ref.listen(onboardingCompletedProvider, (prev, next) {
appFlowNotifier.value = !next;
});
// Other state listeners...
}
}
Initial Data Loading (NEW!) #
The package now supports waiting for initial data to load before navigation:
class MyRoutesManager extends RoutesManager {
// ... other overrides
@override
List<ProviderListenable> get initialDataProviders => [
// Add providers that need to be loaded before navigation begins
userDataProvider,
themeProvider,
localizationProvider,
// Any async provider that should complete before navigation
];
}
Initial Data Loading #
When these providers are specified, the router will:
- Show the splash screen until all data is loaded
- Register the routes manager with the system
- Track loading state automatically
- Provide error handling for data loading failures
Setting Up Go Router #
Integrate with Go Router using the CombinedListen
utility:
final goRouterProvider = Provider<GoRouter>((ref) {
final routesManager = ref.watch(routesManagerProvider);
return GoRouter(
debugLogDiagnostics: true,
redirect: routesManager.onRedirect,
refreshListenable: CombinedListen(routesManager.refreshables),
initialLocation: '/splash',
routes: [
// Your routes...
],
);
});
Complete Example #
See the example folder for a complete implementation.
Debugging #
Enable debug logs to help troubleshoot routing issues:
void main() {
PackageLogger.enableDebugLogs = true;
runApp(ProviderScope(child: MyApp()));
}
Additional Configuration #
Customizing Redirect Logic #
You can override the onRedirect
method in your RoutesManager
subclass to customize the redirect logic:
@override
FutureOr<String?> onRedirect(BuildContext context, GoRouterState state) async {
// Custom logic before calling super
if (specialCondition) {
return '/special-route';
}
// Use default logic
return await super.onRedirect(context, state);
}
Adding Custom State Triggers #
You can add additional ValueNotifier
objects to trigger route refreshes:
final themeChanged = ValueNotifier<bool>(false);
@override
List<ChangeNotifier> get refreshables => [
...super.refreshables,
themeChanged,
];
Waiting for Authenticated State #
The package provides a utility to wait for authentication state to be determined:
Future<void> loadInitialData() async {
// Wait for auth state before proceeding
final authStatus = await waitForAuthState(ref, authProvider);
// Now you can perform actions based on auth status
if (authStatus == AuthStatus.authenticated) {
await ref.read(userDataProvider.notifier).loadUserData();
}
}
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
License #
This project is licensed under the MIT License - see the LICENSE file for details.