Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter - Persistent State between Pages

Tags:

flutter

I'm trying to store some state during page changes. So old data values are available when a page is reloaded.

I've been looking into PageStorage but haven't figured out how it works yet.

I'm writing into the storage with PageStorage.of(context)?.writeState(context, 'Some text is here', identifier: ValueKey('test')); and then unloading the page with the back button.

When I reload the page (with Navigator.of(context).push()), using PageStorage.of(context)?.readState(context, identifier: ValueKey('test')); just gives me null;

Here's a short sample that I wrote to demonstrate how I'm using it. Does anyone know what I'm doing wrong?

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

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() {
    return new MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  final PageStorageBucket _bucket = new PageStorageBucket();
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: PageStorage(
        child: new MyHomePage(),
        bucket: _bucket,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: FloatingActionButton(
        onPressed: () {
          Navigator.of(context).push(new MaterialPageRoute(builder: (context) {
            return new NewPage();
          }));
        },
      ),
    );
  }
}

class NewPage extends StatefulWidget {
  NewPageState createState() => NewPageState();
}

class NewPageState extends State<NewPage> {
  String _text = '';
  @override
  void initState() {
    super.initState();
    _text = PageStorage
        .of(context)
        ?.readState(context, identifier: ValueKey('test'));
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: <Widget>[
          Text('The text is $_text'),
          FloatingActionButton(
            onPressed: () {
              setState(() {
                PageStorage.of(context)?.writeState(
                    context, 'Some text is here',
                    identifier: ValueKey('test'));
              });
            },
          )
        ],
      ),
    );
  }
}
like image 443
Mojachiee Avatar asked Jul 05 '18 15:07

Mojachiee


People also ask

How do I use PageStorageKey?

By adding a PageStorage at the root and adding a PageStorageKey to each page, some of the page's state (e.g. the scroll position of a Scrollable widget) will be stored automatically in its closest ancestor PageStorage, and restored when it's switched back.

What is Pagestoragebucket in flutter?

A storage bucket associated with a page in an app. Useful for storing per-page state that persists across navigations from one page to another.


1 Answers

There were multiple issues with the code you provided.

The first one being in your MyAppState where you didn't provided a key to your PageStorage. Indeed without the key , the written data cannot be saved and I quote :

writeState(BuildContext context, dynamic data, {Object identifier}) → void package:flutter

Write the given data into this page storage bucket using the specified identifier or an identifier computed from the given context. The computed identifier is based on the PageStorageKeys found in the path from context to the PageStorage widget that owns this page storage bucket.

If an explicit identifier is not provided and no PageStorageKeys are found, then the data is not saved.

To resolve this just create a global variable PageStorageKey mykey = new PageStorageKey("testkey"); and pass it along the creation of your PageStorage:

 class MyAppState extends State<MyApp> {
  final PageStorageBucket _bucket = new PageStorageBucket();
  @override
  Widget build(context) {
    return new MaterialApp(
      home: PageStorage(
        child: new MyHomePage(),
        bucket: _bucket,
        key: mykey,
      ),
    );
  }
}

Then use the same key again to write the data :

onPressed: () {
              setState(() {
                PageStorage.of(context).writeState(context, 'Data saved',
                    identifier: ValueKey(mykey));
                updateText();
              });

Finally the way you update the text is, in my opinion not the best way to do it. You should create a method (updateText() for example) and call it after you wrote your data.

 updateText() {
    if (PageStorage.of(context) .readState(context, identifier: ValueKey(mykey)) != null) {
      _text = PageStorage .of(context).readState(context, identifier: ValueKey(mykey));
    } 
    else {
      _text = 'PageStorageNull';
    }
  }

As always it's safer to check if the value is non-null to avoid errors. Here is the full code :

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

PageStorageKey mykey = new PageStorageKey("testkey");

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() {
    return new MyAppState();
  }
}

class MyAppState extends State<MyApp> {
  final PageStorageBucket _bucket = new PageStorageBucket();
  @override
  Widget build(context) {
    return new MaterialApp(
      home: PageStorage(
        child: new MyHomePage(),
        bucket: _bucket,
        key: mykey,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(context) {
    return Center(
      child: FloatingActionButton(
        onPressed: () {
          Navigator.push(context,
              new MaterialPageRoute(builder: (context) => new NewPage()));
        },
      ),
    );
  }
}

class NewPage extends StatefulWidget {
  NewPageState createState() => NewPageState();
}

class NewPageState extends State<NewPage> {
  String _text;

  @override
  void initState() {
    super.initState();
  }

  updateText() {
    if (PageStorage.of(context) .readState(context, identifier: ValueKey(mykey)) != null) {
      _text = PageStorage .of(context).readState(context, identifier: ValueKey(mykey));
    } 
    else {
      _text = 'PageStorageNull';
    }
  }

  @override
  Widget build(context) {
    return Center(
      child: Column(
        children: <Widget>[
          Text('The text is $_text'),
          FloatingActionButton(
            onPressed: () {
              setState(() {
                PageStorage.of(context).writeState(context, 'Data saved',
                    identifier: ValueKey(mykey));
                updateText();
              });
            },
          )
        ],
      ),
    );
  }
}

With this code, press the button to go to the second page. On the second page press the button to update the text with the data provided in the writeState() method.

Hoping this can help you,

Regards

EDIT

Fist things first, sorry for misunderstanding the point.

And actually what you want is possible by using Buckets. Indeed the : PageStorage .of(context).readState(context, identifier: ValueKey(mykey)); can be replace by :

_bucket.readState(context, identifier: ValueKey(mykey));

So what you have to do is make your _bucket variable global, then you need to wrap everything you have in your NewPageState within a PageStorage using the same Key and Bucket as your first PageStorage in the MyAppState

Doing so you will be able to read using the bucket too and keep your data through navigation.

Again he is the full code:

import 'package:flutter/material.dart';

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

PageStorageKey mykey = new PageStorageKey("testkey");
final PageStorageBucket _bucket = new PageStorageBucket();

class MyApp extends StatefulWidget {
  @override
  MyAppState createState() {
    return new MyAppState();
  }
}

class MyAppState extends State<MyApp> {

  @override
  Widget build(context) {
    return new MaterialApp(
      home: PageStorage(
        child: new MyHomePage(),
        bucket: _bucket,
        key: mykey,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(context) {
    return Center(
      child: FloatingActionButton(
        onPressed: () {
          Navigator.push(context,
              new MaterialPageRoute(builder: (context) => new NewPage()));
        },
      ),
    );
  }
}

class NewPage extends StatefulWidget {
  NewPageState createState() => NewPageState();
}

class NewPageState extends State<NewPage> {
  String _text;

  @override
  void initState() {
    super.initState();
    updateText();
  }

  updateText() {

    if (_bucket.readState(context, identifier: ValueKey(mykey)) != null) {
      _text = _bucket.readState(context, identifier: ValueKey(mykey));

    } 
    else {
       print(_bucket.toString());

    }
  }

  @override
  Widget build(context) {
    return PageStorage(
      key:mykey,
      bucket: _bucket,
      child: Column(
        children: <Widget>[
          Text('The text is $_text'),
          FloatingActionButton(
            onPressed: () {
              setState(() {
                _bucket.writeState(context, 'Data saved',
                    identifier: ValueKey(mykey));
                updateText();
              });
            },
          )

        ],
      ),
    );
  }
}
like image 178
Arendhal Avatar answered Sep 26 '22 14:09

Arendhal