Milad-Akarie / auto_route_library

Flutter route generator
MIT License
1.59k stars 406 forks source link

dynamic routes for AutoTabsScaffold don't work #783

Closed danielgchap closed 3 years ago

danielgchap commented 3 years ago

Hi team... here is some code...

return SafeArea( child: GestureDetector( onTap: () => FocusManager.instance.primaryFocus?.unfocus(), child: AutoTabsScaffold( routes: routes.value, bottomNavigationBuilder: (_, tabsRouter) { return BottomNavigationBar(

This is in the build method of a widget.

We have feature flags that we are using that gives users access to certain modules in the app. So the routes can be updated (some routes removed, added). But the autotabsscaffold never seems to really care.

It seems the 'stack' in the AutoTabsScaffold never seems to update the tabsRouter.stack.

Assume the routes array has 4 routes in it. Then as the screen loads, 2 routes are taken out due to user access rights. AutoTabsScaffold still has 4 screens in the tabsRouter, even tho it only has 2 routes.

How can I dynamically assign the correct routes/screen to an autotabsscaffold please? This is a feature that seems like something you've done already and perhaps I haz the dumb?

Would really love some help.

Thank you so much!

Milad-Akarie commented 3 years ago

@danielgchap looks like a bug, I'll investigate this.

Milad-Akarie commented 3 years ago

@danielgchap should be fixed in version auto_route: 3.0.3

danielgchap commented 3 years ago

@Milad-Akarie you rock sir. Let me give it a shot and I'll let you know. Thank you for the prompt response!

danielgchap commented 3 years ago

@Milad-Akarie hey brother. I upgraded but the issue persists. Even if the autotabsscaffold rebuilds with a list of routes, that are the correct routes.. the 'stack' param of the tabsBuilder is never updated with the correct routes/screens. So the tabs may show four tabs, but if the tabRouter initially thinks there are only two screens, it ONLY ever builds tho, even if a new AutoTabsScaffold is init'd with new updated routes.

This is relevant to us because we are awaiting a network call to direct what access the user has and THEN build the autotabsscaffold. (Not all users have the 'rights' to all screens).

How would you suggest handling? Thank you

Milad-Akarie commented 3 years ago

Please show me the code of updating the routes list

danielgchap commented 3 years ago
return SafeArea(
      child: GestureDetector(
          onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
          child: AutoTabsScaffold(
            routes: [
              if (_homePageEnabled!.value == true) const HomeTab(),
              if (_nextPageEnabledState!.value == true)
                const NextTab(),
              const HistoryTab(),
              const SettingsTab(),
            ],
            bottomNavigationBuilder: buildBottomNav,
          )),
    );

The _homePageEnabled and _nextPageEnabledState variables are ValueNotifier that are updated from a stream.

` useEffect(() { final sub = homeStream.listen((event) { _homePageEnabled!.value = event; });

  return sub.cancel;
}, []);

`

I then do 'Widget buildBottomNav(BuildContext context, TabsRouter tabsRouter) ' just like you did in the example for auto_route (where some of your 783 commits are). It updates tabs, but pages are never right.

Thank you bro!

danielgchap commented 3 years ago

Seems like tabsRouter childControllers not updating?

danielgchap commented 3 years ago

Yeah bro. See pages (and stack) are 3 items... child controllers, only 2

Screen Shot 2021-10-25 at 2 16 00 PM

.

danielgchap commented 3 years ago
Screen Shot 2021-10-25 at 2 51 38 PM

Home page never appears. Theres three tabs (Home, History, Settings). But only two screens. History (both Home and History tab navigate to this, why?).. and Settings ,which is fine.

I even changed this from a hook widget to a stateful widget and using setState like you did in the example. Have you tried mocking async network call and updating the routes after a second or two rather than on a button? Frustrating.

Milad-Akarie commented 3 years ago

@danielgchap I'll investigate this more but first, are you passing a list instance to AutoTabsScaffold? which one is your actual code?

routes: routes, or

routes:[
 if (_homePageEnabled!.value == true) const HomeTab(),
              if (_nextPageEnabledState!.value == true)
                const NextTab(),
              const HistoryTab(),
              const SettingsTab(),
]
danielgchap commented 3 years ago

@Milad-Akarie hey buddy. I have done both options above. I'm researching my setup to ensure that that isn't the issue too. Can't thank you enough!

danielgchap commented 3 years ago

@Milad-Akarie can you provide an example where the routes are updated dynamically, perhaps from an async sense? (Including your router and routes setup). I am awaiting a call from a Stream (Riverpood/hooks for state management) to indicate what screens should show.

No matter if I pass the AutoTabsScaffold an array, or have it hold an array and put booleans in that array as to if a tab should show or not. .it does NOT work. The right number of screens NEVER show up.

Milad-Akarie commented 3 years ago

Ill test it as soon as I can. Also I've made some changes to AutoTabsRouter that might fix the issue if it as what I think it is. Might push later today

danielgchap commented 3 years ago

@Milad-Akarie dude, you're the greatest. I can't thank you enough. Sorry to be such a pain! I know you have full time work too! Thank you so much brother!

danielgchap commented 3 years ago

Just to give you some insight to what I got. I tried again but without Riverpod/hooks and just went with setState. However, this still doesn't work sadly. There seems to be some disparity between the updated routes being handed to the AutoTabsScaffold, and the tabsRouter updating in turn.

A friend of mine managed to get it to work but it isn't the same scenario. In his case, (no async/networking code), the initial routes handed to the ATScaffold and thus the tabsrouter, it's a list of about 8 pages, and the setState call just removes one from the array. Everything works in THAT scenario but naturally that isn't what I'm facing.

I tried to attach a dart file but can't. Here's the code (sorry)

import 'package:auto_route/auto_route.dart';
//omitted
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../app_router.dart';
import '../../../core/translations/locale.keys.g.dart';
import '../../../core/utilities/feature_flag/feature_flag_service.dart';
import '../../../feature_flags.dart';

class BackUpWrapper extends StatefulWidget {
  const BackUpWrapper({Key? key}) : super(key: key);

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

class _BackUpWrapperState extends State<BackUpWrapper> {
  final List<PageRouteInfo<void>> _routes = [
    // const HomeTab(),
    // const TroubleshootingTab(),
    const HistoryTab(),
    const SettingsTab(),
  ];
  bool _homePageEnabled = false;
  bool _tshootingPageEnabled = false;

  @override
  void initState() {
    super.initState();
    _setupStreams();
  }

  _setupStreams() {
    final homePageFeatureEnabledStream = context
        .read(featureFlagServiceProvider)
        .isEnabled$(
          FeatureFlags.tempHomePage,
          defaultValue: _homePageEnabled,
        )
        .distinct();

    final symptomPageFeatureEnabledStream = context
        .read(featureFlagServiceProvider)
        .isEnabled$(
          FeatureFlags.tempTroubleshooting,
          defaultValue: _tshootingPageEnabled,
        )
        .distinct();

    homePageFeatureEnabledStream.listen((event) {
      setState(() {
        _homePageEnabled = event;
      });
    });

    symptomPageFeatureEnabledStream.listen((event) {
      setState(() {
        _tshootingPageEnabled = event;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return AutoTabsScaffold(
      routes: [
        if (_homePageEnabled) const HomeTab(),
        if (_tshootingPageEnabled) const TroubleshootingTab(),
        const HistoryTab(),
        const SettingsTab(),
      ],
      bottomNavigationBuilder: buildBottomNav,
    );
  }

  Widget buildBottomNav(BuildContext context, TabsRouter tabsRouter) {
    return BottomNavigationBar(
      selectedItemColor:
          Theme.of(context).bottomNavigationBarTheme.selectedItemColor,
      unselectedItemColor:
          Theme.of(context).bottomNavigationBarTheme.backgroundColor,
      type: BottomNavigationBarType.fixed,
      showSelectedLabels: false,
      showUnselectedLabels: false,
      selectedFontSize: 0,
      currentIndex: tabsRouter.activeIndex,
      onTap: (int index) {
        tabsRouter.setActiveIndex(index);
      },
      items: [
        if (_homePageEnabled)
          BottomNavigationBarItem(
            icon: _buildIcon(
              context,
              Icons.apps,
              LocaleKeys.menu_home.tr(),
              0,
            ),
            label: LocaleKeys.menu_home.tr(),
          ),
        if (_tshootingPageEnabled)
          BottomNavigationBarItem(
            icon: _buildIcon(
              context,
              Icons.report_problem,
              LocaleKeys.menu_symptoms.tr(),
              1,
            ),
            label: LocaleKeys.menu_activityFeed.tr(),
          ),
        BottomNavigationBarItem(
          icon: _buildIcon(
            context,
            Icons.date_range,
            LocaleKeys.menu_history.tr(),
            2,
          ),
          label: LocaleKeys.menu_history.tr(),
        ),
        BottomNavigationBarItem(
          icon: _buildIcon(
            context,
            Icons.account_circle,
            LocaleKeys.menu_settings.tr(),
            3,
          ),
          label: LocaleKeys.menu_settings.tr(),
        ),
      ],
    );
  }

  Widget _buildIcon(
          BuildContext context, IconData iconData, String text, int index) =>
      SizedBox(
        width: double.infinity,
        height: kBottomNavigationBarHeight,
        child: Material(
          // color: _getBgColor(context, curr, index),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(
                iconData,
                color: Theme.of(context).primaryIconTheme.color,
              ),
              Text(text, style: Theme.of(context).textTheme.caption),
            ],
          ),
        ),
      );
}

Thanks again boss man! Heres my email if you need: danielgchap@hotmail.com

Milad-Akarie commented 3 years ago

@danielgchap please check the new build auto_route: ^3.0.4 and confirm if it fixes the issue.

danielgchap commented 3 years ago

@Milad-Akarie @Milad-Akarie @Milad-Akarie !!!!!!!!!! You. Are. A. Freaking. Rock. Star.

A conversation is in process at my job about just how helpful, prompt and responsive you've been. Wow man. I have never had such great help from a git repo and friend in Flutter! You are a legend bro. It's working like a charm. Thank you SO SO SO SO MUCH!!!!!

I'm going to paste my code so you can see what I wrote in conjunction with your changes so you can see and, possibly, help others in the future.

Thank you so much Milad! You rock bro.

import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import '../../../app_router.dart';
imports omitted

class HomePageWrapper extends HookWidget {
  ValueNotifier<bool>? _homePageEnabled;
  ValueNotifier<bool>? _historyPageEnabled;

  HomePageWrapper({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    _homePageEnabled = useState(false);
    _historyPageEnabled = useState(false);

    final homePageEnabledStream = context
        .read(accessProvider)
        .isEnabled$(
          defaultValue: _homePageEnabled!.value,
        )
        .distinct();

    final historyPageEnabledStream = context
        .read(accessProvider)
        .isEnabled$(
          defaultValue: _historyPageEnabled!.value,
        )
        .distinct();

    useEffect(() {
      final sub = homePageFeatureEnabledStream.listen((event) {
        _homePageEnabled!.value = event;
      });

      return sub.cancel;
    }, []);

    useEffect(() {
      final sub = symptomPageFeatureEnabledStream.listen((event) {
        _historyPageEnabled!.value = event;
      });

      return sub.cancel;
    }, []);

    return SafeArea(
        child: GestureDetector(
            onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
            child: AutoTabsScaffold(
                routes: [
                  if (_homePageEnabled!.value) const HomeTab(),
                  if (_historyPageEnabled!.value)
                    const HistoryTab(),
                  const ProfilePage(),
                  const SettingsTab(),
                ],
                bottomNavigationBuilder: buildBottomNav)));
  }

  Widget buildBottomNav(BuildContext context, TabsRouter tabsRouter) {
    return BottomNavigationBar(
      type: BottomNavigationBarType.fixed,
      showSelectedLabels: false,
      showUnselectedLabels: false,
      selectedFontSize: 0,
      currentIndex: tabsRouter.activeIndex,
      onTap: tabsRouter.setActiveIndex,
      items: [
        if (_homePageEnabled!.value)
          BottomNavigationBarItem(
            icon: _buildIcon(
              context,
              Icons.apps,
              LocaleKeys.menu_home.tr(),
              tabsRouter.activeIndex,
              0,
            ),
            label: LocaleKeys.menu_home.tr(),
          ),
        if (_historyPageEnabled!.value)
          BottomNavigationBarItem(
            icon: _buildIcon(
              context,
              Icons.report_problem,
              LocaleKeys.menu_symptoms.tr(),
              tabsRouter.activeIndex,
              1,
            ),
            label: LocaleKeys.menu_activityFeed.tr(),
          ),
        BottomNavigationBarItem(
          icon: _buildIcon(
            context,
            Icons.date_range,
            LocaleKeys.menu_history.tr(),
            tabsRouter.activeIndex,
            2,
          ),
          label: LocaleKeys.menu_history.tr(),
        ),
        BottomNavigationBarItem(
          icon: _buildIcon(
            context,
            Icons.account_circle,
            LocaleKeys.menu_settings.tr(),
            tabsRouter.activeIndex,
            3,
          ),
          label: LocaleKeys.menu_settings.tr(),
        ),
      ],
    );
  }
}

And this is the current router setup tho we will probably be changing this soon but it works currently:

return MaterialApp.router(
      key: globalKey,
      title: F.title,
      theme: lightTheme,
      darkTheme: darkTheme,
      debugShowCheckedModeBanner: false,
      localizationsDelegates: context.localizationDelegates,
      supportedLocales: context.supportedLocales,
      locale: context.locale,
      routerDelegate: AutoRouterDelegate.declarative(
        _appRouter,
        routes: (_) => [
          if (authenticationStateManager.isAuthenticated)
            HomePageWrapper()
          else
            const LoginPageRoute(),
        ],
        navigatorObservers: () => [
omitted
        ],
      ),
      routeInformationParser:
          _appRouter.defaultRouteParser(includePrefixMatches: true),
    );

Again.. thank you so much.. and thank you to all the other authors/collabs too! REALLY appreciate it!

Milad-Akarie commented 3 years ago

@danielgchap You're welcome bro, Thank you for the support. Please keep in mind that when adding or removing tabs, other tabs will lose state. best regards.

danielgchap commented 3 years ago

@Milad-Akarie yessir. This adding/removing only happens on initial load so we're golden. Thanks again brother! All the best!

danielgchap commented 3 years ago

@Milad-Akarie hey bro, not sure if you want a new issue but it's related to this one. When I set the hometabsscaffold.homeIndex: 3 (let's say), and the routes dynamically update, it always just goes to the first index. Is there a way to tell the tabRouter:

"Hey, irregardless of the routes, set the activeIndex to 'this'".... ?

Thanks bro Sorry!

danielgchap commented 3 years ago

It's the active index on the tabsRouter my friend. Is it possible for the activeIndex of the tabsRouter to pay attention to the homeIndex of the AutoTabsScaffold to persist and point to the tab we desire?

I have a workaround for now, I think. lol

danielgchap commented 3 years ago

I'll open a new issue/feature request for the persistent index of tabs. Thanks again!