0
0
Fluttermobile~20 mins

Nested navigation in Flutter - Mini App: Build & Ship

Choose your learning style9 modes available
Build: Nested Navigation Demo
Build a Flutter app with a bottom navigation bar that switches between two tabs. Each tab has its own navigation stack with separate pages.
Target UI
┌───────────────────────────────┐
│ Nested Navigation Demo         │
├───────────────────────────────┤
│ [Home Tab]                    │
│  - Home Page                  │
│  - Details Page (push)        │
│                               │
│ [Settings Tab]                │
│  - Settings Page              │
│  - Profile Page (push)        │
├───────────────────────────────┤
│  Home  |  Settings            │
└───────────────────────────────┘
Use a BottomNavigationBar with two tabs: Home and Settings.
Each tab uses its own Navigator to maintain separate navigation stacks.
Home tab starts with HomePage and can navigate to DetailsPage.
Settings tab starts with SettingsPage and can navigate to ProfilePage.
Switching tabs preserves each tab's navigation stack state.
Starter Code
Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nested Navigation Demo',
      home: const NestedNavigationScreen(),
    );
  }
}

class NestedNavigationScreen extends StatefulWidget {
  const NestedNavigationScreen({super.key});

  @override
  State<NestedNavigationScreen> createState() => _NestedNavigationScreenState();
}

class _NestedNavigationScreenState extends State<NestedNavigationScreen> {
  int _currentIndex = 0;

  // TODO: Add keys and navigators for each tab

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: // TODO: Show the current tab's navigator
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }
}

// TODO: Create HomePage, DetailsPage, SettingsPage, ProfilePage widgets
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Flutter
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nested Navigation Demo',
      home: const NestedNavigationScreen(),
    );
  }
}

class NestedNavigationScreen extends StatefulWidget {
  const NestedNavigationScreen({super.key});

  @override
  State<NestedNavigationScreen> createState() => _NestedNavigationScreenState();
}

class _NestedNavigationScreenState extends State<NestedNavigationScreen> {
  int _currentIndex = 0;

  final GlobalKey<NavigatorState> _homeNavigatorKey = GlobalKey<NavigatorState>();
  final GlobalKey<NavigatorState> _settingsNavigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final isFirstRouteInCurrentTab = await _currentIndex == 0
            ? !_homeNavigatorKey.currentState!.canPop()
            : !_settingsNavigatorKey.currentState!.canPop();
        if (isFirstRouteInCurrentTab) {
          // If user is on first route of current tab, allow app to close
          return true;
        } else {
          // Pop the current tab's navigator
          if (_currentIndex == 0) {
            _homeNavigatorKey.currentState!.pop();
          } else {
            _settingsNavigatorKey.currentState!.pop();
          }
          return false;
        }
      },
      child: Scaffold(
        body: Stack(
          children: [
            Offstage(
              offstage: _currentIndex != 0,
              child: Navigator(
                key: _homeNavigatorKey,
                onGenerateRoute: (routeSettings) {
                  return MaterialPageRoute(
                    builder: (context) => const HomePage(),
                  );
                },
              ),
            ),
            Offstage(
              offstage: _currentIndex != 1,
              child: Navigator(
                key: _settingsNavigatorKey,
                onGenerateRoute: (routeSettings) {
                  return MaterialPageRoute(
                    builder: (context) => const SettingsPage(),
                  );
                },
              ),
            ),
          ],
        ),
        bottomNavigationBar: BottomNavigationBar(
          currentIndex: _currentIndex,
          onTap: (index) {
            setState(() {
              _currentIndex = index;
            });
          },
          items: const [
            BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
            BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
          ],
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => const DetailsPage()),
            );
          },
          child: const Text('Go to Details'),
        ),
      ),
    );
  }
}

class DetailsPage extends StatelessWidget {
  const DetailsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Details Page')),
      body: const Center(child: Text('Details content here')),
    );
  }
}

class SettingsPage extends StatelessWidget {
  const SettingsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => const ProfilePage()),
            );
          },
          child: const Text('Go to Profile'),
        ),
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile Page')),
      body: const Center(child: Text('Profile content here')),
    );
  }
}

This Flutter app uses a BottomNavigationBar with two tabs: Home and Settings.

Each tab has its own Navigator with a unique GlobalKey. This allows each tab to keep its own navigation stack separately.

The Stack widget with Offstage widgets shows only the active tab's navigator, hiding the other but preserving its state.

Pressing buttons on each tab pushes new pages onto that tab's navigator stack.

The WillPopScope intercepts the back button to pop pages from the current tab's stack before exiting the app.

This setup mimics real apps where each tab has independent navigation history.

Final Result
Completed Screen
┌───────────────────────────────┐
│ Nested Navigation Demo         │
├───────────────────────────────┤
│ Home Page                     │
│                               │
│ [Go to Details] (button)      │
│                               │
├───────────────────────────────┤
│  Home  |  Settings            │
└───────────────────────────────┘
Tap 'Go to Details' navigates to Details Page inside Home tab.
Tap 'Settings' tab switches to Settings Page.
Tap 'Go to Profile' on Settings tab navigates to Profile Page.
Switching tabs preserves each tab's navigation stack.
Back button pops pages inside current tab before exiting app.
Stretch Goal
Add a badge with the number '3' on the Settings tab icon.
💡 Hint
Use Stack widget to overlay a small red circle with text on the BottomNavigationBarItem icon.