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!

Top comments (9)

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 Author

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!

Collapse
kkimj profile image
Jiun Kim • Edited on

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 Author

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
disterbia profile image
disterbia

🌚 Browsing with dark mode makes you a better developer by a factor of exactly 40.

It's a scientific fact.