Stacked-Org/stacked

[bug]: [web] Popscope is not working with RouterService

Opened this issue · 5 comments

Describe the bug

Popscope and WillPopScope (Deprecated) is not working in browser. Browser back button ignores settings.

Create a new page (web template) and make the following change to the builder. Set the "canPop" to "false" to avoid back navigation from new page to previous page.

Original:

return ScreenTypeLayout.builder(
        mobile: (_) => const Page3ViewMobile(),
        tablet: (_) => const Page3ViewTablet(),
        desktop: (_) => const Page3ViewDesktop(),
    );

Modified:

return PopScope(
      canPop: false,
      child: ScreenTypeLayout.builder(
        mobile: (_) => const Page3ViewMobile(),
        tablet: (_) => const Page3ViewTablet(),
        desktop: (_) => const Page3ViewDesktop(),
      ),
    );

To reproduce

  1. create app with web template
  2. create new view as follows
    stacked create view page2 -t web
  3. On home_view add a button and call handler
  4. in the model add handler
  Future nextPage() async {
    await _routerService.replaceWith(const Page3ViewRoute());
  }
  1. On page2_view change the builder as above

Expected behavior

With "canPop" set to false, the browser BACK button should not go back.

Screenshots

No response

Additional Context

Even "WillPopScop" (Deprecated) won't work

image

Hey,

Can you check if that's the expected behaviour in a Flutter app without Stacked.

I don't think you're allowed to block the back button, Stacked doesn't do anything different with navigation, we use all the default Flutter navigation functionality just with a better API for the devs to work against.

This works in Flutter app without stacked. Here is the code which you can test (3 scenarios)

  1. Create a new flutter project
  2. replace the entire main.dart with following (I have created a very basic stuff that shows how it works)
  3. Run it on Chrome (or any supported browser) - you will see, clicking the browser back button, it prevents going back.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: MyHomePage(),
        routes: {
          'home': (context) => MyHomePage(),
          'page1': (context) => Page1(),
          'page2': (context) => Page2(),
          'page3': (context) => Page3(),
        });
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  @override
  Widget build(BuildContext context) {
    return Container(
        child: Card(
            child: Center(
      child: Column(
        children: [
          IconButton(
            icon: Icon(Icons.run_circle),
            onPressed: () {
              Navigator.pushNamed(context, 'page1');
            },
          ),
          IconButton(
            icon: Icon(Icons.transfer_within_a_station),
            onPressed: () {
              Navigator.pushNamed(context, 'page2');
            },
          ),
          IconButton(
            icon: Icon(Icons.no_transfer),
            onPressed: () {
              Navigator.pushNamed(context, 'page3');
            },
          ),
        ],
      ),
    )));
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async => false,
      child: Container(
          child: Card(
        child: ListTile(
          title: Text('WillPopScope [Deprecated]'),
          subtitle: Text('Click browser back button'),
          leading: IconButton(
              icon: Icon(Icons.home),
              onPressed: () {
                Navigator.pop(context);
              }),
        ),
      )),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      child: Container(
          child: Card(
        child: ListTile(
          title: Text('PopScope : canPop = false'),
          subtitle: Text('Click browser back button'),
          leading: IconButton(
              icon: Icon(Icons.home),
              onPressed: () {
                Navigator.pop(context);
              }),
        ),
      )),
    );
  }
}

class Page3 extends StatefulWidget {
  const Page3();

  @override
  State<Page3> createState() => Page3State();
}

class Page3State extends State<Page3> {
  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: false,
      onPopInvoked: (didPop) async {
        if (didPop) {
          return;
        }
        _showBackDialog();
      },
      child: Container(
          child: Card(
        child: ListTile(
          title: Text('PopScope : canPop = true'),
          subtitle: Text('Click browser back button'),
          leading: IconButton(
              icon: Icon(Icons.home),
              onPressed: () {
                Navigator.pop(context);
              }),
        ),
      )),
    );
  }

  void _showBackDialog() {
    showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Are you sure?'),
          content: const Text(
            'Are you sure you want to leave this page?',
          ),
          actions: <Widget>[
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('Nevermind'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('Leave'),
              onPressed: () {
                Navigator.pop(context);
                Navigator.pop(context);
              },
            ),
          ],
        );
      },
    );
  }
}

Thanks for filing the issue, much appreciated.

We will take a look at this.

Is this fixed? Thank you for amazing package

I run into this issue too.
I have a horizontal scrolling table and when I try to swipe right to scroll table back to left, it pops the route.
PopScope's onPopInvokedWithResult method is triggered and it had didPop=true, even though canPop=false.
This is making it impossible to use a large table with horizontal scrolling using 2-finger swipe