Skip to content

Instantly share code, notes, and snippets.

@AlexV525
Last active October 29, 2023 17:51
Show Gist options
  • Save AlexV525/6067934cdcad26ab082010a926b58ad1 to your computer and use it in GitHub Desktop.
Save AlexV525/6067934cdcad26ab082010a926b58ad1 to your computer and use it in GitHub Desktop.
Lazy IndexedStack
@josebraz
Copy link

josebraz commented Jan 13, 2022

Nice Job!

I improved this approach with lazy build child and null safety and it works fine

import 'package:flutter/material.dart';

typedef LazyWidgetBuilder = Widget Function(BuildContext context);

class LazyIndexedStack extends StatefulWidget {
  const LazyIndexedStack({
    Key? key,
    required this.index,
    required this.children,
    this.alignment = AlignmentDirectional.topStart,
    this.textDirection,
    this.sizing = StackFit.loose,
  }) : super(key: key);

  final int index;
  final List<LazyWidgetBuilder> children;
  final AlignmentGeometry alignment;
  final TextDirection? textDirection;
  final StackFit sizing;

  @override
  _LazyIndexedStackState createState() => _LazyIndexedStackState();
}

class _LazyIndexedStackState extends State<LazyIndexedStack> {
  late Map<int, bool> _innerWidgetMap;
  late int index;

  @override
  void initState() {
    super.initState();
    index = widget.index;
    _innerWidgetMap = Map<int, bool>.fromEntries(
      List<MapEntry<int, bool>>.generate(
        widget.children.length,
        (int i) => MapEntry<int, bool>(i, i == index),
      ),
    );
  }

  @override
  void didUpdateWidget(LazyIndexedStack oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.index != widget.index) {
      _changeIndex(widget.index);
    }
  }

  void _activeCurrentIndex(int index) {
    if (_innerWidgetMap[index] != true) {
      _innerWidgetMap[index] = true;
    }
  }

  void _changeIndex(int value) {
    if (value == index) {
      return;
    }
    setState(() {
      index = value;
    });
  }

  bool _hasInit(int index) {
    final bool? result = _innerWidgetMap[index];
    if (result == null) {
      return false;
    }
    return result == true;
  }

  List<Widget> _buildChildren(BuildContext context) {
    final List<Widget> list = <Widget>[];
    for (int i = 0; i < widget.children.length; i++) {
      if (_hasInit(i)) {
        list.add(widget.children[i].call(context));
      } else {
        list.add(const SizedBox.shrink());
      }
    }
    return list;
  }

  @override
  Widget build(BuildContext context) {
    _activeCurrentIndex(index);
    return IndexedStack(
      index: index,
      children: _buildChildren(context),
      alignment: widget.alignment,
      sizing: widget.sizing,
      textDirection: widget.textDirection,
    );
  }
}

@AlexV525
Copy link
Author

Oh...I do have a null-safe version. I'll update it then.

@esDotDev
Copy link

esDotDev commented Apr 8, 2022

Thanks for the code! Here is an alternate version, that accounts for the child list size changing. It also doesn't accept a null index at all, which cleans up the code a little:

import 'package:flutter/material.dart';

/// A lazy-loading [IndexedStack] that loads [children] accordingly.
class LazyIndexedStack extends StatefulWidget {
  const LazyIndexedStack({
    Key? key,
    this.alignment = AlignmentDirectional.topStart,
    this.textDirection,
    this.sizing = StackFit.loose,
    this.index = 0,
    this.children = const [],
  }) : super(key: key);

  final AlignmentGeometry alignment;
  final TextDirection? textDirection;
  final StackFit sizing;
  final int index;
  final List<Widget> children;

  @override
  _LazyIndexedStackState createState() => _LazyIndexedStackState();
}

class _LazyIndexedStackState extends State<LazyIndexedStack> {
  late List<bool> _activated = _initializeActivatedList();

  List<bool> _initializeActivatedList() => List<bool>.generate(widget.children.length, (int i) => i == widget.index);

  @override
  void didUpdateWidget(covariant LazyIndexedStack oldWidget) {
    if (oldWidget.children.length != widget.children.length) {
      _activated = _initializeActivatedList();
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    // Mark current index as active
    _activated[widget.index] = true;
    final children = List.generate(_activated.length, (i) {
      return _activated[i] ? widget.children[i] : const SizedBox.shrink();
    });
    return IndexedStack(
      alignment: widget.alignment,
      sizing: widget.sizing,
      textDirection: widget.textDirection,
      index: widget.index,
      children: children,
    );
  }
}

Other tweaks:

  • moved the buildChildren method inside of build, as its only 3 lines when using list.generate
  • Removed the check for _activatedList[widget.index] , looking it up is likely slower than just storing it

@AlexV525
Copy link
Author

Thanks for the code! Here is an alternate version, that accounts for the child list size changing. It also doesn't accept a null index at all, which cleans up the code a little

Hi @esDotDev. Thanks for the feedback!
I've setup a workshop with the implementation at https://github.com/AlexV525/dartpad_workshops/tree/main/implement_lazy_indexed_stack, and I guess I'll redirect the gist to the workshop recently. Can you review it and see if your updates are still working with the workshop version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment