jb3rndt / PersistentBottomNavBarV2

A highly customizable persistent bottom navigation bar for Flutter
https://pub.dev/packages/persistent_bottom_nav_bar_v2
BSD 3-Clause "New" or "Revised" License
47 stars 48 forks source link

How to back from page with navBar: true with handleAndroidBackButtonPress: false #145

Closed kawi15 closed 2 months ago

kawi15 commented 2 months ago

Version

5.2.2

What platforms are you seeing the problem on?

Android

What happened?

I want to have overrided functionality of pressing back button on Android. To do it I need to set handleAndroidBackButtonPress: false and implement PopScope or willPopScope function and I did it. Everything is working as I want except one thing. When I go to another page with navBar visible, when I press android back button it's not coming back to previous page, it's closing app, somehow Navigator.of(context).pop() is executing onWillPop again.

My question is how to back from this page using PopScope? When I set handleAndroidBackButtonPress: true it is working, but then it's remembering switched tabs and as you can see in my code I want to back always to 0 index, not the previous one, so this option is not good also.

Long story short: Navigator.of(context).pop() not working properly in PopScope, when used with button it is working.

Steps to reproduce

  1. Go to another page with visible navBar from main PersistentBottomNavBar page.
  2. Click on android back button

Code to reproduce the problem

void onWillPop(bool didPop) {
    if (pageController.index == 0 && context.read<NavigationCubit>().state) {
      DateTime now = DateTime.now();
      if (currentBackPressTime == null ||
          now.difference(currentBackPressTime!) > Duration(seconds: 2)) {
        print('test3');
        currentBackPressTime = now;
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: const Text('pierdolnij jeszcze raz by wyjść'),
          duration: const Duration(seconds: 2),
          width: MediaQuery.of(context).size.width * 0.9,
          shape: const RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(10))),
          behavior: SnackBarBehavior.floating,
        ));
      }
      else {
        SystemNavigator.pop();
      }
    } else {
      if (context.read<NavigationCubit>().state) {
        pageController.jumpToTab(0);
      } else {
        context.read<NavigationCubit>().updateMainNavigator(true);
        Navigator.of(context).pop();
        //it's somehow execute onWillPop() again
      }
    }
  }

return PopScope(
      onPopInvoked: onWillPop,
      canPop: false,
      child: PersistentTabView(
        controller: pageController,
        navBarHeight: 70,
        handleAndroidBackButtonPress: false,
        navBarBuilder: (navBarConfig) => Style4BottomNavBar(
          navBarConfig: navBarConfig,
          navBarDecoration: const NavBarDecoration(
              border: Border(
                  top: BorderSide(
                      color: Color(0xFFD1D5DB)
                  )
              ),
              padding: EdgeInsets.only(
                bottom: 8,
              ),
              boxShadow: [
                BoxShadow(
                    offset: Offset(0, 4),
                    blurRadius: 6,
                    spreadRadius: -2
                )
              ]
          ),
        ),
        onTabChanged: (index) {
          setState(() {
            currentIndex = index;
          });
        },
        tabs: [
          PersistentTabConfig(
            screen: HomePage(),
            item: ItemConfig(
              activeForegroundColor: const Color(0xFF8430CB),
              icon: Padding(
                padding: const EdgeInsets.only(
                  top: 4,
                ),
                child: SvgPicture.asset(
                    currentIndex == 0
                        ? 'assets/home_active.svg'
                        : 'assets/home.svg'
                ),
              ),
              title: 'Główna',
              textStyle: const TextStyle(
                fontSize: 11,
                fontWeight: FontWeight.w600,
                color: Color(0xFF9A99AA)
              )
            )
          ),
          PersistentTabConfig(
              screen: SearchPage(),
              item: ItemConfig(
                  activeForegroundColor: const Color(0xFF8430CB),
                  icon: Icon(
                    Icons.home,
                  ),
                  title: 'Główna'
              )
          ),
          PersistentTabConfig(
              screen: BoxesPage(),
              item: ItemConfig(
                  activeForegroundColor: const Color(0xFF8430CB),
                  icon: Icon(
                    Icons.home,
                  ),
                  title: 'Główna'
              )
          ),
          PersistentTabConfig(
              screen: ProfilePage(),
              item: ItemConfig(
                  activeForegroundColor: const Color(0xFF8430CB),
                  icon: SvgPicture.asset(
                      currentIndex == 3
                          ? 'assets/account_active.svg'
                          : 'assets/account.svg'
                  ),
                  title: 'Główna',
                  textStyle: const TextStyle(
                      fontSize: 11,
                      fontWeight: FontWeight.w600,
                      color: Color(0xFF9A99AA)
                  )
              )
          ),
        ],
      ),
    );

Relevant log output

No response

Screenshots

No response

lucasfcardozo commented 2 months ago

If I understand correctly, your problem seems to be in the "context". Within its onWillPop function, the context used in Navigator.of(context).pop(); refers to the root (if we can say so) (the location where the PersistentTabView is.

Try this, and let me know if you need more help:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Flutter Demo',
    debugShowCheckedModeBanner: false,
    routes: {
      "/home": (context) => const ScreenPage(title: "Home", contentMessage: "Home"),
      "/home/second": (context) => const ScreenPage(title: "Home 2nd", contentMessage: "Home 2nd"),

      "/search": (context) => const ScreenPage(title: "Search", contentMessage: "Search"),
      "/search/second": (context) => const ScreenPage(title: "Search 2nd", contentMessage: "Search 2nd"),

      "/profile": (context) => const ScreenPage(title: "Profile", contentMessage: "Profile"),
      "/profile/second": (context) => const ScreenPage(title: "Profile 2nd", contentMessage: "Profile 2nd"),
    },
    home: const MyHomePage(),
  );
}

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

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

class _MyHomePageState extends State<MyHomePage> {

  void onPopInvoked(bool didPop) {
    if (pageController.index == 0) {
      DateTime now = DateTime.now();
      if (currentBackPressTime == null || now.difference(currentBackPressTime!) > Duration(seconds: 2)) {
        currentBackPressTime = now;
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
          content: const Text('Press back again to exit'),
          duration: const Duration(seconds: 2),
          width: MediaQuery.of(context).size.width * 0.9,
          shape: const RoundedRectangleBorder(
              borderRadius: BorderRadius.all(Radius.circular(10))),
          behavior: SnackBarBehavior.floating,
        ));
      } else {
        SystemNavigator.pop();
      }
    } else {
      if (selectedTabContext != null && Navigator.of(selectedTabContext!).canPop()) {
        Navigator.of(selectedTabContext!).pop();
      } else {
        pageController.jumpToTab(0);
      }
    }
  }

  DateTime? currentBackPressTime;
  BuildContext? selectedTabContext;

  final PersistentTabController pageController = PersistentTabController(initialIndex: 0);

  @override
  Widget build(BuildContext context) => PopScope(
    onPopInvoked: onPopInvoked,
    canPop: false,
    child: PersistentTabView(
      controller: pageController,
      navBarHeight: 70,
      handleAndroidBackButtonPress: false,

      // look this VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV
      selectedTabContext: (context) => selectedTabContext = context,

      navBarBuilder: (navBarConfig) => Style4BottomNavBar(
        navBarConfig: navBarConfig,
        navBarDecoration: const NavBarDecoration(
          border: Border(
            top: BorderSide(color: Color(0xFFD1D5DB))
          ),
          padding: EdgeInsets.only(bottom: 8),
          boxShadow: [
            BoxShadow(
              offset: Offset(0, 4),
              blurRadius: 6,
              spreadRadius: -2
            )
          ]
        ),
      ),
      tabs: [
        PersistentTabConfig(
          screen: ScreenPage(key: GlobalKey(), title: "Home", contentMessage: "Home 1"),
          item: ItemConfig(
            activeForegroundColor: const Color(0xFF8430CB),
            icon: Icon(Icons.home),
            title: 'Home'
          )
        ),
        PersistentTabConfig(
          screen: ScreenPage(key: GlobalKey(), title: "Search", contentMessage: "Search"),
          item: ItemConfig(
            activeForegroundColor: const Color(0xFF8430CB),
            icon: Icon(Icons.search),
            title: 'Search'
          )
        ),
        PersistentTabConfig(
          screen: ScreenPage(key: GlobalKey(), title: "Profile", contentMessage: "Profile"),
          item: ItemConfig(
            activeForegroundColor: const Color(0xFF8430CB),
            icon: Icon(Icons.person),
            title: 'Profile'
          )
        )
      ],
    ),
  );
}

class ScreenPage extends StatelessWidget {
  final String title;
  final String contentMessage;

  const ScreenPage({super.key, required this.title, required this.contentMessage});

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: Text(title),
    ),
    body: Container(
      width: double.infinity,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Text(contentMessage),
          if (!title.endsWith("2nd"))
            TextButton(
              onPressed: () {
                Navigator.of(context).pushNamed("/${title.toLowerCase()}/second");
              },
              child: Text("Go to 2nd screen"),
            )
        ],
      ),
    ),
  );
}
kawi15 commented 2 months ago

Yes, that was the problem and your solution is great. Thank you very much.