Flutter for web: back/forward button problem

736 Views Asked by At

I am facing the following problem:

I have a StatefulWidget which can generate different view in case of state change:

class _HomePageState extends State<HomePage> {
  bool _changed = false;

  @override
  Widget build(BuildContext context) {
    return changed ? View2() : View1();
  }
}

If I change a state _changed to true and hit the back button, a then the forward button, I would like my application to create fresh _HomePageState and display a View1, but currently the View2 is presented, so I believe that the state is persisted somehow.

How can I force rebuild of the widget's state?

Example app presenting the problem: link

Steps to reproduce:

  1. Open some site, eg. www.google.com
  2. Paste the link to the app https://issueapp.tiiny.site
  3. Change the state of the app by pressing a button
  4. Press browser's back button
  5. Press browser's forward button

GIF presenting the issue:

enter image description here

Full code which reproduces the problem:

import 'package:flutter/material.dart';

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

class Application extends StatelessWidget {
  const Application({super.key});

  @override
  Widget build(BuildContext context) => const HomePage();
}

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

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

class _HomePageState extends State<HomePage> {
  bool _changed = false;

  @override
  Widget build(BuildContext context) {
    return Directionality(
      textDirection: TextDirection.ltr,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(_changed ? "CHANGED" : "NOT CHANGED"),
          ElevatedButton(
            onPressed: () { setState(() { _changed = !_changed; }); },
            child: const Text("CHANGE"),
          ),
        ],
      ),
    );
  }
}
4

There are 4 best solutions below

0
On

This is because your screen is not changing, So browsers are not detecting. You can use WillPopScope see below code :

import 'package:flutter/material.dart';

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

class Application extends StatelessWidget {
  const Application({super.key});

  @override
  Widget build(BuildContext context) => const HomePage();
}

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

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

class _HomePageState extends State<HomePage> {
  bool _changed = false;

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        setState(() {
          _changed = !_changed;
        });
        return false;
      },
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text(_changed ? "CHANGED" : "NOT CHANGED"),
            ElevatedButton(
              onPressed: () { setState(() { _changed = !_changed; }); },
              child: const Text("CHANGE"),
            ),
          ],
        ),
      ),
    );
  }
}
0
On

Just wrap your Column with WillPopScope & add the below condition for its willPopScope property,

onWillPop: () async {
        setState(() {
          _changed;
        });
        return true;
      },

However, I'll recommend using state management tools like value notifiers or change notifiers to have a smooth process.

6
On

This only happen on release version of flutter web. Your flutter web state will be keep in navigation stack, until you do a page refresh.

Here some additional comment from flutter web issue related to Refreshing browser resets navigation stack and local variables :

When you refresh the page, the entire app is destroyed and launched again. This is how the browser works, nothing specific to Flutter here. So the values of local variables are expected to disappear.

The way navigation works today is by keeping the navigation stack in memory. This means the stack is destroyed on page refresh, and the history is lost.

Read the github complete comment here by mdebbar

2
On

There are a couple of ways to do that, the easiest way now is just adding a wrapper as a parent widget, that receives a callback when you press the back button from your browser, and then restart the widget.

Results:

enter image description here

Code:

  • You need to call MyParentWidget first.
  • You can use any widget to refresh the key, I'm just using setState for simplicity.
class MyParentWidget extends StatefulWidget {
  const MyParentWidget({
    super.key,
  });

  @override
  State<MyParentWidget> createState() => _MyParentWidgetState();
}

class _MyParentWidgetState extends State<MyParentWidget> {
  @override
  Widget build(BuildContext context) {
    return MyHomePage(
      key: UniqueKey(),
      onBackPressed: () {
        setState(() {});
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    required this.onBackPressed,
    super.key,
  });

  final VoidCallback onBackPressed;

  @override
  State<MyHomePage> createState() {
    return _MyHomePageState();
  }
}

class _MyHomePageState extends State<MyHomePage> {
  bool _changed = false;

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () => Future(() {
        widget.onBackPressed();
        return false;
      }),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            _textForCurrentState(),
            style: const TextStyle(
              color: Colors.black,
              decoration: TextDecoration.none,
            ),
          ),
          ElevatedButton(
            onPressed: () {
              setState(() {
                _changed = !_changed;
              });
            },
            child: const Text("CHANGE"),
          ),
        ],
      ),
    );
  }

  String _textForCurrentState() {
    return _changed ? "CHANGED" : "NOT CHANGED";
  }
}