Popular Searches
Popular Course Categories
Popular Courses

Flutter Bottom Navigation

Flutter Bottom Navigation

Flutter Navigation & Screens


Flutter Bottom Navigation: Sending Information from One Screen to Another


Bottom navigation is commonly used in Flutter applications to switch between important sections such as Home, Products, Cart, Profile, and Settings. In modern Material 3 Flutter applications, NavigationBar is the recommended Material widget for persistent navigation between primary destinations. Flutter also provides BottomNavigationBar for older Material 2 implementations. Flutter Material Widgets Documentation


When using bottom navigation, different sections often need to share or receive information. For example, a product selected on the Home tab may need to be displayed on the Cart tab, or a selected category may need to affect the Products tab.




1. What Is Bottom Navigation?


Bottom navigation is a persistent navigation component placed at the bottom of the application interface. It allows users to switch between the main sections of an application.


A typical application may have:



  • Home

  • Products

  • Cart

  • Orders

  • Profile


Conceptually, the structure looks like this:


                Flutter App
                    |
        +-----------+-----------+
        |           |           |
      Home       Products      Profile
        |           |           |
        +-----------+-----------+
                    |
             Bottom Navigation



2. NavigationBar vs BottomNavigationBar


Flutter's Material 3 design uses NavigationBar. Flutter's Material 3 migration documentation recommends replacing the older Material 2 BottomNavigationBar with NavigationBar in Material 3 applications. Flutter Material 3 Migration Guide



























WidgetDesign SystemTypical Usage
NavigationBarMaterial 3Modern Flutter applications
BottomNavigationBarMaterial 2Existing or older applications
CupertinoTabBarCupertino/iOSiOS-style applications



3. Basic Bottom Navigation Using NavigationBar


The selected bottom-navigation item is normally represented by an integer index. When the user selects another destination, setState() can update the selected index.


import 'package:flutter/material.dart';

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

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;

  final List<Widget> screens = const [
    HomeScreen(),
    ProductsScreen(),
    ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: screens[selectedIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            selectedIcon: Icon(Icons.home),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_bag_outlined),
            selectedIcon: Icon(Icons.shopping_bag),
            label: 'Products',
          ),
          NavigationDestination(
            icon: Icon(Icons.person_outline),
            selectedIcon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
      ),
    );
  }
}




4. Creating Individual Screens


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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Home Screen',
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Products Screen',
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Profile Screen',
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}




5. What Does Sending Information Mean in Bottom Navigation?


Bottom navigation changes the currently displayed destination. However, applications often need data to move between those destinations.


For example:


Home
 |
 | Selected Product
 v
Products
 |
 | Add Product
 v
Cart
 |
 | User Information
 v
Profile

There are several ways to handle this information:



  • Pass data directly through widget constructors.

  • Keep shared state in a common parent widget.

  • Use callbacks to send information upward.

  • Use a shared model or state-management solution.

  • Use a route when a destination should open as a separate screen.




6. Passing Data Through a Common Parent


One of the simplest approaches for a small application is to keep shared data in the widget that owns the bottom navigation and pass that data to the selected screen.


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

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;

  String selectedProduct = 'Flutter Course';

  @override
  Widget build(BuildContext context) {
    final screens = [
      HomeScreen(
        onProductSelected: (product) {
          setState(() {
            selectedProduct = product;
          });
        },
      ),
      ProductsScreen(
        selectedProduct: selectedProduct,
      ),
      ProfileScreen(
        selectedProduct: selectedProduct,
      ),
    ];

    return Scaffold(
      body: screens[selectedIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_bag_outlined),
            label: 'Products',
          ),
          NavigationDestination(
            icon: Icon(Icons.person_outline),
            label: 'Profile',
          ),
        ],
      ),
    );
  }
}




7. Sending Information From Home to Products


Suppose the Home screen contains a button for selecting a course. The selected course can be sent back to the parent through a callback.


class HomeScreen extends StatelessWidget {
  final ValueChanged<String> onProductSelected;

  const HomeScreen({
    super.key,
    required this.onProductSelected,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
        onPressed: () {
          onProductSelected('Flutter Course');
        },
        child: const Text('Select Flutter Course'),
      ),
    );
  }
}


The parent receives the selected value:


HomeScreen(
  onProductSelected: (product) {
    setState(() {
      selectedProduct = product;
    });
  },
)

The Products screen can then receive the updated value:


class ProductsScreen extends StatelessWidget {
  final String selectedProduct;

  const ProductsScreen({
    super.key,
    required this.selectedProduct,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        'Selected: $selectedProduct',
        style: const TextStyle(fontSize: 22),
      ),
    );
  }
}




8. Sending a Custom Object Between Bottom Navigation Screens


For real applications, passing a complete object is often more useful than passing individual values.


Product Model


class Product {
  final int id;
  final String name;
  final double price;

  const Product({
    required this.id,
    required this.name,
    required this.price,
  });
}


Store the Selected Product


Product? selectedProduct;

Pass It to Another Destination


ProductsScreen(
  product: selectedProduct,
)

Receive It


class ProductsScreen extends StatelessWidget {
  final Product? product;

  const ProductsScreen({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    if (product == null) {
      return const Center(
        child: Text('No product selected'),
      );
    }

    return Center(
      child: Text(
        '${product!.name} - ₹${product!.price}',
      ),
    );
  }
}




9. Passing Information From Home to Cart


A common example is adding an item on the Home or Products tab and displaying it in the Cart tab.


class CartItem {
  final String name;
  final double price;

  const CartItem({
    required this.name,
    required this.price,
  });
}


Maintain the cart in the parent:


final List<CartItem> cartItems = [];

Add an item:


setState(() {
  cartItems.add(
    const CartItem(
      name: 'Flutter Course',
      price: 4999,
    ),
  );
});

Pass the cart to the Cart screen:


CartScreen(
  items: cartItems,
)

Cart Screen


class CartScreen extends StatelessWidget {
  final List<CartItem> items;

  const CartScreen({
    super.key,
    required this.items,
  });

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        final item = items[index];

        return ListTile(
          title: Text(item.name),
          subtitle: Text(
            '₹${item.price.toStringAsFixed(0)}',
          ),
        );
      },
    );
  }
}




10. Complete Bottom Navigation With Shared Data


The following example demonstrates a Home, Products, Cart, and Profile navigation structure. The selected product is stored in the parent and can be accessed by other destinations.


import 'package:flutter/material.dart';

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

class Product {
  final int id;
  final String name;
  final double price;

  const Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Bottom Navigation',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const MainScreen(),
    );
  }
}

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

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;

  Product? selectedProduct;

  final List<Product> products = const [
    Product(
      id: 1,
      name: 'Flutter Course',
      price: 4999,
    ),
    Product(
      id: 2,
      name: 'Dart Course',
      price: 3999,
    ),
    Product(
      id: 3,
      name: 'UI/UX Course',
      price: 2999,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    final screens = [
      HomeScreen(
        onProductSelected: (product) {
          setState(() {
            selectedProduct = product;
          });
        },
      ),
      ProductsScreen(
        products: products,
        selectedProduct: selectedProduct,
        onProductSelected: (product) {
          setState(() {
            selectedProduct = product;
          });
        },
      ),
      CartScreen(
        selectedProduct: selectedProduct,
      ),
      ProfileScreen(
        selectedProduct: selectedProduct,
      ),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter App'),
      ),
      body: screens[selectedIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            selectedIcon: Icon(Icons.home),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_bag_outlined),
            selectedIcon: Icon(Icons.shopping_bag),
            label: 'Products',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_cart_outlined),
            selectedIcon: Icon(Icons.shopping_cart),
            label: 'Cart',
          ),
          NavigationDestination(
            icon: Icon(Icons.person_outline),
            selectedIcon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  final ValueChanged<Product> onProductSelected;

  const HomeScreen({
    super.key,
    required this.onProductSelected,
  });

  @override
  Widget build(BuildContext context) {
    const product = Product(
      id: 1,
      name: 'Flutter Course',
      price: 4999,
    );

    return Center(
      child: ElevatedButton(
        onPressed: () {
          onProductSelected(product);
        },
        child: const Text('Select Flutter Course'),
      ),
    );
  }
}

class ProductsScreen extends StatelessWidget {
  final List<Product> products;
  final Product? selectedProduct;
  final ValueChanged<Product> onProductSelected;

  const ProductsScreen({
    super.key,
    required this.products,
    required this.selectedProduct,
    required this.onProductSelected,
  });

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];

        return ListTile(
          title: Text(product.name),
          subtitle: Text(
            '₹${product.price.toStringAsFixed(0)}',
          ),
          trailing: product.id == selectedProduct?.id
              ? const Icon(Icons.check)
              : null,
          onTap: () {
            onProductSelected(product);
          },
        );
      },
    );
  }
}

class CartScreen extends StatelessWidget {
  final Product? selectedProduct;

  const CartScreen({
    super.key,
    required this.selectedProduct,
  });

  @override
  Widget build(BuildContext context) {
    if (selectedProduct == null) {
      return const Center(
        child: Text('Cart is empty'),
      );
    }

    return Center(
      child: Text(
        'Cart: ${selectedProduct!.name}',
        style: const TextStyle(fontSize: 22),
      ),
    );
  }
}

class ProfileScreen extends StatelessWidget {
  final Product? selectedProduct;

  const ProfileScreen({
    super.key,
    required this.selectedProduct,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        selectedProduct == null
            ? 'No product selected'
            : 'Selected Product: ${selectedProduct!.name}',
        style: const TextStyle(fontSize: 20),
      ),
    );
  }
}




11. Important Concept: Bottom Navigation Is Not the Same as Push Navigation


It is important to understand the difference between switching bottom-navigation destinations and pushing a completely new route.































Bottom NavigationNavigator.push()
Switches between primary destinationsOpens a new route
Usually remains visibleUsually replaced by the new screen
Uses selected index or navigation stateUses Navigator route stack
Suitable for Home, Cart, Profile, SettingsSuitable for Details, Edit, Checkout, Forms
Often keeps top-level navigation persistentCreates a navigation stack entry

Flutter's navigation system uses the Navigator as a stack of routes, while NavigationBar is intended for switching between primary destinations. Flutter Navigation and Routing




12. Opening a Detail Screen From Bottom Navigation


Sometimes a bottom-navigation destination contains a list, and tapping an item should open a separate detail route. In this case, bottom navigation and normal Navigator-based navigation can be used together.


onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) {
        return ProductDetailsScreen(
          product: product,
        );
      },
    ),
  );
}

The flow becomes:


Bottom Navigation
       |
       v
Products Tab
       |
       | Tap Product
       v
Product Details Route
       |
       | Back
       v
Products Tab

This is useful when the detail page should temporarily appear above the bottom-navigation interface.




13. Sending Data to a Detail Screen From a Bottom Navigation Tab


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

  @override
  Widget build(BuildContext context) {
    final product = const Product(
      id: 10,
      name: 'Flutter Training',
      price: 4999,
    );

    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) {
                  return ProductDetailsScreen(
                    product: product,
                  );
                },
              ),
            );
          },
          child: const Text('View Product'),
        ),
      ),
    );
  }
}

class ProductDetailsScreen extends StatelessWidget {
  final Product product;

  const ProductDetailsScreen({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Center(
        child: Text(
          '₹${product.price}',
          style: const TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}




14. Returning Information From a Detail Screen


A detail screen can return a result to the bottom-navigation tab using Navigator.pop().


Navigator.pop(
  context,
  'Product Added',
);

The Products tab can wait for the result:


final result = await Navigator.push<String>(
  context,
  MaterialPageRoute(
    builder: (context) {
      return ProductDetailsScreen(
        product: product,
      );
    },
  ),
);

if (result != null) {
  print(result);
}


Flutter's official navigation documentation uses this push/pop pattern to return information from one route to another. Flutter: Return Data From a Screen




15. Passing Data With Callbacks


A callback is a function passed from a parent widget to a child widget. The child can call that function when information needs to be sent back to the parent.


Parent


HomeScreen(
  onSelected: (value) {
    setState(() {
      selectedValue = value;
    });
  },
)

Child


class HomeScreen extends StatelessWidget {
  final ValueChanged<String> onSelected;

  const HomeScreen({
    super.key,
    required this.onSelected,
  });

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        onSelected('Flutter');
      },
      child: const Text('Select'),
    );
  }
}


Callbacks are useful when the data only needs to travel from a child destination back to the widget that owns the bottom-navigation state.




16. Sending Data Between Tabs Using a Shared Parent


When several tabs need access to the same data, the parent widget can own that state.


class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;
  int cartCount = 0;
  String? selectedCategory;

  void addToCart() {
    setState(() {
      cartCount++;
    });
  }

  void selectCategory(String category) {
    setState(() {
      selectedCategory = category;
    });
  }
}


Now different destinations can receive the same data:


HomeScreen(
  onAddToCart: addToCart,
);

ProductsScreen(
  selectedCategory: selectedCategory,
);

CartScreen(
  cartCount: cartCount,
);




17. Bottom Navigation With a Shared Cart Counter


A common application requirement is displaying the number of items in the cart while switching between Home, Products, and Profile.


int cartCount = 0;

Update the count:


setState(() {
  cartCount++;
});

Display the count in the navigation destination:


NavigationDestination(
  icon: Badge(
    label: Text('$cartCount'),
    child: const Icon(
      Icons.shopping_cart_outlined,
    ),
  ),
  selectedIcon: Badge(
    label: Text('$cartCount'),
    child: const Icon(
      Icons.shopping_cart,
    ),
  ),
  label: 'Cart',
)

This pattern allows the parent navigation widget to control the shared cart state while the Cart tab displays the corresponding information.




18. Bottom Navigation With a User Object


Suppose a logged-in user needs to be available to Home, Orders, and Profile.


class User {
  final int id;
  final String name;
  final String email;

  const User({
    required this.id,
    required this.name,
    required this.email,
  });
}


Create the user:


const user = User(
  id: 101,
  name: 'Manish',
  email: '[email protected]',
);

Pass it to different tabs:


HomeScreen(
  user: user,
);

OrdersScreen(
  user: user,
);

ProfileScreen(
  user: user,
);




19. State Management for Larger Bottom Navigation Applications


For a small application, keeping shared information in the parent of the bottom-navigation destinations can be sufficient. As the application grows, multiple screens may need to read and update the same state.


Flutter's state-management documentation explains approaches for managing data shared across screens and throughout an application. Flutter State Management Documentation


Common state-management approaches include:



  • Provider

  • Riverpod

  • Bloc/Cubit

  • ChangeNotifier

  • ValueNotifier

  • InheritedWidget

  • Other application-specific state architectures


The basic principle is:


                 Shared Application State
                         |
          +--------------+--------------+
          |              |              |
        Home           Cart           Profile
          |              |              |
          +--------------+--------------+
                         |
                  Bottom Navigation



20. Using IndexedStack With Bottom Navigation


An IndexedStack can be useful when you want to keep multiple tab widgets in the widget tree while displaying only the selected one.


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

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;

  final screens = const [
    HomeScreen(),
    ProductsScreen(),
    CartScreen(),
    ProfileScreen(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: selectedIndex,
        children: screens,
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_bag_outlined),
            label: 'Products',
          ),
          NavigationDestination(
            icon: Icon(Icons.shopping_cart_outlined),
            label: 'Cart',
          ),
          NavigationDestination(
            icon: Icon(Icons.person_outline),
            label: 'Profile',
          ),
        ],
      ),
    );
  }
}


IndexedStack displays one child at a time based on the index while keeping the other children in the widget tree, which can be useful for preserving tab-specific widget state.




21. Nested Navigation With Bottom Navigation


More complex applications may require each bottom-navigation tab to have its own navigation stack.


For example:


Home Tab
  |
  +-- Home
  +-- Product Details
  +-- Checkout

Profile Tab
  |
  +-- Profile
  +-- Edit Profile
  +-- Settings


In such an architecture, each primary destination can have its own navigation history. Flutter's navigation documentation notes that applications with multiple Navigator widgets are among the scenarios where Router-based navigation can be useful. Flutter Navigation and Routing




22. Bottom Navigation With Named Routes


Named routes can be used for navigation, although Flutter currently does not recommend named routes for most new applications. For learning or existing applications, they may still be encountered.


MaterialApp(
  routes: {
    '/home': (context) => const HomeScreen(),
    '/products': (context) => const ProductsScreen(),
    '/profile': (context) => const ProfileScreen(),
  },
);

Navigation:


Navigator.pushNamed(
  context,
  '/products',
);

Flutter's documentation recommends considering go_router or Navigator with MaterialPageRoute for most new applications instead of relying on named routes. Flutter Named Routes Documentation




23. Passing Arguments With Named Routes


If a named route is used, information can be passed through the arguments parameter.


Navigator.pushNamed(
  context,
  '/product-details',
  arguments: product,
);

The destination can read the argument:


final product =
    ModalRoute.of(context)!.settings.arguments as Product;

Flutter provides an official example for passing arguments to named routes. Pass Arguments to a Named Route




24. Bottom Navigation and Deep Linking


When an application needs URLs that directly open specific content, such as a product or profile page, navigation becomes more complex.


For example:


myapp.com/home
myapp.com/products
myapp.com/products/101
myapp.com/profile

Flutter supports deep linking, and applications with advanced navigation and deep-linking requirements can use Router-based navigation or a routing package such as go_router. Flutter Deep Linking Documentation




25. NavigationBar With Different Icons


NavigationBar(
  selectedIndex: selectedIndex,
  onDestinationSelected: (index) {
    setState(() {
      selectedIndex = index;
    });
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.home_outlined),
      selectedIcon: Icon(Icons.home),
      label: 'Home',
    ),
    NavigationDestination(
      icon: Icon(Icons.search_outlined),
      selectedIcon: Icon(Icons.search),
      label: 'Search',
    ),
    NavigationDestination(
      icon: Icon(Icons.favorite_outline),
      selectedIcon: Icon(Icons.favorite),
      label: 'Favorites',
    ),
    NavigationDestination(
      icon: Icon(Icons.person_outline),
      selectedIcon: Icon(Icons.person),
      label: 'Profile',
    ),
  ],
)



26. Passing Search Information Between Tabs


Suppose a user searches for a product in the Home tab and the Products tab should display the selected search query.


String searchQuery = '';

void updateSearch(String query) {
  setState(() {
    searchQuery = query;
  });
}


Home:


HomeScreen(
  onSearch: updateSearch,
)

Products:


ProductsScreen(
  searchQuery: searchQuery,
)

Products screen:


class ProductsScreen extends StatelessWidget {
  final String searchQuery;

  const ProductsScreen({
    super.key,
    required this.searchQuery,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
        searchQuery.isEmpty
            ? 'All Products'
            : 'Searching for: $searchQuery',
      ),
    );
  }
}




27. Practical Example: Home to Cart


Consider an e-commerce application with four bottom-navigation destinations:


Home | Products | Cart | Profile

When a product is selected on Home:


Home
 |
 | Product
 v
Parent State
 |
 v
Cart

The parent maintains the cart:


final List<Product> cart = [];

Add the selected product:


void addToCart(Product product) {
  setState(() {
    cart.add(product);
  });
}

Pass it to Cart:


CartScreen(
  cart: cart,
)

This creates a simple one-way data flow that is easy to understand in small applications.




28. Practical Example: Profile Information Across Tabs


class User {
  final String name;
  final String email;

  const User({
    required this.name,
    required this.email,
  });
}


Store the user at the parent level:


const user = User(
  name: 'Manish',
  email: '[email protected]',
);

Pass the user to multiple tabs:


HomeScreen(user: user);
OrdersScreen(user: user);
ProfileScreen(user: user);

This avoids duplicating the same user data in each screen.




29. Complete Bottom Navigation Example With Data Transfer


import 'package:flutter/material.dart';

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

class Product {
  final int id;
  final String name;
  final double price;

  const Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Bottom Navigation Data',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const MainScreen(),
    );
  }
}

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

  @override
  State<MainScreen> createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int selectedIndex = 0;

  final List<Product> cart = [];

  final products = const [
    Product(
      id: 1,
      name: 'Flutter Course',
      price: 4999,
    ),
    Product(
      id: 2,
      name: 'Dart Course',
      price: 3999,
    ),
    Product(
      id: 3,
      name: 'UI/UX Course',
      price: 2999,
    ),
  ];

  void addToCart(Product product) {
    setState(() {
      cart.add(product);
    });
  }

  @override
  Widget build(BuildContext context) {
    final screens = [
      HomeScreen(
        products: products,
        onAddToCart: addToCart,
      ),
      ProductsScreen(
        products: products,
        onAddToCart: addToCart,
      ),
      CartScreen(
        cart: cart,
      ),
      const ProfileScreen(),
    ];

    return Scaffold(
      appBar: AppBar(
        title: const Text('My Flutter App'),
      ),
      body: screens[selectedIndex],
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) {
          setState(() {
            selectedIndex = index;
          });
        },
        destinations: [
          const NavigationDestination(
            icon: Icon(Icons.home_outlined),
            selectedIcon: Icon(Icons.home),
            label: 'Home',
          ),
          const NavigationDestination(
            icon: Icon(Icons.shopping_bag_outlined),
            selectedIcon: Icon(Icons.shopping_bag),
            label: 'Products',
          ),
          NavigationDestination(
            icon: Badge(
              isLabelVisible: cart.isNotEmpty,
              label: Text('${cart.length}'),
              child: const Icon(
                Icons.shopping_cart_outlined,
              ),
            ),
            selectedIcon: Badge(
              isLabelVisible: cart.isNotEmpty,
              label: Text('${cart.length}'),
              child: const Icon(
                Icons.shopping_cart,
              ),
            ),
            label: 'Cart',
          ),
          const NavigationDestination(
            icon: Icon(Icons.person_outline),
            selectedIcon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  final List<Product> products;
  final ValueChanged<Product> onAddToCart;

  const HomeScreen({
    super.key,
    required this.products,
    required this.onAddToCart,
  });

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Text(
          'Featured Products',
          style: Theme.of(context)
              .textTheme
              .headlineSmall,
        ),
        const SizedBox(height: 15),
        ...products.map(
          (product) {
            return Card(
              child: ListTile(
                title: Text(product.name),
                subtitle: Text(
                  '₹${product.price.toStringAsFixed(0)}',
                ),
                trailing: ElevatedButton(
                  onPressed: () {
                    onAddToCart(product);
                  },
                  child: const Text('Add'),
                ),
              ),
            );
          },
        ),
      ],
    );
  }
}

class ProductsScreen extends StatelessWidget {
  final List<Product> products;
  final ValueChanged<Product> onAddToCart;

  const ProductsScreen({
    super.key,
    required this.products,
    required this.onAddToCart,
  });

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];

        return ListTile(
          title: Text(product.name),
          subtitle: Text(
            '₹${product.price.toStringAsFixed(0)}',
          ),
          trailing: IconButton(
            icon: const Icon(Icons.add_shopping_cart),
            onPressed: () {
              onAddToCart(product);
            },
          ),
        );
      },
    );
  }
}

class CartScreen extends StatelessWidget {
  final List<Product> cart;

  const CartScreen({
    super.key,
    required this.cart,
  });

  @override
  Widget build(BuildContext context) {
    if (cart.isEmpty) {
      return const Center(
        child: Text(
          'Your cart is empty',
          style: TextStyle(fontSize: 20),
        ),
      );
    }

    return ListView.builder(
      itemCount: cart.length,
      itemBuilder: (context, index) {
        final product = cart[index];

        return ListTile(
          leading: const Icon(
            Icons.shopping_bag,
          ),
          title: Text(product.name),
          subtitle: Text(
            '₹${product.price.toStringAsFixed(0)}',
          ),
        );
      },
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Profile Screen',
        style: TextStyle(fontSize: 24),
      ),
    );
  }
}


How This Example Works



  1. MainScreen owns the bottom-navigation state.

  2. The parent also owns the cart list.

  3. Home and Products receive the onAddToCart callback.

  4. When a product is selected, the child calls the callback.

  5. The parent updates the cart using setState().

  6. The updated cart is passed to the Cart destination.

  7. The NavigationBar badge displays the number of cart items.

  8. Switching to Cart displays the products added from another destination.




30. Best Practices for Bottom Navigation Data Transfer



  • Keep shared state at the lowest common parent that needs to coordinate the data.

  • Use constructor parameters for straightforward data transfer.

  • Use callbacks when child screens need to notify the parent.

  • Use model classes for structured application data.

  • Use state management when many unrelated widgets need the same changing data.

  • Use NavigationBar for modern Material 3 applications.

  • Use Navigator.push() for temporary/detail routes opened from a bottom-navigation destination.

  • Use Navigator.pop() to return a result from a pushed screen.

  • Use IndexedStack when preserving the state of tab widgets is important.

  • For complex nested navigation or deep linking, consider Router-based navigation or a routing package such as go_router.

  • Avoid unnecessarily duplicating the same state across multiple bottom-navigation screens.




31. Common Mistakes


Mistake 1: Creating Separate Copies of Shared Data


If Home, Cart, and Profile each maintain separate copies of the same data, the screens can become inconsistent.


Instead, keep shared state in a common owner or use an appropriate state-management approach.


Mistake 2: Confusing Tab Switching With Route Navigation


Changing the selected NavigationBar index is different from pushing a new route with Navigator.push().


Mistake 3: Using Global Variables Unnecessarily


Global variables can make data flow difficult to trace. Prefer explicit data flow or a suitable state-management architecture.


Mistake 4: Passing Too Many Parameters


If a screen requires many related values, create a model object instead of passing many independent parameters.


Mistake 5: Ignoring Tab State


If each tab contains forms, scroll positions, filters, or other local state, consider whether the selected navigation architecture should preserve those states.




32. Bottom Navigation Data Flow


                 MainScreen
                     |
             Shared Application State
                     |
       +-------------+-------------+
       |             |             |
      Home        Products        Cart
       |             |             |
       +-------------+-------------+
                     |
              NavigationBar
                     |
        selectedIndex / destinations

The important concept is that the bottom-navigation destinations do not necessarily need to communicate directly with each other. A common parent or shared state layer can coordinate the information.




33. Interview Questions


Q1. What is NavigationBar in Flutter?


NavigationBar is a Material 3 widget used for persistent navigation between primary destinations in an application.


Q2. What is the difference between NavigationBar and BottomNavigationBar?


NavigationBar is the Material 3 component, while BottomNavigationBar is the older Material 2 component.


Q3. How can you send information from one bottom-navigation tab to another?


For a small application, the common parent can own the shared state and pass it to the required tabs through constructors. Callbacks can be used to send updates back to the parent.


Q4. Can Navigator.push() be used inside a bottom-navigation tab?


Yes. A bottom-navigation destination can use Navigator.push() to open a detail, edit, or other temporary route.


Q5. How do you return information from a detail screen?


Use Navigator.pop(context, result) and await the result from the pushed route.


Q6. When should state management be used?


State management becomes useful when multiple unrelated widgets or destinations need to read and update the same application state.


Q7. What is IndexedStack used for?


IndexedStack can display one child at a time while keeping the other children in the widget tree, which can help preserve the state of bottom-navigation tabs.




34. Practice Exercises



  1. Create a Flutter app with Home, Products, Cart, and Profile tabs.

  2. Create a Product model and send selected products from Home to Cart.

  3. Add a cart counter badge to the Cart navigation destination.

  4. Create a Profile model and make it available to Home and Profile tabs.

  5. Create a Products tab and open a Product Details route using Navigator.push().

  6. Return a selected product from the Product Details screen using Navigator.pop().

  7. Use IndexedStack to preserve the state of multiple tabs.

  8. Build the same application using a state-management approach.

  9. Add a search field to Home and pass the search query to the Products tab.

  10. Implement separate navigation stacks for two bottom-navigation destinations.




35. Quick Revision























































ConceptSyntax / Approach
Modern Material bottom navigationNavigationBar
Older Material bottom navigationBottomNavigationBar
Track selected destinationselectedIndex
Change selected destinationonDestinationSelected
Send data to childConstructor parameter
Send data to parentCallback
Share state between tabsCommon parent/state-management solution
Open detail screenNavigator.push()
Return informationNavigator.pop(context, result)
Preserve tab widget stateIndexedStack
Complex navigationRouter / go_router



36. Key Takeaways



  • Flutter's NavigationBar provides Material 3 bottom navigation.

  • Bottom navigation is primarily used for switching between an app's main destinations.

  • Data can be shared between tabs through a common parent.

  • Constructor parameters provide a clear way to pass information to a destination.

  • Callbacks allow a child screen to notify the parent about changes.

  • Model classes are useful for passing structured information.

  • Navigator.push() can open a detail route from a bottom-navigation tab.

  • Navigator.pop() can return information from that detail route.

  • IndexedStack can help preserve the state of tab widgets.

  • State-management solutions become useful when shared data grows across the application.

  • Router-based navigation or packages such as go_router can be considered for advanced navigation and deep-linking requirements.




37. Official Flutter Resources





38. Flutter Training Resources



whatsapp