GIfatahTH / states_rebuilder

a simple yet powerful state management technique for Flutter
494 stars 56 forks source link

navigator 2 WillPopScope is not working #256

Closed tk2232 closed 1 year ago

tk2232 commented 2 years ago

With the new navigator 2 WillPopScope is not working. Do we have to override the back button differently with navigator 2?

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: navigatorRM.routeInformationParser,
      routerDelegate: navigatorRM.routerDelegate,
    );
  }
}

final navigatorRM = RM.injectNavigator(
  initialLocation: '/TestPage2',
  routes: {
    '/TestPage1': (_) => const TestPage1(),
    '/TestPage2': (_) => const TestPage2(),
  },
);

class TestPage2 extends StatelessWidget {
  const TestPage2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        print('onWillPop page 2');
        return false;
      },
      child: Scaffold(
        body: Center(
          child: Container(
            color: Colors.red,
            height: 500,
            width: 500,
            child: ElevatedButton(
              onPressed: () {
                navigatorRM.to('/TestPage1');
              },
              child: const Text('Nav to page 1'),
            ),
          ),
        ),
      ),
    );
  }
}

class TestPage1 extends StatelessWidget {
  const TestPage1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        print('onWillPop page 1');
        return false;
      },
      child: Scaffold(
        body: Center(
          child: Container(
            color: Colors.green,
            height: 500,
            width: 500,
            child: ElevatedButton(
              onPressed: () {
                navigatorRM.to('/TestPage2');
              },
              child: const Text('Nav to page 2'),
            ),
          ),
        ),
      ),
    );
  }
}
amoslai5128 commented 2 years ago

@GIfatahTH Would you mind taking a look?

GIfatahTH commented 2 years ago

@amoslai5128 @tk2232 I am working on a solution for this issue.

GIfatahTH commented 2 years ago

From Flutter docs, WillPopScope.onWillPop is invoked only if the route is popped using maybePop method.

maybePop is implicitly called when the back button of an android device is tapped or when the back arrow of the AppBar of a Scaffold is pressed.

See below your example modified so the WillPopScope is used correctly.

The current nav2 api works well with WillPopScope.

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: navigatorRM.routeInformationParser,
      routerDelegate: navigatorRM.routerDelegate,
    );
  }
}

final navigatorRM = RM.injectNavigator(
  initialLocation: '/TestPage2',
  routes: {
    '/TestPage1': (_) => const TestPage1(),
    '/TestPage2': (_) => const TestPage2(),
  },
);

class TestPage2 extends StatelessWidget {
  const TestPage2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        print('onWillPop page 2');
        return false;
      },
      child: Scaffold(
        body: Center(
          child: Container(
            color: Colors.red,
            height: 500,
            width: 500,
            child: ElevatedButton(
              onPressed: () {
                navigatorRM.to('/TestPage1');
              },
              child: const Text('Nav to page 1'),
            ),
          ),
        ),
      ),
    );
  }
}

class TestPage1 extends StatelessWidget {
  const TestPage1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        print('onWillPop page 1');
        return false;
      },
      child: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
            color: Colors.green,
            height: 500,
            width: 500,
            child: Column(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).maybePop();
                    },
                    child: const Text('Navigate back using maybePop'),
                  ),
                ),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                      //or
                      // navigatorRM.back();
                    },
                    child: const Text('Navigate back using pop'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
GIfatahTH commented 2 years ago

@tk2232 @amoslai5128

In the current version, there is the onNavigateBack parameter of InjectedNavigator that can be used to globally control the back navigation.

For the next version, I added the OnNavigateBackScope widget that can be used to locally control all kind of back navigation.

Try this example:

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

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: navigatorRM.routeInformationParser,
      routerDelegate: navigatorRM.routerDelegate,
    );
  }
}

final navigatorRM = RM.injectNavigator(
  initialLocation: '/TestPage2',
  routes: {
    '/TestPage1': (_) => const TestPage1(),
    '/TestPage2': (_) => const TestPage2(),
  },
);

class TestPage2 extends StatelessWidget {
  const TestPage2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.red,
          height: 500,
          width: 500,
          child: ElevatedButton(
            onPressed: () {
              navigatorRM.to('/TestPage1');
            },
            child: const Text('Nav to page 1'),
          ),
        ),
      ),
    );
  }
}

class TestPage1 extends StatelessWidget {
  const TestPage1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return OnNavigateBackScope(
      onNavigateBack: () {
        RM.navigate.toDialog(
          AlertDialog(
            content: Text('Are you sure to quit this page'),
            actions: [
              TextButton(
                onPressed: () {
                  navigatorRM.forceBack();
                },
                child: Text('YES'),
              ),
              TextButton(
                onPressed: () {
                  navigatorRM.back();
                },
                child: Text('No'),
              ),
            ],
          ),
          postponeToNextFrame: true,
        );
        return false;
      },
      child: Scaffold(
        appBar: AppBar(),
        body: Center(
          child: Container(
            color: Colors.green,
            height: 500,
            width: 500,
            child: Column(
              children: [
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).maybePop();
                    },
                    child: const Text('Navigate back using maybePop'),
                  ),
                ),
                Expanded(
                  child: ElevatedButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                      //or
                      // navigatorRM.back();
                    },
                    child: const Text('Navigate back using pop'),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
tk2232 commented 2 years ago

I tested OnBackNavigationScope and it works without any problems. Thanks, great job. I would wait for the release version before closing the issue

amoslai5128 commented 2 years ago

@GIfatahTH Thanks for the amazing solution, and I really hope this library can get better day by day. Recently I do a project that used VueJS 3, and there is a v-router package feels very user-friendly at some points to me, I think it deserves to be taken a look at. https://router.vuejs.org/guide/

Especially at the "Named Views" section: Here is an online playground, all the UI widgets were defined in UserSettings.vue, and route settings in router.js. Whenever there is a navigation event, the <router-view> works like a rebuilder to display the corresponding screen, most importantly the browser back button and history mode work perfectly without any configs needed for the developer.