project-screen

gsmlg-app's avatarfrom gsmlg-app

Guide for creating screens with routing conventions (name/path constants) and adaptive scaffold (project)

⭐1stars🔀0forks📁View on GitHub🕐Updated Jan 7, 2026⬇️1downloads

When & Why to Use This Skill

This Claude skill streamlines Flutter screen development by enforcing project-specific routing conventions and utilizing adaptive scaffolds for responsive design. It automates the creation of UI components using Mason templates, ensuring consistency across mobile, tablet, and desktop platforms while integrating seamlessly with GoRouter and localization workflows.

Use Cases

  • Scenario 1: Rapidly scaffolding new Flutter screens with pre-configured responsive layouts (mobile/tablet/desktop) using Mason templates to reduce boilerplate code.
  • Scenario 2: Implementing standardized routing and navigation constants to maintain a clean, scalable codebase and ensure type-safe navigation with GoRouter.
  • Scenario 3: Building complex UI structures like split-view dashboards or collapsible navigation rails using the AppAdaptiveScaffold's advanced slot system.
  • Scenario 4: Ensuring all new UI components are automatically prepared for localization (i18n) and unit testing by following the skill's mandatory project structure.
nameproject-screen
descriptionGuide 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(),
  ),
),

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);
  },
)
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 only
  • large - 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