slovnicki / beamer

A routing package built on top of Router and Navigator's pages API, supporting arbitrary nested navigation, guards and more.
MIT License
591 stars 129 forks source link

Navigation history does not clear even after popping the page #646

Open jeslinjacob1995 opened 1 year ago

jeslinjacob1995 commented 1 year ago

Describe the bug Navigation history does not clear even after popping the page . If I pop from one page it will navigate correctly , but if i press browser back button it will take me to same page which i already popped

Beamer version: (e.g. v0.14.1, master, ...) beamer: ^1.5.6

To Reproduce Steps to reproduce the behavior:

import 'package:beamer/beamer.dart';
import 'package:example_beamer/home_location.dart';
import 'package:flutter/material.dart';

import 'account_listing_page.dart';
import 'home_page.dart';

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

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

  final routerDelegate = BeamerDelegate(
    initialPath: '/home',
    locationBuilder: RoutesLocationBuilder(
      routes: {
        '*' : (context, state, data) => const HomePage(),
        // '/account' : (context, state, data) => const AccountListingPage(),
    },
    ),
  );

  @override
  Widget build(BuildContext context) {
    return  WillPopScope(
      onWillPop: () async{
        return false;
      },
      child: MaterialApp.router(
        debugShowCheckedModeBanner: false,
        routerDelegate: routerDelegate,
        routeInformationParser: BeamerParser(),
        theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
        ),
      ),
    );
  }
}
import 'package:beamer/beamer.dart';
import 'package:example_beamer/account_locations.dart';
import 'package:flutter/material.dart';

import 'account_details.dart';
import 'account_listing_page.dart';

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {

  int selectedIndex = 0;
  final key = GlobalKey<BeamerState>();

  final routeDelegates = [
      BeamerDelegate(
          // initialPath: 'account',
          locationBuilder: (routeInfo,_){
          return AccountLocations(routeInfo);
      },

      ),
      BeamerDelegate(locationBuilder: (routeInfo,_){
        return AccountLocations(routeInfo);
      })
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 500,
              height: MediaQuery.of(context).size.height,
              color: Colors.blue,
              child: Column(
                children: [
                  ListTile(
                    title: const Text("Accounts",),
                    onTap: (){
                      setState(() {
                        selectedIndex = 0;
                      });
                    },
                    selected: selectedIndex == 0,
                  ),
                  ListTile(
                    title: const Text("Profile"),
                    onTap: (){
                      setState(() {
                        selectedIndex = 1;
                      });
                    },
                    selected: selectedIndex == 1,
                  ),
                ],
              ),
            ),
            Expanded(child: Container(
              color: Colors.white,
              child: Beamer(
                key: key,
                backButtonDispatcher: BeamerBackButtonDispatcher(
                  delegate: routeDelegates[0],
                  fallbackToBeamBack: false,
                ),
                routerDelegate: routeDelegates[0],
              ),
            ))

          ],
        ),
      ),
    );
  }
}
import 'package:beamer/beamer.dart';
import 'package:example_beamer/account_details.dart';
import 'package:example_beamer/account_listing_page.dart';
import 'package:flutter/material.dart';

class AccountLocations extends BeamLocation<BeamState>{

  AccountLocations(RouteInformation routerInformation) : super(routerInformation);

  @override
  List<BeamPage> buildPages(BuildContext context, BeamState state) => [
    const BeamPage(
      key: ValueKey('/account'),
      title: 'Account Listing',
      type: BeamPageType.noTransition,
      child: AccountListingPage(),
    ),
    if(state.uri.path.contains('/details'))
    const BeamPage(
      key: ValueKey('/details'),
      title: 'Account Details',
      type: BeamPageType.noTransition,
      child: AccountDetails(),
    ),
    if(state.uri.path.contains('/statement'))
    const BeamPage(
      key: ValueKey('/statement'),
      title: 'Account Statement',
      type: BeamPageType.noTransition,
      child: AccountStatement(),
    ),
  ];

  @override
  List<Pattern> get pathPatterns => ["/account"];

}

from account statement page if i call Navigator.of(context).maybePop();it pops to correct page , but again if i press on browser back button it will take me to account statement page

Expected behavior I am expecting that i should not go to the page which it already popped

I am running the code from flutter web with chrome browser

stan-at-work commented 3 months ago

@jeslinjacob1995 Is this still an issue, try updating to the latest beamer version, and the latest flutter version.

stan-at-work commented 3 months ago

Describe the bug

Navigation history does not clear even after popping the page . If I pop from one page it will navigate correctly , but if i press browser back button it will take me to same page which i already popped

Beamer version: (e.g. v0.14.1, master, ...)

beamer: ^1.5.6

To Reproduce

Steps to reproduce the behavior:


import 'package:beamer/beamer.dart';

import 'package:example_beamer/home_location.dart';

import 'package:flutter/material.dart';

import 'account_listing_page.dart';

import 'home_page.dart';

void main() {

  runApp( MyApp());

}

class MyApp extends StatelessWidget {

   MyApp({super.key});

  final routerDelegate = BeamerDelegate(

    initialPath: '/home',

    locationBuilder: RoutesLocationBuilder(

      routes: {

        '*' : (context, state, data) => const HomePage(),

        // '/account' : (context, state, data) => const AccountListingPage(),

    },

    ),

  );

  @override

  Widget build(BuildContext context) {

    return  WillPopScope(

      onWillPop: () async{

        return false;

      },

      child: MaterialApp.router(

        debugShowCheckedModeBanner: false,

        routerDelegate: routerDelegate,

        routeInformationParser: BeamerParser(),

        theme: ThemeData(

        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),

        useMaterial3: true,

        ),

      ),

    );

  }

}

import 'package:beamer/beamer.dart';

import 'package:example_beamer/account_locations.dart';

import 'package:flutter/material.dart';

import 'account_details.dart';

import 'account_listing_page.dart';

class HomePage extends StatefulWidget {

  const HomePage({super.key});

  @override

  State<HomePage> createState() => _HomePageState();

}

class _HomePageState extends State<HomePage> {

  int selectedIndex = 0;

  final key = GlobalKey<BeamerState>();

  final routeDelegates = [

      BeamerDelegate(

          // initialPath: 'account',

          locationBuilder: (routeInfo,_){

          return AccountLocations(routeInfo);

      },

      ),

      BeamerDelegate(locationBuilder: (routeInfo,_){

        return AccountLocations(routeInfo);

      })

  ];

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      body: Center(

        child: Row(

          mainAxisAlignment: MainAxisAlignment.center,

          children: <Widget>[

            Container(

              width: 500,

              height: MediaQuery.of(context).size.height,

              color: Colors.blue,

              child: Column(

                children: [

                  ListTile(

                    title: const Text("Accounts",),

                    onTap: (){

                      setState(() {

                        selectedIndex = 0;

                      });

                    },

                    selected: selectedIndex == 0,

                  ),

                  ListTile(

                    title: const Text("Profile"),

                    onTap: (){

                      setState(() {

                        selectedIndex = 1;

                      });

                    },

                    selected: selectedIndex == 1,

                  ),

                ],

              ),

            ),

            Expanded(child: Container(

              color: Colors.white,

              child: Beamer(

                key: key,

                backButtonDispatcher: BeamerBackButtonDispatcher(

                  delegate: routeDelegates[0],

                  fallbackToBeamBack: false,

                ),

                routerDelegate: routeDelegates[0],

              ),

            ))

          ],

        ),

      ),

    );

  }

}

import 'package:beamer/beamer.dart';

import 'package:example_beamer/account_details.dart';

import 'package:example_beamer/account_listing_page.dart';

import 'package:flutter/material.dart';

class AccountLocations extends BeamLocation<BeamState>{

  AccountLocations(RouteInformation routerInformation) : super(routerInformation);

  @override

  List<BeamPage> buildPages(BuildContext context, BeamState state) => [

    const BeamPage(

      key: ValueKey('/account'),

      title: 'Account Listing',

      type: BeamPageType.noTransition,

      child: AccountListingPage(),

    ),

    if(state.uri.path.contains('/details'))

    const BeamPage(

      key: ValueKey('/details'),

      title: 'Account Details',

      type: BeamPageType.noTransition,

      child: AccountDetails(),

    ),

    if(state.uri.path.contains('/statement'))

    const BeamPage(

      key: ValueKey('/statement'),

      title: 'Account Statement',

      type: BeamPageType.noTransition,

      child: AccountStatement(),

    ),

  ];

  @override

  List<Pattern> get pathPatterns => ["/account"];

}

from account statement page if i call Navigator.of(context).maybePop();it pops to correct page , but again if i press on browser back button it will take me to account statement page

Expected behavior

I am expecting that i should not go to the page which it already popped

I am running the code from flutter web with chrome browser

Also add:

backButtonDispatcher: BeamerBackButtonDispatcher( delegate: routerDelegate, ),

As a parameter to materialApp.router(...);

stan-at-work commented 2 months ago

@jeslinjacob1995 Is this fixed ?

TheMaverickProgrammer commented 4 weeks ago

This is still happening for us in beamer 1.7.0. The history is cleared but pressing back on a web browser still takes us back to the last page we were on, but we expect a cleared history to result in a no-op for the back button.

stan-at-work commented 4 weeks ago

This is still happening for us in beamer 1.7.0. The history is cleared but pressing back on a web browser still takes us back to the last page we were on, but we expect a cleared history to result in a no-op for the back button.

How do you clear the history?

TheMaverickProgrammer commented 3 weeks ago

This is still happening for us in beamer 1.7.0. The history is cleared but pressing back on a web browser still takes us back to the last page we were on, but we expect a cleared history to result in a no-op for the back button.

How do you clear the history?

On web, we're using beamBack within a nested Beamer. beamBack has a comment claiming to remove the last entry in history. We have tried a scenario without a nested Beamer as well. We see the navigation stack is different in this scenario, but the outcome on web is exactly the same which is odd.

For instance, say I'm on page A and go to page B. If we beam back from B, then I expect B to be the last entry and so it gets removed. We then end up on A. However, from A, pressing the back arrow once more in the browser will go to back B!

We can get around this, sort of, by using beamToReplacementNamed which clears the history, but pressing back on the web browser will do nothing, when we expect to just return to page A and then stop there.

ryanscott0515 commented 3 weeks ago

Here is a case with Beamer using beamBack compared to the browser's back arrow, on Chrome 129, Flutter 3.24.2, and Beamer 1.7.0:

https://github.com/user-attachments/assets/804ab4f3-1e9e-4d9e-886e-c312e46a7ec5

In this video, I call beamToNamed on some pages. I do this three times, pushing page1, page2, and page3, and then click the browser back arrow, and finally press a button which will call beamBack.

As expected, the browser back button returns me to page2, and then the subsequent beamBack leads to page1.

When I change the order of these operations, something different happens. From 00:08, starting on page1, I push page2, page3, and page4. I hover the back button for a moment, but do not press it. I click the button to beamBack, which brings me to page3, and then I click the browser back button. I expect this to bring me to page2, but instead it brings me back to page4, which had been supposedly cleared by the previous beamBack. It seems like the browser history disagrees with the history Beamer knows about.

Here is the code I used for this:

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

const int _numPages = 5;
const String _prefix = "/page";
final List<String> paths = [
  for (int i = 0; i < _numPages; i++) "$_prefix$i",
];

String _depthToNamed(int depth) {
  return paths[depth];
}

int _nameToDepth(String name) {
  return int.parse(name.split(_prefix.substring(1)).last);
}

void main() {
  Beamer.setPathUrlStrategy();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  MyApp({super.key});
  final routerDelegate = BeamerDelegate(
    initialPath: paths[0],
    locationBuilder: BeamerLocationBuilder(
      beamLocations: [
        Locations(),
      ],
    ).call,
  );

  @override
  Widget build(BuildContext context) {
    return BeamerProvider(
      routerDelegate: routerDelegate,
      child: MaterialApp.router(
        title: 'Beamer test',
        debugShowCheckedModeBanner: false,
        routerDelegate: routerDelegate,
        routeInformationParser: BeamerParser(),
        backButtonDispatcher: BeamerBackButtonDispatcher(
          delegate: routerDelegate,
          alwaysBeamBack: true,
          fallbackToBeamBack: true,
        ),
      ),
    );
  }
}

class Screen extends StatelessWidget {
  const Screen({
    super.key,
    required this.name,
    this.depth = 0,
  });

  final String name;
  final int depth;

  void _beamToNext(BuildContext context) {
    Beamer.of(context).beamToNamed(_depthToNamed(depth + 1));
  }

  void _back(BuildContext context) {
    Beamer.of(context).beamBack();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Text(name),
          if (depth + 1 < _numPages)
            MaterialButton(
              height: 60.0,
              minWidth: 60.0,
              onPressed: () => _beamToNext(context),
              child: Text("Beam to ${depth + 1}"),
            ),
          if (depth > 0)
            MaterialButton(
              height: 60.0,
              minWidth: 60.0,
              onPressed: () => _back(context),
              child: Text("Beam back to ${depth - 1}"),
            ),
        ],
      ),
    );
  }
}

class MyNamedScreen extends StatelessWidget {
  const MyNamedScreen({super.key, required this.name});

  final String name;

  @override
  Widget build(BuildContext context) {
    return Screen(
      name: name,
      depth: _nameToDepth(name),
    );
  }
}

class Locations extends BeamLocation<BeamState> {
  @override
  List<Pattern> get pathPatterns => paths;

  @override
  List<BeamPage> buildPages(BuildContext context, BeamState state) {
    return [
      for (String s in state.pathPatternSegments)
        if (pathPatterns.contains('/$s'))
          BeamPage(
            key: ValueKey(s),
            child: MyNamedScreen(
              name: s,
            ),
          )
    ];
  }
}