| name | project-screen |
| description | Guide for creating screens with routing conventions (name/path constants) and adaptive scaffold (project) |
Flutter Screen Development Skill
This skill guides the creation of screens following this project's routing and layout conventions.
When to Use
Trigger this skill when:
- Creating a new screen or page
- Adding a route to the application
- User asks to "create a screen", "add a page", or "implement a new view"
Mason Template
Always use Mason template first:
# Basic screen with optional subfolder
mason make screen --name ScreenName --folder subfolder
# Options available:
# --has_adaptive_scaffold: Use AppAdaptiveScaffold (default: true)
# --has_app_bar: Include SliverAppBar (default: true)
Project Structure
Screens are organized in lib/screens/ by domain:
lib/screens/
├── app/ # App-level screens (splash, error)
│ ├── splash_screen.dart
│ └── error_screen.dart
├── home/ # Home feature
│ └── home_screen.dart
├── settings/ # Settings feature
│ ├── settings_screen.dart
│ ├── appearance_settings_screen.dart
│ └── accent_color_settings_screen.dart
└── showcase/ # Demo/example screens
└── showcase_screen.dart
Routing Convention (MANDATORY)
Every screen MUST define static route constants:
class ProfileScreen extends StatelessWidget {
static const name = 'Profile Screen'; // Display name
static const path = '/profile'; // GoRouter path
const ProfileScreen({super.key});
// ...
}
Screen Template
import 'package:app_adaptive_widgets/app_adaptive_widgets.dart';
import 'package:app_locale/app_locale.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_template/destination.dart';
class ExampleScreen extends StatelessWidget {
static const name = 'Example Screen';
static const path = '/example';
const ExampleScreen({super.key});
@override
Widget build(BuildContext context) {
return AppAdaptiveScaffold(
selectedIndex: Destinations.indexOf(const Key(ExampleScreen.name), context),
onSelectedIndexChange: (idx) => Destinations.changeHandler(idx, context),
destinations: Destinations.navs(context),
appBar: AppBar(
title: Text(context.l10n.screenTitle),
centerTitle: true,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
body: (context) => SafeArea(
child: Center(
child: Text('Screen content'),
),
),
);
}
}
Router Integration
After creating a screen, add it to lib/router.dart:
GoRoute(
name: ExampleScreen.name,
path: ExampleScreen.path,
pageBuilder: (context, state) => NoTransitionPage(
key: state.pageKey,
child: const ExampleScreen(),
),
),
Navigation Destinations
For screens in the main navigation, update lib/destination.dart:
static List<NavigationDestination> navs(BuildContext context) {
return [
// existing destinations...
NavigationDestination(
key: Key(ExampleScreen.name),
icon: const Icon(Icons.example),
label: context.l10n.example,
),
];
}
Key Dependencies
import 'package:app_adaptive_widgets/app_adaptive_widgets.dart'; // Responsive layout
import 'package:app_locale/app_locale.dart'; // Localization
import 'package:app_artwork/app_artwork.dart'; // Icons, animations
import 'package:flutter_app_template/destination.dart'; // Navigation
AppAdaptiveScaffold Features
Responsive Body Slots
AppAdaptiveScaffold(
// Main body - used for medium+ breakpoints
body: (context) => MainContent(),
// Small screen body (mobile)
smallBody: (context) => MobileContent(),
// Medium-large screen body (tablet)
mediumLargeBody: (context) => TabletContent(),
// Large screen body (desktop)
largeBody: (context) => DesktopContent(),
// Extra large screen body
extraLargeBody: (context) => WideDesktopContent(),
)
Secondary Body (Split View)
AppAdaptiveScaffold(
body: (context) => ListView(...),
// Secondary panel (detail view)
secondaryBody: (context) => DetailView(),
// Hide secondary on small screens
smallSecondaryBody: AdaptiveScaffold.emptyBuilder,
// Body to secondary ratio (0.3 = 30% body, 70% secondary)
bodyRatio: 0.3,
// Orientation of split
bodyOrientation: Axis.horizontal, // or Axis.vertical
)
Collapsible Navigation Rail
AppAdaptiveScaffold(
// Enable collapse toggle button
showCollapseToggle: true,
// Custom icons for toggle
collapseIcon: Icons.menu_open,
expandIcon: Icons.menu,
// Control extended state externally
isExtendedOverride: isNavExtended,
// Listen for state changes
onExtendedChange: (isExtended) {
setState(() => isNavExtended = isExtended);
},
)
Navigation Rail Customization
AppAdaptiveScaffold(
// Leading widget (collapsed state)
leadingUnextendedNavRail: Icon(Icons.menu),
// Leading widget (extended state)
leadingExtendedNavRail: Text('My App'),
// Trailing widget below destinations
trailingNavRail: IconButton(icon: Icon(Icons.logout), onPressed: logout),
// Rail width
navigationRailWidth: 72,
extendedNavigationRailWidth: 192,
// Destination alignment
groupAlignment: -1.0, // Top aligned
)
Drawer Configuration
AppAdaptiveScaffold(
// Use drawer instead of bottom nav on small desktop
useDrawer: true,
// Custom breakpoint for drawer
drawerBreakpoint: Breakpoints.smallDesktop,
// Custom app bar when using drawer
appBar: AppBar(title: Text('My App')),
// Breakpoint to show app bar
appBarBreakpoint: Breakpoints.small,
)
Breakpoints Reference
Breakpoints.small- Mobile (< 600px)Breakpoints.medium- Tablet (600-840px)Breakpoints.mediumLarge- Small desktop (840-1200px)Breakpoints.large- Desktop (1200-1600px)Breakpoints.extraLarge- Wide desktop (> 1600px)
AppAdaptiveAction (Responsive Action Buttons)
For action buttons that adapt to screen size:
// Define actions
final actions = [
AppAdaptiveAction(
title: 'Edit',
icon: Icons.edit,
onPressed: () => handleEdit(),
),
AppAdaptiveAction(
title: 'Delete',
icon: Icons.delete,
onPressed: () => handleDelete(),
disabled: !canDelete,
),
];
// Display in AppBar or elsewhere
AppAdaptiveActionList(
actions: actions,
size: AppAdaptiveActionSize.medium, // small, medium, large
direction: Axis.horizontal,
hideDisabled: true,
)
Action Sizes:
small- Popup menu (overflow menu)medium- Icon buttons onlylarge- Text buttons with icons
Localization
Add screen text to app_lib/locale/lib/l10n/app_en.arb:
{
"exampleTitle": "Example",
"@exampleTitle": {
"description": "Title for the example screen"
}
}
Then run: melos run gen-l10n
Testing
Create screen tests in test/screens/:
testWidgets('ExampleScreen renders correctly', (tester) async {
await tester.pumpWidget(
MaterialApp(home: ExampleScreen()),
);
expect(find.text('Example'), findsOneWidget);
});
Run tests: flutter test test/screens/example_screen_test.dart