csells / go_router

The purpose of the go_router for Flutter is to use declarative routes to reduce complexity, regardless of the platform you're targeting (mobile, web, desktop), handling deep linking from Android, iOS and the web while still allowing an easy-to-use developer experience.
https://gorouter.dev
441 stars 97 forks source link

allow pages to stop from navigating away, e.g. if there are unsaved state changes #138

Closed jamolina2021 closed 2 years ago

jamolina2021 commented 3 years ago

Hello, I am going to start a web project with Flutter, for another project use the Fluro library, however, I could not solve the following problem: If a user is on a page that contains a form and is making modifications is said form, I would like that if he tries to exit without having saved the modifications (either by putting another url, with the browser's backbutton, ...) it would show him an alertdialog giving him the possibility of canceling the navigation to the new url or, on the contrary, of canceling the modifications and staying on the same page where he is. The question is, can I do this with go_router? go_router has any mechanism by which this functionality can be achieved? Please, if this functionality is possible with go_router could you give me some indications. Thank you so much for all the help. Regards, Jose

csells commented 3 years ago

Have you tried using a WillPopScope widget? I'm curious what happens when you do.

ghost commented 3 years ago

Hello @csells

Have you tried using a WillPopScope widget? I'm curious what happens when you do.

It just has no effect. The onWillPop event is not executed under any circumstances.

2021-11-06_10-32-12

Thanks. Regards, Jose

csells commented 3 years ago

I don't yet know how to do this in go_router or using the routing API (aka Nav2) at all. I agree it would be a good feature to add.

ghost commented 3 years ago

This feature is essential for form control and it is strange that the Flutter development team did not take it into account, since any professional application should have it. Please, if you find any way to include this feature in go_router let me know, otherwise it is impossible to develop professional applications with flutter web. Thank you so much for all your wonderful work. Go_router is a fabulous library.

jamolina2021 commented 3 years ago

Hi csells, I have continued investigating and the backbutton does trigger the onWillPop event, but the url change does not trigger it. Thanks. Regards, Jose

ghost commented 2 years ago

Any news regarding this problem?

csells commented 2 years ago

This is an excellent feature to add to go_router. I haven't added it yet, however. Perhaps you'd care to submit a PR?

ghost commented 2 years ago

I have seen this feature in qlevar_router (https://github.com/SchabanBo/qlevar_router), it also has a fabulous example of how to create a dashboard very easily (https://github.com/SchabanBo/qr_samples/blob/44d6268cc2eeff32e263bf4fd6c43e25f2e00dd5/lib/common_cases/dashboard.dart#L135). It has incredible support. :)

csells commented 2 years ago

It sounds like you're all set with qlevar_router. Certainly it's been around longer than go_router.

ghost commented 2 years ago

I really like the philosophy and documentation of go_router, but things as basic as a dashboard structure (an example would be great) or the functionality of this ticket should already exist, but as time is passing and it is not solved I have had to look other alternatives. Thanks @csells.

csells commented 2 years ago

Many sample and features of go_router have come from contributors. If there are features you feel are missing, please feel free to contribute.

ghost commented 2 years ago

Thanks @csells.

csells commented 2 years ago

@jamolina2021 Perhaps WillPopScope will do the trick? I've got this working so far:

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

void main() => runApp(const MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

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

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final result = await showOkCancelAlertDialog(
          context: context,
          title: 'Pop',
          message: 'Are you sure you want to pop?',
        );

        return result == OkCancelResult.ok;
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Flutter WillPopScope demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              OutlinedButton(
                child: const Text('Push'),
                onPressed: () {
                  Navigator.of(context).push<void>(
                    MaterialPageRoute<void>(
                      builder: (BuildContext context) {
                        return const MyStatefulWidget();
                      },
                    ),
                  );
                },
              ),
              const Text('Push to a new screen, then press the back '
                  'button in the appBar to ask if a pop is ok.'),
            ],
          ),
        ),
      ),
    );
  }
}

I have to see about getting it to work with go_router next.

csells commented 2 years ago

@jamolina2021 I'm able to make WillPopScope work for this case using the code below. Can you confirm?

import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(MyApp());

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

  static const String _title = 'Flutter Code Sample';

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        title: _title,
        routerDelegate: router.routerDelegate,
        routeInformationParser: router.routeInformationParser,
      );

  final router = GoRouter(
    routes: [
      GoRoute(
        path: '/',
        builder: (c, s) => const MyStatefulWidget(),
      )
    ],
  );
}

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

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final result = await showOkCancelAlertDialog(
          context: context,
          title: 'Pop',
          message: 'Are you sure you want to pop?',
        );

        return result == OkCancelResult.ok;
      },
      child: Scaffold(
        appBar: AppBar(title: const Text('Flutter WillPopScope demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              OutlinedButton(
                child: const Text('Push'),
                onPressed: () => context.push('/'),
              ),
              const Text('Push to a new screen, then press the back '
                  'button in the appBar to ask if a pop is ok.'),
            ],
          ),
        ),
      ),
    );
  }
}
Screen Shot 2021-12-30 at 1 54 45 PM
csells commented 2 years ago

I also added a writeup in the docs: https://gorouter.dev/user-input. can you let me know if this works for you?