ark_navigation 0.1.0-dev.1
ark_navigation: ^0.1.0-dev.1 copied to clipboard
Typed modular Flutter navigation with explicit stack operations, deep links, nested scopes, and no code generation.
example/ark_navigation_example.dart
import 'package:ark_navigation/ark_navigation.dart' as ark;
import 'package:flutter/material.dart';
void main() {
runApp(const ExampleHost());
}
final class HomeDestination extends ark.NavigationDestination<void> {
const HomeDestination();
}
final class ProductDestination extends ark.NavigationDestination<bool> {
const ProductDestination({required this.productId});
final String productId;
}
final class ExampleHost extends StatefulWidget {
const ExampleHost({super.key});
@override
State<ExampleHost> createState() => _ExampleHostState();
}
final class _ExampleHostState extends State<ExampleHost> {
late final ark.NavigationSession _session = ark.NavigationSession(
id: const ark.NavigationSessionId('example'),
graph: _createGraph(),
);
@override
void dispose() {
_session.dispose();
super.dispose();
}
@override
Widget build(final BuildContext context) {
return MaterialApp.router(
title: 'Ark Navigation example',
routerConfig: _session.routerConfig,
);
}
}
ark.NavigationGraph _createGraph() {
const ark.NavigationScopeId rootScopeId = ark.NavigationScopeId('root');
const ark.NavigationModuleId moduleId = ark.NavigationModuleId('catalog');
return ark.NavigationGraph(
rootScopeId: rootScopeId,
scopes: const <ark.NavigationScopeDefinition>[
ark.NavigationScopeDefinition(
id: rootScopeId,
restorationScopeId: 'example-root',
),
],
modules: <ark.NavigationModule>[
ark.NavigationModule(
id: moduleId,
routes: <ark.NavigationRouteDefinitionBase>[
ark.NavigationRouteDefinition<HomeDestination, void>(
id: const ark.NavigationRouteId('home'),
pageBuilder: (final context, final destination, final entry) =>
const HomePage(),
uriCodec: ark.CallbackNavigationUriCodec<HomeDestination>(
pattern: '/',
encoder: (final destination) => Uri(path: '/'),
decoder: (final match) => const HomeDestination(),
),
),
ark.NavigationRouteDefinition<ProductDestination, bool>(
id: const ark.NavigationRouteId('product'),
pageBuilder: (final context, final destination, final entry) =>
ProductPage(productId: destination.productId),
uriCodec: ark.CallbackNavigationUriCodec<ProductDestination>(
pattern: '/products/:productId',
encoder: (final destination) => Uri(
pathSegments: <String>['products', destination.productId],
),
decoder: (final match) =>
ProductDestination(productId: match.path('productId')),
),
stackPlanBuilder: (final destination) =>
<ark.NavigationTarget<Object?>>[
const ark.NavigationTarget<Object?>(
destination: HomeDestination(),
),
ark.NavigationTarget<Object?>(destination: destination),
],
),
],
),
],
mounts: <ark.NavigationMount>[
ark.NavigationMount(
id: const ark.NavigationMountId('catalog-root'),
moduleId: moduleId,
scopeId: rootScopeId,
),
],
);
}
final class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
final class _HomePageState extends State<HomePage> {
String _message = 'No result yet';
Future<void> _openProduct() async {
final ark.NavigationTicket<bool> ticket =
ark.ArkNavigationBuildContextExtension(context).readNavigation
.push<bool>(const ProductDestination(productId: 'keyboard-42'));
final ark.NavigationOperationResult commit = await ticket.committed;
if (commit is! ark.NavigationCommitted) {
if (mounted) {
setState(() => _message = 'Navigation was not committed');
}
return;
}
final ark.NavigationCompletion<bool> completion = await ticket.completed;
if (!mounted) {
return;
}
setState(() {
_message = switch (completion) {
ark.NavigationPopped<bool>(:final result) => 'Purchased: $result',
ark.NavigationRemoved<bool>() => 'Product route was replaced',
ark.NavigationNotCommitted<bool>() => 'Navigation was not committed',
};
});
}
@override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Catalog')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(_message),
const SizedBox(height: 16),
FilledButton(
onPressed: _openProduct,
child: const Text('Open product'),
),
],
),
),
);
}
}
final class ProductPage extends StatelessWidget {
const ProductPage({required this.productId, super.key});
final String productId;
@override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Product $productId')),
body: Center(
child: FilledButton(
onPressed: () => ark.ArkNavigationBuildContextExtension(
context,
).readNavigation.pop(result: true),
child: const Text('Buy and return a result'),
),
),
);
}
}