Skip to content

Instantly share code, notes, and snippets.

@definev
Last active December 22, 2025 15:35
Show Gist options
  • Select an option

  • Save definev/ebf4f92a7e1f817664ea10b0ef837ea2 to your computer and use it in GitHub Desktop.

Select an option

Save definev/ebf4f92a7e1f817664ea10b0ef837ea2 to your computer and use it in GitHub Desktop.
Initial loading splash screen in ZenRouter
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