DEV Community

Cover image for Flutter Web - Fix Refresh & Back button Problem
swimmingkiim
swimmingkiim

Posted on • Updated on

Flutter Web - Fix Refresh & Back button Problem

Image description

Update Notice!!!

The explaination below is somewhat outdated. Now, I recommends you to use go_router, a routing package using Flutter Navigation 2.0!

Intro

When I’m on a flutter web, I faced with routing issue. Both MaterialApp and GetMaterialApp had the same issues, which was routing acts weird when browser’s back button and refresh button are clicked. So I’ve tried some test project with flutter web and I found the solution. Now, it works just like native web project. I’d like to share this solution with you.

Flutter Navigation 2.0

There is this thing called Flutter Navigation 2.0 . I didn’t dig much about it, but to simply put, it’s navigation system is now more customizable with this Navigation 2.0.

Since the original routing system had so many problems in web platform, I had to try this one. And it worked, thankfully. You can use with MaterialApp.router instead of just MaterialApp . But if you do that you need to implement some override method which involves in navigation logic. Instead overriding each methods, I used GetMaterialApp.router which comes with default methods, and therefore, only extra thing to do is just build method.

Code Implementation

Install Getx

At this moment, I used below version of Getx package.



dependencies:
  ...
  get: ^4.6.1


Enter fullscreen mode Exit fullscreen mode

Basic Routing Stuff

For convenience, I used simple routing path and getPages with snippet.



abstract class Routes {
  static const HOME = '/';
  static const LOGIN = '/login';
  static const SIGNUP = '/signup';
}


Enter fullscreen mode Exit fullscreen mode



abstract class AppPages {
  static final pages = [
    GetPage(
      name: Routes.HOME,
      page: () => Home(),
    ),
    GetPage(
      name: Routes.LOGIN,
      page: () => Login(),
    ),
    GetPage(
      name: Routes.SIGNUP,
      page: () => Signup(),
    ),
  ];
}


Enter fullscreen mode Exit fullscreen mode

Create GetMaterialApp.router

When you use GetMaterialApp.router instead of just GetMaterialApp , you don’t need to provide(and you can’t) initialRoute . But you need to provide getPages and routerDelegate fields with values.



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

void main() {
  runApp(GetMaterialApp.router(
    debugShowCheckedModeBanner: false,
    defaultTransition: Transition.fade,
    getPages: AppPages.pages,
    routerDelegate: AppRouterDelegate(),
  ));
}


Enter fullscreen mode Exit fullscreen mode

Create Sample Pages

For testing, I created some simple test pages with different background colors.

  • Note that when you navigate between pages, you need to use Get.rootDelegate instead of Get because we’re using routerDelegate to handle routing.


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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red,
      child: TextButton(
        child: Text(
          'Home',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () => Get.rootDelegate.toNamed(Routes.LOGIN),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.orange,
      child: TextButton(
        child: Text(
          'Login',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () => Get.rootDelegate.toNamed(Routes.SIGNUP),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: TextButton(
        child: Text(
          'Signup',
          style: TextStyle(color: Colors.white),
        ),
        onPressed: () => Get.rootDelegate.toNamed(Routes.HOME),
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

Create router delegate with GetDelegate

So, this is the main part.

  • Create AppRouterDelegate or what ever you like to call with GetDelegate extended.
  • Override build method and return Navigator with onPopPage and pages .
    • onPopPage → This is invoked when pop action happened, and return wheather pop is successful or not. If you don’t want to block pop action, just leave it there with default code.
    • pages → This is different from getPages . This is more related to what page to show in current moment. I’m not fully figured out yet, but it’s also different from navigation history stack. So, in order to show the right page according to current url, I used currentConfiguration.currentPage . Note that this field can’t be empty since it related to current view. When currentConfiguration is null(which means first entered the page), you can just simply return initial page.



class AppRouterDelegate extends GetDelegate {
  @override
  Widget build(BuildContext context) {
    return Navigator(
      onPopPage: (route, result) => route.didPop(result),
      pages: currentConfiguration != null
          ? [currentConfiguration!.currentPage!]
          : [GetNavConfig.fromRoute(Routes.HOME)!.currentPage!],
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

And plus, since you can write build method and other override method(if you want to customize login) you can easily add user authentication status check before navigate to any page.

Conclusion

I think Flutter Web has a long way to go, but I’m glad Flutter is keeping up there work! I hope this article can help those who struggles with Flutter Web routing issue.

Cheers!

Buy Me A Coffee

Top comments (22)

Collapse
 
korca0220 profile image
ParkJuneWoo

Hello!! This article was really helpful! Thank you
But how did you handle it like Get.back()? An error occurs in this process. Same as Get.rootDelegate.popRoute().

I would ultimately like to implement the following elements.

  1. Go back, go forward after refresh in a web browser. -> Success
  2. Widgets that return to the previous page when the <- button is pressed (previously use Get.back())
Collapse
 
swimmingkiim profile image
swimmingkiim

You can do that by using Get.roodDelegate.popHistory with custom logic.

  1. First, override popHistory method in AppRouterDelegate
class AppRouterDelegate extends GetDelegate {
  GetNavConfig get prevRoute =>  // here
      history.length < 2 ? history.last : history[history.length - 2];

  @override
  Future<GetNavConfig> popHistory() async {  // and here
    final result = prevRoute;
    Get.rootDelegate.offNamed(prevRoute.currentPage!.name);
    return result;
  }

  @override
  Widget build(BuildContext context) {
    return Navigator(
      ......
    );
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. And call it like this
Get.rootDelegate.popHistory();
Enter fullscreen mode Exit fullscreen mode

I've tested it and it works for me. Hope this can help you :-)

Collapse
 
korca0220 profile image
ParkJuneWoo

Thank you!! That was really helpful

If you refresh it, it doesn't work popHistory, but I think we can solve it in a different way!

Thread Thread
 
shambhulky profile image
shambhulky • Edited

How you have implemented this? Even without overriding, getting same result.

Collapse
 
kkimj profile image
Jiun Kim • Edited

Hello!
We can go back via html API, dart:html

// import
import 'dart:html' as html;

{
  // within callBack
  // ..
  html.window.history.back();
}
Enter fullscreen mode Exit fullscreen mode

Thanks!
HANW!

Collapse
 
kkimj profile image
Jiun Kim

Helloo Buddies~

Collapse
 
everfinal88 profile image
Paul Lam

Hi, thanks for the article. I tested the code and found out the browser back history did not really get back to the previous page state. For example when i scroll to certain location and navigate back again, instead of getting back new page was loaded. Is there any way we can preserve the page state using your code?

Collapse
 
swimmingkiim profile image
swimmingkiim

Yes, I also found some issue of that approach. As of now, the simplest way to solve this problem is using @kkimj 's method. ( window.history.back()) If you want to manually manage your history stack, then check out pushHistory method in Delegete class. Maybe you can cache those records and write your own back method logic.

Collapse
 
disterbia profile image
disterbia

when i did it your way on the flutter web, Get.rootDelegate.toNamed("/") works, but Get.rootDelegate.offToNamed("/") throws an Unexpected null value error. I think there is an error related to the key, but I can't figure it out.
Also Get.snackbar() throws the same error.

Collapse
 
8138976373 profile image
Safvan Puthalath

class AppRouterDelegate extends GetDelegate {
@override
Widget build(BuildContext context) {
return Navigator(
key: Get.key, //Please add this line to get snackbar,dialog,etc.
onPopPage: (route, result) => route.didPop(result),
pages: currentConfiguration != null
? [currentConfiguration!.currentPage!]
: [GetNavConfig.fromRoute(Routes.HOME)!.currentPage!],
);
}
}

Collapse
 
disterbia profile image
disterbia
Collapse
 
verrelravanelli profile image
verrelravanelli

Hey, i tried to open your stackoverflow link but its say already removed. Do you already find the solution for this problem? Thanks

Collapse
 
imanyarahmadi profile image
iManYarahmadi

hi my friends i use this article for my app route but im using this code on flutter web and when refresh or back,forward button in browser clicked nothing happend in my app.. how can handle this ?
my code like this

class AppRouterDelegate extends GetDelegate {
GetNavConfig get prevRoute => // here
history.length < 2 ? history.last : history[history.length - 2];

@override
Future popHistory() async {
// and here
final result = prevRoute;
Get.rootDelegate.offNamed(prevRoute.currentPage!.name);
return result;
}

@override
Widget build(BuildContext context) {
GlobalKey mainNavigatorKey = GlobalKey();

return Navigator(
  key: mainNavigatorKey,
  onPopPage: (route, result) => route.didPop(result),
  pages: currentConfiguration != null
      ? [currentConfiguration!.currentPage!]
      : [GetNavConfig.fromRoute(Routes.LOGIN)!.currentPage!],
);
Enter fullscreen mode Exit fullscreen mode

}
}

Collapse
 
woywie87 profile image
woywie87

Hi, It was very helpful. But I realized that with this fix, WillPopScope widget (or new PopScope) don't work (Flutter Web)
Case like this:

  1. Go to new page by "Get.rootDelegate.toNamed(Routes.SecondPage)"
  2. Press back button - onWillPop in SecondPage method never fired :/
Collapse
 
kyoshio profile image
Kyo

Hello, how do I make Getx compatible with Go router? My project depends heavily on GetX. How can I quickly migrate to go router

Collapse
 
raj_vaghasiya_c22fd5ee132 profile image
Raj Vaghasiya

have you found any solution?
i have same problem. please let me know

Collapse
 
suisui profile image
SuiSui

Yes, you can mix GetX pages and GoRouter.
Like this sample.

Future<void> main() async {
  usePathUrlStrategy();
  GoRouter.optionURLReflectsImperativeAPIs = true;
  var router = GoRouter(navigatorKey: Get.key, initialLocation: '/', routes: [
    GoRoute(
      name: 'home',
      path: '/',
      builder: (context, state) => YourHomePage(),
    ),
    GoRoute(
      name: 'page2',
      path: '/page2',
      builder: (context, state) => YourPage2(),
    )
  ]);

  runApp(GetMaterialApp.router(
    debugShowCheckedModeBanner: false,
    routeInformationParser: router.routeInformationParser,
    routerDelegate: router.routerDelegate,
    routeInformationProvider: router.routeInformationProvider,
    builder: (BuildContext context, Widget? child) {
      return child ?? Container();
    },
  ));
}
.
.
.
.
.
static toPage2() {
    BuildContext? context = Get.context;
    if(context==null) return;
    context.pushNamed('page2');
  }
Enter fullscreen mode Exit fullscreen mode
Collapse
 
wuwang profile image
huho

@swimmingkiim why you recommend to user go_router?

Collapse
 
_mohamed_mab profile image
Mohamed Elbendary • Edited

Hello !! the solution of overriding popHistory() not working, I can't go back to the previous page on mobile, is there any solution can handle it like Get.back() but in navigator 2.0

Collapse
 
shambhulky profile image
shambhulky

How to logout ? After logout, still going to previous page on back press.
How to go home from login so that login page will not open when we press back button ?

Collapse
 
impatelravi profile image
Ravi Patel

Try with arguments. It's not working. As it use root where no arguments are passed.

Collapse
 
danhquyen0109 profile image
danhquyen0109

You can try:

Get.rootDelegate.arguments()