Search code examples
flutterparameter-passingbottomnavigationview

How to pass listview data while using bottom navigation bar in flutter?


I want to pass data onclick of the listview items from one screen to another. All screen has bottom navigation bar with end drawer. Tried to pass data from second screen to details screen but was unsuccessful as there is no Navigator used. Anyone can help me with this? Following is the implemented code

bottom_nav_bar.dart

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

import 'Utility.dart';
import 'main.dart';

class CustomAnimatedBottomBar extends StatelessWidget {
  CustomAnimatedBottomBar({
    Key? key,
    this.selectedIndex = ScreenType.home,
    this.showElevation = true,
    this.iconSize = 24,
    this.backgroundColor,
    this.itemCornerRadius = 40,
    this.animationDuration = const Duration(milliseconds: 270),
    this.mainAxisAlignment = MainAxisAlignment.spaceBetween,
    required this.items,
    required this.onItemSelected,
    this.curve = Curves.linear,
  })  : assert(items.length >= 2 && items.length <= 5),
        super(key: key);

  final ScreenType selectedIndex;
  final double iconSize;
  final Color? backgroundColor;
  final bool showElevation;
  final Duration animationDuration;
  final List<BottomNavyBarItem> items;
  final ValueChanged<ScreenType> onItemSelected;
  final MainAxisAlignment mainAxisAlignment;
  final double itemCornerRadius;
  final Curve curve;

  @override
  Widget build(BuildContext context) {
    final bgColor = backgroundColor ?? Theme.of(context).bottomAppBarColor;

    return Container(
      decoration: BoxDecoration(
        color: bgColor,
        boxShadow: [
          if (showElevation)
            const BoxShadow(
              color: Colors.black12,
              blurRadius: 2,
            ),
        ],
      ),
      child: SafeArea(
        child: Container(
          width: double.infinity,
          height: kToolbarHeight,
          padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 8),
          child: Row(
            mainAxisAlignment: mainAxisAlignment,
            children: items.map((item) {
              var index = item;
              return GestureDetector(
                onTap: () => onItemSelected(index.screenType),
                child: _ItemWidget(
                  item: item,
                  iconSize: iconSize,
                  isSelected: index.screenType == selectedIndex,
                  backgroundColor: bgColor,
                  itemCornerRadius: itemCornerRadius,
                  animationDuration: animationDuration,
                  curve: curve,
                ),
              );
            }).toList(),
          ),
        ),
      ),
    );
  }
}

class _ItemWidget extends StatelessWidget {
  final double iconSize;
  final bool isSelected;
  final BottomNavyBarItem item;
  final Color backgroundColor;
  final double itemCornerRadius;
  final Duration animationDuration;
  final Curve curve;

  const _ItemWidget({
    Key? key,
    required this.item,
    required this.isSelected,
    required this.backgroundColor,
    required this.animationDuration,
    required this.itemCornerRadius,
    required this.iconSize,
    this.curve = Curves.linear,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Semantics(
      container: true,
      selected: isSelected,
      child: AnimatedContainer(
        width: isSelected ? 130 : 50,
        height: double.maxFinite,
        duration: animationDuration,
        curve: curve,
        decoration: BoxDecoration(
          color:
          isSelected ? item.activeColor.withOpacity(0.2) : backgroundColor,
          borderRadius: BorderRadius.circular(itemCornerRadius),
        ),
        child: Container(
          width: isSelected ? 130 : 50,
          padding: EdgeInsets.symmetric(horizontal: 10),
          child: Row(
            // mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              IconTheme(
                data: IconThemeData(
                  size: iconSize,
                  color: isSelected
                      ? item.activeColor.withOpacity(1)
                      : item.inactiveColor == null
                      ? item.activeColor
                      : item.inactiveColor,
                ),
                child: item.icon,
              ),
              if (isSelected)
                Expanded(
                  child: Container(
                    padding: EdgeInsets.symmetric(horizontal: 4),
                    child: DefaultTextStyle.merge(
                      style: TextStyle(
                        color: item.activeColor,
                        fontWeight: FontWeight.bold,
                      ),
                      maxLines: 1,
                      textAlign: item.textAlign,
                      child: item.title,
                    ),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}

class BottomNavyBarItem {
  BottomNavyBarItem({
    required this.screenType,
    required this.icon,
    required this.title,
    this.activeColor = Colors.blue,
    this.textAlign,
    this.inactiveColor,
  });

  final ScreenType screenType;
  final Widget icon;
  final Widget title;
  final Color activeColor;
  final Color? inactiveColor;
  final TextAlign? textAlign;
}

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/FifthScreen.dart';
import 'package:flutter_app/details_screen.dart';
import 'package:flutter_app/profile_screen.dart';
import 'package:flutter_app/secondPage.dart';

import 'ThirdPage.dart';
import 'Utility.dart';
import 'bottom_nav_bar.dart';
import 'firstpage.dart';
import 'fourthPage.dart';
import 'home_screen.dart';
import 'message_screen.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

enum ScreenType {
  firstScreen,
  secondScreen,
  thirdScreen,
  forthScreen,
  fifthScreen,
  detailsScreen,
  home,
  messages,
  profile
}

class _MyHomePageState extends State<MyHomePage> {
  ScreenType _screenType = ScreenType.home;
  final _inactiveColor = Colors.grey;

  GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: getTitle(_screenType),
      ),
      endDrawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: <Widget>[
            DrawerHeader(
              child: Text('Drawer Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: Text('First Screen'),
              onTap: (){onTabTapped(ScreenType.firstScreen);
              Navigator.of(context).pop();
              },
            ),
            ListTile(
              title: Text('Second Screen'),
              onTap:(){onTabTapped(ScreenType.secondScreen);
              Navigator.of(context).pop();
              },
            ),
            ListTile(
              title: Text('Third Screen'),
              onTap: (){onTabTapped(ScreenType.thirdScreen);
              Navigator.of(context).pop();
              },
            ),
          ],
        ),
      ),
      body: _body(_screenType),
      bottomNavigationBar: _buildBottomBar(),
    );
  }

  Widget _body(ScreenType screenType) {
    switch (screenType) {
      case ScreenType.firstScreen:
        return FirstScreen(
          navigateScreen: (screenType) => onTabTapped(screenType),
        );
      case ScreenType.secondScreen:
        return SecondScreen(
          onClickList: (model){
            setState(() {
              _screenType = ScreenType.detailsScreen;
            });
          },
        );
      case ScreenType.thirdScreen:
        return const ThirdScreen();
      case ScreenType.forthScreen:
        return const ForthScreen();
      case ScreenType.home:
        return const HomeScreen();
      case ScreenType.messages:
        return const MessagesScreen();
      case ScreenType.profile:
        return const ProfileScreen();
      case ScreenType.fifthScreen:
        return const FifthScreen();
      case ScreenType.detailsScreen:
        return DetailsScreen();
    }
  }

  Widget _buildBottomBar() {
    return CustomAnimatedBottomBar(
      backgroundColor: Colors.black,
      selectedIndex: _screenType,
      showElevation: true,
      itemCornerRadius: 24,
      curve: Curves.easeIn,
      onItemSelected: onTabTapped,
      items: <BottomNavyBarItem>[
        BottomNavyBarItem(
          screenType: ScreenType.home,
          icon: Icon(Icons.apps),
          title: Text('Home'),
          activeColor: Colors.green,
          inactiveColor: _inactiveColor,
          textAlign: TextAlign.center,
        ),
        BottomNavyBarItem(
          screenType: ScreenType.messages,
          icon: Icon(Icons.message),
          title: Text('Messages'),
          activeColor: Colors.purpleAccent,
          inactiveColor: _inactiveColor,
          textAlign: TextAlign.center,
        ),
        BottomNavyBarItem(
          screenType: ScreenType.profile,
          icon: Icon(Icons.account_circle_rounded),
          title: Text('Profile'),
          activeColor: Colors.pink,
          inactiveColor: _inactiveColor,
          textAlign: TextAlign.center,
        ),
      ],
    );
  }

  void onTabTapped(ScreenType screenType) {
    if ((_scaffoldKey.currentState ?? ScaffoldState()).isEndDrawerOpen) {
      (_scaffoldKey.currentState ?? ScaffoldState()).openEndDrawer();
    }
    setState(() {
      _screenType = screenType;
    });
  }

  Widget getTitle(ScreenType screenType) {
    switch (screenType) {
      case ScreenType.firstScreen:
        return Text("First Screen");
      case ScreenType.secondScreen:
        return Text("Second Screen");
      case ScreenType.thirdScreen:
        return Text("Third Screen");
      case ScreenType.forthScreen:
        return Row(
          children: [
            IconButton(onPressed: (){
              onTabTapped(ScreenType.firstScreen);
            }, icon: Icon(Icons.arrow_back_ios)),
            Text("Fourth Screen"),
          ],
        );
      case ScreenType.detailsScreen:
        return Text("Details Screen");
      case ScreenType.home:
        return Text("Home");
      case ScreenType.messages:
        return Text("Message");
      case ScreenType.profile:
        return Text("Profile");
      case ScreenType.fifthScreen:
      // TODO: Handle this case.
        return Text("Fifth Sceen");
        break;
    }
  }
}

second_page.dart

import 'package:flutter/material.dart';

typedef OnClickList(Model);

class SecondScreen extends StatefulWidget {
  // final VoidCallback voidCallback;
  final OnClickList onClickList;
  const SecondScreen({Key? key, required this.onClickList}) : super(key: key);

  @override
  _SecondScreenState createState() => _SecondScreenState();
}

class _SecondScreenState extends State<SecondScreen> {
  @override
  Widget build(BuildContext context) {

    List<Model> items = [
      Model(text: "Text 1 to next screen"),
      Model(text: "Text 2 to next screen"),
      Model(text: "Text 3 to next screen"),
    ];
    return Container(
      child: Center(
        child: Column(
          children: [
            Text("Second Screen"),
            ListView.builder(
                itemCount: items.length,
                shrinkWrap: true,scrollDirection: Axis.vertical,
                itemBuilder: (context, index){
                  Model model = items[index];
              return GestureDetector(
                onTap: (){
                  widget.onClickList(model.text);
                },
                child: Card(
                  child: Text("${items[index].text}"),
                ),
              );
            })
          ],
        ),
      ),
    );
  }
}

class Model{
  String text;

  Model({required this.text});
}

details_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/secondPage.dart';

class DetailsScreen extends StatefulWidget {
  final Model? model;

  const DetailsScreen({Key? key, this.model}):super(key:key);

  @override
  _DetailsScreenState createState() => _DetailsScreenState();
}

class _DetailsScreenState extends State<DetailsScreen> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Text(widget.model!.text.toString()),
      ),
    );
  }
}

first_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_app/main.dart';

typedef NavigateScreen(ScreenType);

class FirstScreen extends StatefulWidget {
  const FirstScreen({
    Key? key,
    required this.navigateScreen,
  }) : super(key: key);

  final NavigateScreen navigateScreen;

  @override
  _FirstScreenState createState() => _FirstScreenState();
}

class _FirstScreenState extends State<FirstScreen> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Center(
        child: Flex(
          direction: Axis.vertical,
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text("First Screen"),
            ElevatedButton(
              onPressed: () => widget.navigateScreen(ScreenType.forthScreen),
              child: Text("Go To Forth Screen"),
            ),
            ElevatedButton(
              onPressed: () => widget.navigateScreen(ScreenType.fifthScreen),
              child: Text("Go To Fifth Screen"),
            ),

          ],
        ),
      ),
    );
  }
}

Solution

  • To pass data from second screen to details screen:

    1. Add a variable in homepage state which can take content clicked from second screen.
    class _MyHomePageState extends State<MyHomePage> {
      ScreenType _screenType = ScreenType.home;
      final _inactiveColor = Colors.grey;
      Model? fromSecond;
    
    …….
    
    }
    
    1. Change onClickList implementation and switch case.
    Widget _body(ScreenType screenType) {
      switch (screenType) {
        case ScreenType.firstScreen:
          return FirstScreen(
            navigateScreen: (screenType) => onTabTapped(screenType),
          );
        case ScreenType.secondScreen:
          return SecondScreen(
            onClickList: (model) {
              fromSecond = model;
              setState(() {
                _screenType = ScreenType.detailsScreen;
              });
            },
          );
    ……..
    
    …….
    
    
    case ScreenType.detailsScreen:
      {
        if(fromSecond!=null) {
          return DetailsScreen(model: fromSecond);
        } else {
          return DetailsScreen();
        }
      }
    ………..
    
    ……….
    
    
    }
    
    1. On SecondScreen make gesture detector return model to onClickList callback.
    …………
    
    …………
    return GestureDetector(
      onTap: (){
        widget.onClickList(model);
      },
      child: Card(
        child: Text("${items[index].text}"),
      ),
    );
    …….
    
    …….