Note to Keep Alive
Tags: flutter, beginners
Published: 2022-09-22 12:16:12

Recently testing on the example of keeping image in ListView. Simply using `AutomaticKeepAliveClientMixin` can solve the case.

This is an image keeping example class.

```dart
class ImageKeepAliveWidget extends StatefulWidget {
  const ImageKeepAliveWidget({Key? key}) : super(key: key);

  @override
  State<ImageKeepAliveWidget> createState() => _ImageKeepAliveWidgetState();
}

/// use [AutomaticKeepAliveClientMixin] keeping the state
class _ImageKeepAliveWidgetState extends State<ImageKeepAliveWidget>
    with AutomaticKeepAliveClientMixin<ImageKeepAliveWidget> {
  String? _imageUrl;

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

  void _fetchUrl() async {
    try {
      http.Response res =
          await http.get(Uri.parse('https://dog.ceo/api/breeds/image/random'));
      _imageUrl = json.decode(res.body)['message'];
      setState(() {});
    } catch (e) {
      debugPrint('error in getting image url');
    }
  }

  @override
  Widget build(BuildContext context) {
    /// must add super.build(context) to preserve state
    super.build(context);
    if (_imageUrl?.isNotEmpty == true) {
      return Image.network(_imageUrl!);
    } else {
      return const CircularProgressIndicator();
    }
  }

  @override
  bool get wantKeepAlive => true;
}
```

But how the mechanism is doing? Let’s take a look to the ListView first.

If you trace the ListView `addAutomaticKeepAlives` , you will find ListView children delegate is using `SliverChildListDelegate`. Inside its build function, you can see if the `addAutomaticKeepAlives` is true, the child widget will be wrapped by a `AutomaticKeepAlive` widget.

```dart
@override
  Widget? build(BuildContext context, int index) {
    /// ...
    if (addAutomaticKeepAlives)
      child = AutomaticKeepAlive(child: child);
    return KeyedSubtree(key: key, child: child);
  }
```

What will `AutomaticKeepAlive` do?

Checking the `_AutomaticKeepAliveState`, we can see the `_updateChild` is called when this stateful widget is updated. It wraps a NotificationListener for listening the KeepAliveNotification. And later it will wrap a `KeepAlive` widget for the listener widget. 

```dart
@override
void initState() {
  super.initState();
  _updateChild();
}

@override
void didUpdateWidget(AutomaticKeepAlive oldWidget) {
  super.didUpdateWidget(oldWidget);
  _updateChild();
}

void _updateChild() {
  _child = NotificationListener<KeepAliveNotification>(
    onNotification: _addClient,
    child: widget.child!,
  );
}

@override
Widget build(BuildContext context) {
  assert(_child != null);
  return KeepAlive(
    keepAlive: _keepingAlive,
    child: _child!,
  );
}
```

Let’s check what the notification callback has done in the listener widget.

In short, the callback function has 2 main goals. One is using a map to store the KeepAliveNotification handler. Another is to apply parent data to child which is the later `KeepAlive` widget.

```dart
bool _addClient(KeepAliveNotification notification) {
  final Listenable handle = notification.handle;
  _handles ??= <Listenable, VoidCallback>{};
  assert(!_handles!.containsKey(handle));
  _handles![handle] = _createCallback(handle);
  handle.addListener(_handles![handle]!);
  if (!_keepingAlive) {
    _keepingAlive = true;
    final ParentDataElement<KeepAliveParentDataMixin>? childElement = _getChildElement();
    if (childElement != null) {
      // If the child already exists, update it synchronously.
      _updateParentDataOfChild(childElement);
    } else {
      // If the child doesn't exist yet, we got called during the very first
      // build of this subtree. Wait until the end of the frame to update
      // the child when the child is guaranteed to be present.
      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
        if (!mounted) {
          return;
        }
        final ParentDataElement<KeepAliveParentDataMixin>? childElement = _getChildElement();
        assert(childElement != null);
        _updateParentDataOfChild(childElement!);
      });
    }
  }
  return false;
}

ParentDataElement<KeepAliveParentDataMixin>? _getChildElement() {
	///...
}
```

In the `KeepAlive` widget, we can see the `applyParentData` function about how the parent data will set the renderObject keepAlive status by the notification from the `AutomaticKeepAliveClientMixin`. If it is not keepAlive, the renderObject will redo the layout.

```dart
@override
void applyParentData(RenderObject renderObject) {
  assert(renderObject.parentData is KeepAliveParentDataMixin);
  final KeepAliveParentDataMixin parentData = renderObject.parentData! as KeepAliveParentDataMixin;
  if (parentData.keepAlive != keepAlive) {
    parentData.keepAlive = keepAlive;
    final AbstractNode? targetParent = renderObject.parent;
    if (targetParent is RenderObject && !keepAlive)
      targetParent.markNeedsLayout(); // No need to redo layout if it became true.
  }
}
```

In summary, the whole process will be like this:



![summary](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlsozwkrhlahbj16md67.png)