Skip to content

Instantly share code, notes, and snippets.

@dukex
Last active July 11, 2022 01:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dukex/38be63ebd8a6f9f0a209a4f6949080b4 to your computer and use it in GitHub Desktop.
Save dukex/38be63ebd8a6f9f0a209a4f6949080b4 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
final books = [
const Book(id: '3160', name: 'The Odyssey', author: 'Homer'),
const Book(id: '8795', name: 'The Divine Comedy', author: 'Dante Alighieri'),
];
void main() {
final betterRoutes = BetterRouter(routes: {
'/': (_) => const HomeScreen(),
'/books': (_) => const BooksScreen(),
r"\/books\/(?<id>.+)": (_) => const BookScreen(),
'-matchAll': (_) => const Text('not found page'),
});
runApp(MaterialApp(onGenerateRoute: betterRoutes));
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return AppShell(
title: const Text("Welcome"),
body: TextButton(
onPressed: () => Navigator.pushNamed(context, "/books"),
child: const Text("Go to books")));
}
}
class Book {
final String id;
final String name;
final String author;
const Book({required this.id, required this.name, required this.author});
}
class BooksScreen extends StatelessWidget {
const BooksScreen({super.key});
@override
Widget build(BuildContext context) {
return AppShell(
title: const Text("Books"),
body: Column(children: [
for (var i = 0; i < books.length; i++)
TextButton(
onPressed: () =>
Navigator.pushNamed(context, "/books/${books[i].id}"),
child: Text(books[i].name))
]));
}
}
class BookScreen extends StatelessWidget {
const BookScreen({super.key});
@override
Widget build(BuildContext context) {
final params =
ModalRoute.of(context)!.settings.arguments as Map<String, String?>;
final book = books.firstWhere((b) => b.id == params["id"]!);
return AppShell(
title: Text(book.name),
body: Column(
children: [Text("By ${book.author}"), Text("ID: ${params['id']}")],
));
}
}
class AppShell extends StatelessWidget {
final Widget body;
final Widget title;
const AppShell({super.key, required this.body, required this.title});
@override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: title), body: body);
}
}
class RouteInfo {
final String path;
final Map<String, String?> data;
RouteInfo({required this.path, required this.data});
}
class Matcher {
final String path;
final RegExp matcher;
const Matcher(this.path, this.matcher);
RouteInfo? matchWith(expPath) {
final match = matcher.firstMatch(expPath);
if (match == null) {
return null;
}
final values =
List.generate(match.groupCount, (index) => match.group(index + 1));
final data = Map.fromIterables(match.groupNames, values);
return RouteInfo(path: path, data: data);
}
}
class BetterRouter {
final Map<String, WidgetBuilder> routes;
BetterRouter({required this.routes}) : assert(routes['-matchAll'] != null);
pageRouteBuilder<T>(RouteSettings settings, WidgetBuilder builder) {
return MaterialPageRoute<T>(settings: settings, builder: builder);
}
Route<dynamic>? call(RouteSettings settings) {
final String path = settings.name!;
RouteInfo routeInfo = _routeInfo(path);
final WidgetBuilder pageContentBuilder = routes[routeInfo.path]!;
final Route<dynamic> route = pageRouteBuilder<dynamic>(
settings.copyWith(arguments: routeInfo.data),
pageContentBuilder,
);
return route;
}
Iterable<Matcher> get _mapper =>
routes.keys.map((path) => Matcher(path, RegExp("^$path\$")));
final _mapped = <String, RouteInfo>{};
RouteInfo _routeInfo(path) => _mapped.putIfAbsent(
path,
() => _mapper.map((m) => m.matchWith(path)).lastWhere(
(element) => element != null,
orElse: () => RouteInfo(path: "-matchAll", data: {}),
)!);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment