Last active
December 22, 2025 15:35
-
-
Save definev/ebf4f92a7e1f817664ea10b0ef837ea2 to your computer and use it in GitHub Desktop.
Initial loading splash screen in ZenRouter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:async'; | |
| import 'package:flutter/material.dart'; | |
| import 'package:zenrouter/zenrouter.dart'; | |
| import 'package:zenrouter_devtools/zenrouter_devtools.dart'; | |
| bool loggedIn = false; | |
| bool loaded = false; | |
| Future<bool> _authCheck() async { | |
| // Simulate loading | |
| if (!loaded) await Future.delayed(const Duration(seconds: 2)); | |
| loaded = true; | |
| return loggedIn; | |
| } | |
| Future<AppRoute?> ensureInitializeRedirect( | |
| AppCoordinator coordinator, | |
| AppRoute route, { | |
| bool? expectAuth, | |
| AppRoute? redirectTo, | |
| }) async { | |
| try { | |
| final authCheck = await _authCheck(); | |
| if (expectAuth == authCheck) return route; | |
| // Expect = true => need auth | |
| // Expect = false => need not auth | |
| return authCheck ? HomeRoute() : AuthRoute(redirectTo: redirectTo); | |
| } catch (_) { | |
| return AuthRoute(redirectTo: redirectTo); | |
| } finally { | |
| if (!coordinator.initialized.isCompleted) { | |
| coordinator.initialized.complete(); | |
| } | |
| } | |
| } | |
| /// ROUTES | |
| abstract class AppRoute extends RouteTarget with RouteUnique {} | |
| class IndexRoute extends AppRoute with RouteRedirect<AppRoute> { | |
| @override | |
| Widget build( | |
| covariant Coordinator<RouteUnique> coordinator, | |
| BuildContext context, | |
| ) => SizedBox(); | |
| @override | |
| Uri toUri() => Uri.parse('/'); | |
| @override | |
| Future<AppRoute?> redirectWith(AppCoordinator coordinator) async => | |
| await ensureInitializeRedirect(coordinator, this); | |
| } | |
| class HomeRoute extends AppRoute with RouteRedirect<AppRoute> { | |
| @override | |
| Widget build( | |
| covariant Coordinator<RouteUnique> coordinator, | |
| BuildContext context, | |
| ) => Scaffold( | |
| appBar: AppBar(title: Text('Home')), | |
| body: Center( | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| spacing: 8, | |
| children: [ | |
| FilledButton( | |
| onPressed: () => coordinator.push(BookmarkRoute()), | |
| child: Text('Bookmark'), | |
| ), | |
| TextButton( | |
| onPressed: () { | |
| loggedIn = false; | |
| coordinator.replace(AuthRoute()); | |
| }, | |
| child: Text('Log out'), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| @override | |
| Uri toUri() => Uri.parse('/home'); | |
| @override | |
| Future<AppRoute?> redirectWith(AppCoordinator coordinator) async { | |
| return await ensureInitializeRedirect( | |
| coordinator, | |
| this, | |
| expectAuth: true, | |
| redirectTo: this, | |
| ); | |
| } | |
| } | |
| class AuthRoute extends AppRoute with RouteRedirect<AppRoute> { | |
| AuthRoute({this.redirectTo}); | |
| final AppRoute? redirectTo; | |
| @override | |
| List<Object?> get props => [redirectTo]; | |
| @override | |
| Widget build(covariant AppCoordinator coordinator, BuildContext context) { | |
| return Scaffold( | |
| appBar: AppBar(title: Text('Auth')), | |
| body: Center( | |
| child: TextButton( | |
| onPressed: () { | |
| loggedIn = true; | |
| if (redirectTo != null) { | |
| coordinator.root.reset(); | |
| coordinator.recover(redirectTo!); | |
| return; | |
| } | |
| coordinator.recover(HomeRoute()); | |
| }, | |
| child: Text(switch (true) { | |
| _ when redirectTo != null => redirectTo.runtimeType.toString(), | |
| _ => 'Home', | |
| }), | |
| ), | |
| ), | |
| ); | |
| } | |
| @override | |
| Uri toUri() => Uri.parse('/auth').replace( | |
| queryParameters: { | |
| if (redirectTo != null) 'redirectTo': redirectTo!.toUri().toString(), | |
| }, | |
| ); | |
| @override | |
| Future<AppRoute?> redirectWith(AppCoordinator coordinator) async { | |
| return await ensureInitializeRedirect(coordinator, this, expectAuth: false); | |
| } | |
| } | |
| class BookmarkRoute extends AppRoute | |
| with RouteRedirect<AppRoute>, RouteDeepLink { | |
| @override | |
| Widget build( | |
| covariant Coordinator<RouteUnique> coordinator, | |
| BuildContext context, | |
| ) => Scaffold( | |
| appBar: AppBar(title: Text('Bookmark')), | |
| body: ListView.builder( | |
| itemBuilder: (context, index) => ListTile( | |
| title: Text('Bookmark $index'), | |
| leading: Icon(Icons.bookmark), | |
| onTap: () => | |
| coordinator.push(BookmarkDetailRoute(id: index.toString())), | |
| ), | |
| ), | |
| ); | |
| @override | |
| Uri toUri() => Uri.parse('/bookmark'); | |
| @override | |
| Future<AppRoute?> redirectWith(AppCoordinator coordinator) async { | |
| return await ensureInitializeRedirect( | |
| coordinator, | |
| this, | |
| expectAuth: true, | |
| redirectTo: BookmarkRoute(), | |
| ); | |
| } | |
| @override | |
| DeeplinkStrategy get deeplinkStrategy => DeeplinkStrategy.custom; | |
| @override | |
| Future<void> deeplinkHandler(AppCoordinator coordinator, Uri uri) async { | |
| coordinator.pushOrMoveToTop(HomeRoute()); | |
| coordinator.push(this); | |
| } | |
| } | |
| class BookmarkDetailRoute extends AppRoute | |
| with RouteRedirect<AppRoute>, RouteDeepLink { | |
| BookmarkDetailRoute({required this.id}); | |
| final String id; | |
| @override | |
| Widget build( | |
| covariant Coordinator<RouteUnique> coordinator, | |
| BuildContext context, | |
| ) => Scaffold( | |
| appBar: AppBar(title: Text('Bookmark $id')), | |
| body: Center(child: Text('Bookmark $id')), | |
| ); | |
| @override | |
| Uri toUri() => Uri.parse('/bookmark/$id'); | |
| @override | |
| Future<AppRoute?> redirectWith(AppCoordinator coordinator) async { | |
| return await ensureInitializeRedirect( | |
| coordinator, | |
| this, | |
| expectAuth: true, | |
| redirectTo: BookmarkDetailRoute(id: id), | |
| ); | |
| } | |
| @override | |
| DeeplinkStrategy get deeplinkStrategy => DeeplinkStrategy.custom; | |
| @override | |
| Future<void> deeplinkHandler(AppCoordinator coordinator, Uri uri) async { | |
| coordinator.pushOrMoveToTop(HomeRoute()); | |
| coordinator.pushOrMoveToTop(BookmarkRoute()); | |
| coordinator.push(this); | |
| } | |
| } | |
| class NotFoundRoute extends AppRoute { | |
| @override | |
| Widget build( | |
| covariant Coordinator<RouteUnique> coordinator, | |
| BuildContext context, | |
| ) => Text('Not Found'); | |
| @override | |
| Uri toUri() => Uri.parse('/not-found'); | |
| } | |
| /// COORDINATOR | |
| class AppCoordinator extends Coordinator<AppRoute> with CoordinatorDebug { | |
| final Completer<void> initialized = Completer<void>(); | |
| @override | |
| AppRoute parseRouteFromUri(Uri uri) { | |
| return switch (uri.pathSegments) { | |
| [] => IndexRoute(), | |
| ['home'] => HomeRoute(), | |
| ['auth'] => AuthRoute( | |
| redirectTo: parseRouteFromUri( | |
| Uri.parse(uri.queryParameters['redirectTo'] ?? '/home'), | |
| ), | |
| ), | |
| ['bookmark'] => BookmarkRoute(), | |
| ['bookmark', String id] => BookmarkDetailRoute(id: id), | |
| _ => NotFoundRoute(), | |
| }; | |
| } | |
| /// You can override the layoutBuilder to add a loading screen | |
| @override | |
| Widget layoutBuilder(BuildContext context) { | |
| return Stack( | |
| children: [ | |
| Positioned.fill(child: super.layoutBuilder(context)), | |
| FutureBuilder( | |
| future: initialized.future, | |
| builder: (context, snapshot) => switch (snapshot.connectionState) { | |
| // Loading screen | |
| .waiting || .none || .active => ColoredBox( | |
| color: Colors.amber.shade300, | |
| child: Center(child: CircularProgressIndicator()), | |
| ), | |
| .done => const SizedBox(), | |
| }, | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| void main() { | |
| runApp(MainApp()); | |
| } | |
| class MainApp extends StatelessWidget { | |
| MainApp({super.key}); | |
| final coordinator = AppCoordinator(); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp.router( | |
| routerDelegate: coordinator.routerDelegate, | |
| routeInformationParser: coordinator.routeInformationParser, | |
| ); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment