A walkthrough of a multi-page app demonstrating navigation, routing, and state management across screens.
- Creating multiple screens
- Navigation with
Navigator.push()andNavigator.pop() - Passing data between screens
- Named routes
- AppBar back button
The routing app has three screens:
Home Screen → Detail Screen → Settings Screen
↑ ↓
└──────────────┘
// lib/main.dart
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
import 'screens/detail_screen.dart';
import 'screens/settings_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'FlutterJS Routing Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true,
),
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/detail': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
},
);
}
}// lib/screens/home_screen.dart
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Welcome to FlutterJS!',
style: Theme.of(context).textTheme.headlineMedium,
),
SizedBox(height: 32),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/detail',
arguments: {
'title': 'Item 1',
'description': 'Details about Item 1',
},
);
},
child: Text('View Item 1'),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
'/detail',
arguments: {
'title': 'Item 2',
'description': 'Details about Item 2',
},
);
},
child: Text('View Item 2'),
),
],
),
),
);
}
}// lib/screens/detail_screen.dart
import 'package:flutter/material.dart';
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Extract arguments
final args = ModalRoute.of(context)!.settings.arguments as Map;
final title = args['title'] as String;
final description = args['description'] as String;
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Details',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 16),
Text(
description,
style: Theme.of(context).textTheme.bodyLarge,
),
SizedBox(height: 32),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go Back'),
),
],
),
),
);
}
}// lib/screens/settings_screen.dart
import 'package:flutter/material.dart';
class SettingsScreen extends StatefulWidget {
@override
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool _notificationsEnabled = true;
bool _darkModeEnabled = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings'),
),
body: ListView(
children: [
SwitchListTile(
title: Text('Enable Notifications'),
value: _notificationsEnabled,
onChanged: (bool value) {
setState(() {
_notificationsEnabled = value;
});
},
),
SwitchListTile(
title: Text('Dark Mode'),
value: _darkModeEnabled,
onChanged: (bool value) {
setState(() {
_darkModeEnabled = value;
});
},
),
Divider(),
ListTile(
title: Text('App Version'),
subtitle: Text('1.0.0'),
),
],
),
);
}
}MaterialApp(
initialRoute: '/', // Starting route
routes: {
'/': (context) => HomeScreen(),
'/detail': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
},
)Benefits:
- Clean navigation:
Navigator.pushNamed(context, '/detail') - Centralized route management
- Easy to maintain
Send data:
Navigator.pushNamed(
context,
'/detail',
arguments: {
'title': 'Item 1',
'description': 'Details about Item 1',
},
);Receive data:
final args = ModalRoute.of(context)!.settings.arguments as Map;
final title = args['title'] as String;AppBar(
title: Text('Home'),
actions: [
IconButton(
icon: Icon(Icons.settings),
onPressed: () {
Navigator.pushNamed(context, '/settings');
},
),
],
)The back button appears automatically when there are routes in the stack!
1. User on HomeScreen (/)
↓
2. Clicks "View Item 1"
↓
3. Navigator.pushNamed('/detail') called
↓
4. DetailScreen pushed onto stack
↓
5. Stack: [HomeScreen, DetailScreen]
↓
6. User sees DetailScreen
1. User on DetailScreen
↓
2. Clicks "Go Back" or AppBar back button
↓
3. Navigator.pop() called
↓
4. DetailScreen removed from stack
↓
5. Stack: [HomeScreen]
↓
6. User sees HomeScreen again
cd examples/routing_appdart run ../../bin/flutterjs.dart run --to-js --serve- Navigate to different screens
- Use back buttons
- Pass different data to detail screen
- Change settings toggles
FlutterJS generates proper navigation:
<!-- Home Screen -->
<div class="flutter-scaffold">
<header class="flutter-appbar">
<h1>Home</h1>
<button class="flutter-icon-button">
<svg><!-- settings icon --></svg>
</button>
</header>
<main>
<!-- Content -->
</main>
</div>
<!-- When navigating to Detail Screen -->
<div class="flutter-scaffold">
<header class="flutter-appbar">
<button class="flutter-back-button">
<svg><!-- back arrow --></svg>
</button>
<h1>Item 1</h1>
</header>
<main>
<p>Details about Item 1</p>
</main>
</div>Notice:
- Back button appears automatically
- Clean URL routing (planned feature)
- Browser back/forward buttons work (planned)
routes: {
'/': (context) => HomeScreen(),
'/detail': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
'/profile': (context) => ProfileScreen(), // New screen
},Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
),
);(Note: Custom transitions are planned for future releases)
void _navigateIfLoggedIn(BuildContext context) {
if (isUserLoggedIn) {
Navigator.pushNamed(context, '/profile');
} else {
Navigator.pushNamed(context, '/login');
}
}lib/
├── main.dart
├── screens/
│ ├── home_screen.dart
│ ├── detail_screen.dart
│ └── settings_screen.dart
└── widgets/
└── custom_card.dart
// lib/routes.dart
class Routes {
static const String home = '/';
static const String detail = '/detail';
static const String settings = '/settings';
}
// Usage
Navigator.pushNamed(context, Routes.detail);MaterialApp(
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundScreen(),
);
},
)- Named routes make navigation cleaner
- Arguments pass data between screens
- AppBar back button appears automatically
- Navigator stack manages route history
- FlutterJS preserves routing in generated HTML
- Learn more about Routing & Navigation
- Try the Counter App Example
- Explore State Management across routes