Bird
Raised Fist0
Fluttermobile~20 mins

Nested navigation in Flutter - Mini App: Build & Ship

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of nested navigation in a Flutter app?
easy
A. To manage navigation inside a specific part of the app separately
B. To replace the main Navigator of the app
C. To create multiple apps inside one Flutter project
D. To disable navigation on certain screens

Solution

  1. Step 1: Understand nested navigation concept

    Nested navigation allows managing navigation stacks inside a part of the app independently from the main app navigation.
  2. Step 2: Identify the purpose

    This helps keep navigation organized and user-friendly by isolating navigation flows.
  3. Final Answer:

    To manage navigation inside a specific part of the app separately -> Option A
  4. Quick Check:

    Nested navigation = Manage part navigation separately [OK]
Hint: Nested navigation isolates navigation flows inside app parts [OK]
Common Mistakes:
  • Thinking nested navigation replaces the main Navigator
  • Confusing nested navigation with multiple apps
  • Assuming nested navigation disables navigation
2. Which Flutter widget is essential to implement nested navigation?
easy
A. MaterialApp
B. Container
C. Scaffold
D. Navigator

Solution

  1. Step 1: Identify widget for navigation

    The Navigator widget manages a stack of routes and is essential for navigation in Flutter.
  2. Step 2: Confirm nested navigation use

    Nested navigation uses a Navigator with a unique key inside a widget subtree.
  3. Final Answer:

    Navigator -> Option D
  4. Quick Check:

    Navigator widget = navigation management [OK]
Hint: Navigator widget controls navigation stacks [OK]
Common Mistakes:
  • Choosing MaterialApp which is for app-wide setup
  • Selecting Scaffold which is for layout
  • Picking Container which is a basic layout widget
3. Given this nested Navigator code snippet, what screen will be shown first inside the nested navigator?
Navigator(
  key: _nestedKey,
  onGenerateRoute: (settings) {
    return MaterialPageRoute(
      builder: (context) => NestedHomeScreen(),
    );
  },
)
medium
A. NestedHomeScreen
B. The main app's home screen
C. An error because onGenerateRoute is missing
D. A blank screen

Solution

  1. Step 1: Analyze onGenerateRoute usage

    The onGenerateRoute callback returns a MaterialPageRoute with NestedHomeScreen as the builder.
  2. Step 2: Determine initial screen

    Since this is the only route generated, NestedHomeScreen will be the first screen shown inside the nested Navigator.
  3. Final Answer:

    NestedHomeScreen -> Option A
  4. Quick Check:

    onGenerateRoute returns NestedHomeScreen [OK]
Hint: onGenerateRoute builder shows initial nested screen [OK]
Common Mistakes:
  • Confusing main app screen with nested screen
  • Assuming missing route causes error
  • Expecting blank screen without routes
4. What is wrong with this nested Navigator code?
Navigator(
  onGenerateRoute: (settings) {
    return MaterialPageRoute(
      builder: (context) => NestedScreen(),
    );
  },
)
It causes navigation issues inside the app.
medium
A. Not wrapping Navigator inside a Scaffold
B. Using MaterialPageRoute instead of CupertinoPageRoute
C. Missing a unique key for the nested Navigator
D. Not defining initialRoute property

Solution

  1. Step 1: Check Navigator setup

    For nested navigation, Navigator should have a unique GlobalKey to manage its own stack separately.
  2. Step 2: Identify missing key

    This code lacks a key, which can cause navigation conflicts or unexpected behavior.
  3. Final Answer:

    Missing a unique key for the nested Navigator -> Option C
  4. Quick Check:

    Nested Navigator needs unique key [OK]
Hint: Always assign a unique key to nested Navigator [OK]
Common Mistakes:
  • Thinking route type causes issues
  • Believing Scaffold is mandatory for Navigator
  • Assuming initialRoute is required
5. You want to create a tabbed interface where each tab has its own navigation stack. Which approach correctly implements nested navigation for this use case?
hard
A. Use a single Navigator for the whole app and switch tabs by pushing routes
B. Use a Navigator with a unique key inside each tab widget to manage its own stack
C. Use multiple MaterialApp widgets, one per tab
D. Use IndexedStack without any Navigator inside tabs

Solution

  1. Step 1: Understand tab navigation needs

    Each tab should maintain its own navigation stack independently to allow back navigation inside tabs.
  2. Step 2: Apply nested Navigator per tab

    Using a Navigator with a unique key inside each tab widget isolates navigation stacks correctly.
  3. Step 3: Evaluate other options

    Single Navigator can't isolate stacks; multiple MaterialApps is heavy and incorrect; IndexedStack alone doesn't handle navigation.
  4. Final Answer:

    Use a Navigator with a unique key inside each tab widget to manage its own stack -> Option B
  5. Quick Check:

    Nested Navigator per tab = isolated stacks [OK]
Hint: Assign unique Navigator per tab for independent navigation [OK]
Common Mistakes:
  • Using one Navigator for all tabs causing stack conflicts
  • Trying multiple MaterialApps which is not recommended
  • Ignoring Navigator inside tabs and losing navigation control
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard