modular icon indicating copy to clipboard operation
modular copied to clipboard

TabBar + RouterOutlet + pushReplacementNamed not working correctly together

Open emersonsiega opened this issue 3 years ago • 7 comments
trafficstars

Bug description

We have a page with a TabBar and a RouterOutlet. Each tab has a route that is rendered inside this RouterOutlet. I can navigate with pushNamed to an inner page where there's a similar TabBar+RouterOutlet, but with different menu options. After enter this inner page and navigate in TabBar using Modular.to.navigate, the entire stack is cleared. When I try to call Modular.to.pop(), the app is closed, because the stack is empty.

I tried to change this navigation to use Modular.to.pushReplacementNamed, because the correct TabBar behavior is to change just the last route in the stack, but the RouterOutlet content doesn't changed correctly. I was looking to the navigation history and it looks like pushReplacementNamed is not replacing the last page correctly.

Environment

➜  flutter --version
Flutter 3.0.1 • channel stable • https://github.com/flutter/flutter.git
Framework • revision fb57da5f94 (3 weeks ago) • 2022-05-19 15:50:29 -0700
Engine • revision caaafc5604
Tools • Dart 2.17.1 • DevTools 2.12.2

Modular version: 5.0.2

To Reproduce

Add a TabBar rendering the content inside a RouterOutlet in two pages, navigate to the second page and navigate using the second TabBar.

Tab bar menu fired with Modular.to.navigate:

  • Navigation works well in the first page
  • Go to an inner page with other TabBar
  • After change tabs, the current stack is cleared and the back button closes the app instead of getting back to the previous page

Tab bar menu fired with Modular.to.pushReplacementNamed:

  • Navigation works well in the first time each tab is accessed.
  • Trying to get back to a previous tab, the content is not rendered
  • Looking at the stack, the previous pages are not replaced with Modular.to.pushReplacementNamed

Expected behavior

Modular.to.pushReplacementNamed should works correctly within the described context.

emersonsiega avatar Jun 06 '22 20:06 emersonsiega

Hi @emersonsiega, See my example for a similar case: https://gist.github.com/eduardoflorence/4d33da7bdb262ef349641fcfac9e10ca

eduardoflorence avatar Jun 07 '22 10:06 eduardoflorence

Hi @emersonsiega,

See my example for a similar case: https://gist.github.com/eduardoflorence/4d33da7bdb262ef349641fcfac9e10ca

Nice, I'll try using relative path for navigation and post the result here...

Thanks!

emersonsiega avatar Jun 07 '22 10:06 emersonsiega

@eduardoflorence your example works because the RouterOutlet is using just ChildRoutes. In my case, some of the children are ModuleRoute.

I changed your example in a way that doesn't work... https://gist.github.com/emersonsiega/3d0e26003aa5bf9babfb502f18490f9d

emersonsiega avatar Jun 28 '22 18:06 emersonsiega

@jacobaraujo7 can you give me a help with the scenario above?

emersonsiega avatar Jun 28 '22 18:06 emersonsiega

@emersonsiega, the example below works:

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

void main() {
  runApp(ModularApp(module: AppModule(), child: AppWidget()));
}

class AppModule extends Module {
  @override
  List<ModularRoute> get routes => [
        ChildRoute('/', child: (_, __) => HomePage()),
        ModuleRoute('/products', module: ProductsModule(), transition: TransitionType.noTransition),
      ];
}

class AppWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Modular.to.addListener(() {
      print('Navigate: ${Modular.to.path}');
      print('History: ${Modular.to.navigateHistory.map((e) => e.name)}');
    });

    return MaterialApp.router(
      routeInformationParser: Modular.routeInformationParser,
      routerDelegate: Modular.routerDelegate,
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: ElevatedButton(
          child: const Text('Go Products'),
          // navigate to module only
          onPressed: () async {
            await Modular.to.pushNamed('/products/');
          },
        ),
      ),
    );
  }
}

class ProductsModule extends Module {
  @override
  List<ModularRoute> get routes => [
        ChildRoute('/', child: (_, __) => ProductsPage(), children: [
          //ChildRoute('/red', child: (_, __) => Container(color: Colors.red)),
          ModuleRoute('/red', module: RedModule()),

          ChildRoute('/yellow', child: (_, __) => Container(color: Colors.yellow)),
          ChildRoute('/green', child: (_, __) => Container(color: Colors.green)),
        ])
      ];
}

class ProductsPage extends StatefulWidget {
  @override
  State<ProductsPage> createState() => _ProductsPageState();
}

class _ProductsPageState extends State<ProductsPage> {
  @override
  void initState() {
    super.initState();
    // Set the initial route inside the module
    Modular.to.pushNamed('./yellow');
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        // Returns to root product module before pop
        Modular.to.pushReplacementNamed(Modular.to.path.endsWith('/red/') ? '../' : './');
        return true;
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Products')),
        body: NavigationListener(builder: (context, child) {
          return Row(
            children: [
              SizedBox(
                width: 100,
                child: Column(children: [
                  ListTile(
                    title: const Text('Red'),
                    onTap: () => Modular.to.pushReplacementNamed('./red/'),
                    selected: Modular.to.path.endsWith('/red/'),
                  ),
                  ListTile(
                    title: const Text('Yellow'),
                    onTap: () =>
                        Modular.to.pushReplacementNamed(Modular.to.path.endsWith('/red/') ? '../yellow' : './yellow'),
                    selected: Modular.to.path.endsWith('/yellow'),
                  ),
                  ListTile(
                    title: const Text('Green'),
                    onTap: () =>
                        Modular.to.pushReplacementNamed(Modular.to.path.endsWith('/red/') ? '../green' : './green'),
                    selected: Modular.to.path.endsWith('/green'),
                  ),
                ]),
              ),
              const Expanded(child: RouterOutlet())
            ],
          );
        }),
      ),
    );
  }
}

class RedModule extends Module {
  @override
  List<ModularRoute> get routes => [
        ChildRoute(
          '/',
          child: (_, __) => RedPage(),
        ),
      ];
}

class RedPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(color: Colors.red);
  }
}

eduardoflorence avatar Jun 29 '22 19:06 eduardoflorence

The best solution is use RouteOutlet + RouteOutlet instead TabBar

Bwolfs2 avatar Aug 10 '22 13:08 Bwolfs2

Hello everyone,

I found an unintuitive behavior in the application's navigation history that I believe is related to this problem while working with a configuration very similar to the following: flutter_modular non-intuitive navigation behavior.

The navigation history, when navigating from the /step_one route to /step_two/type_one using the Modular.to.pushNamed('./step_two/type_one') call exhibited the following behavior:

Before

[log] Current route: /gradual_process/step_one
[log] Navigation history: [/gradual_process/, /gradual_process/step_one]

After

[log] Current route: /gradual_process/step_two
[log] Navigation history: [/gradual_process/, /gradual_process/step_one, /gradual_process/step_two/type_one, /gradual_process/step_two]

Notice how the extra /step_two route has been added to the top of the stack.

Is this behavior expected?

rairongf avatar Mar 13 '23 14:03 rairongf