1, the origin

If I didn’t mention the restorationId attribute, most people probably wouldn’t know what it does or even exists. Even though it occurs frequently in components as arguments. Let’s take a look at some components that have that property, such as a restorationId property in the ListView.


There are also restorationId properties in the GridView.


The PageView component also has a restorationId property.


There is also a restorationId property in the SingleChildScrollView component.


There is also a restorationId property in the NestedScrollView component.


There is also a restorationId property in the CustomScrollView component.


There is also a restorationId property in the TextField component.

There are many other components that have the restorationId property, and you can feel that any component that has a touch of sliding has a restorationId property. With that said, let’s take a look at what this property does.


2. What the restorationId property does

Here’s an example of what the restorationId property can do using ListView. The following two giFs have the effect of no restorationId and with restorationId respectively. See that restorationId is used to keep sliding offsets under certain circumstances.

No restorationId There are restorationId
class ListViewDemo extends StatelessWidget {
  const ListViewDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView(
      restorationId: 'toly'.//tag1
        children: List.generate(25, (index) => ItemBox(index: index,)).toList()); }}class ItemBox extends StatelessWidget {
  final int index;

  const ItemBox({Key? key, required this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      decoration: BoxDecoration(
          border: Border(
              bottom: BorderSide(
        width: 1 / window.devicePixelRatio,
      ))),
      height: 56,
      child: Text(
        'the first$indexA ',
        style: TextStyle(fontSize: 20),),); }}Copy the code

In addition, in order to demonstrate the trigger of recovery, you need to check the Do not Retain Activity in the developer option, which will kill the Activity after the user leaves. For example, when you click the Home button or the menu bar to switch screens, the Activity is not destroyed immediately, but the system depends on the situation. Turning this option on avoids the uncertainty of testing. Note: Be sure to turn it off after testing.

In Android, this is done through onSaveInstanceState. When the system destroys your Activity “without your permission,” such as switching between horizontal and vertical screens, clicking the Home button, or navigating the menu bar. The system provides an opportunity for you to save temporary state data via onSaveInstanceState callbacks, so that the next time the user enters, it will feel like a violation. It is also important to note that the state is not stored permanently, and onSaveInstanceState is not triggered when the user voluntarily exits the application. That is, if you set up a restorationId for a ListView and the user swiped it and then hit the back key to exit, it won’t revert to its original location when it comes back in. Note that for this to work you need to specify an arbitrary string for restorationScopeId in the MaterialApp.


3. How to store other data using the Restoration mechanism

A lot of people might be satisfied by now that restorationId stores temporary state, a new skill called GET. But that’s just the tip of the iceberg, restorationId is packaged in a ListView and can only store slide offsets, so it’s worth digging deeper. Let’s take a look at RestorationMixin with a little timer demo.

Ordinary counter State storage counter

The above two timers dynamically show that they can store state data when the user actively exits the application and maintain state when the user enters. The key is RestorationMixin. Ordinary timer source is not posted, we should already be familiar with the heart. The implementation defines a RestorableCounter component for interface presentation,

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

class RestorationExampleApp extends StatelessWidget {
  const RestorationExampleApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      restorationScopeId: 'app',
      title: 'Restorable Counter',
      home: RestorableCounter(restorationId: 'counter')); }}class RestorableCounter extends StatefulWidget {
  const RestorableCounter({Key? key, this.restorationId}) : super(key: key);
  final String? restorationId;
  @override
  State<RestorableCounter> createState() => _RestorableCounterState();
}
Copy the code

Do this in _RestorableCounterState: First mix in RestorationMixin, then overwrite the restorationId and restoreState methods. Provide a RestorableInt object to record values.

class _RestorableCounterState extends State<RestorableCounter>
    with RestorationMixin{ // 1. Mixin RestorationMixin

  // 3. Use RestorableInt to record values
  final RestorableInt _counter = RestorableInt(0);


  // 2. Overriding restorationId Provides ids
  // @override
  String? get restorationId => widget.restorationId;


  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    // 4. Register _counter
    registerForRestoration(_counter, 'count');
  }


  @override
  void dispose() {
    _counter.dispose(); / / 5. Destroyed
    super.dispose();
  }
Copy the code

In component builds, we can access or manipulate values via _counter. Value.

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Restorable Counter'),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Text('You have pushed the button this many times:'),
          Text(
            '${_counter.value}',
            style: Theme.of(context).textTheme.headline4,
          ),
        ],
      ),
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: _incrementCounter,
      tooltip: 'Increment',
      child: const Icon(Icons.add),
    ),
  );
}

void _incrementCounter() {
  setState(() {
    _counter.value++;
  });
}
Copy the code

What we just had was a RestorableInt, so you might be worried about other data types. A number of RestorableXXX data types are available in Flutter for use. If not, you can customize RestorableXXX to fulfill requirements by extending RestorableProperty

.

According to the official update announcement, iOS is not currently supported, but will be in the future.


4. How is state storage implemented in a sliding system

After watching the little demo above, you might be wondering how it’s stored in a sliding system, so let’s take a look. We follow the restorationId property trail of ListView and see that it passes all the way to the parent construct. It is eventually used in ScrollView as an input parameter to the Scrollable component. That is, the root of this attribute is used in Scrollable. This component is the root of the sliding trigger, which is why the sliding related components all have the restorationId attribute.

ListView --> BoxScrollView --> ScrollView --> Scrollable
Copy the code


ScrollableState is mixed in with RestorationMixin, where the type used for storage is _RestorableScrollOffset.

RestoreState and restorationId methods are also overridden.


The TextField component also has this state recovery feature.

That’s it for this article, but more in-depth RestorationMixin implementations and their related classes remain to be explored.