Как стать автором
Обновить

Flutter ConstWidget

Время на прочтение4 мин
Количество просмотров3K

Проблематика

Обновление данных в интерфейсе всегда задействует немало ресурсов а его реализация может быть выполнена множеством неоптимальных способов. Повышение производительности, не только радует пользователя, но и расширяет круг целевой аудитории с более старыми устройствами. State management Как? Когда? Почему? Каким способом? Лучше всего изменять состояние виджета/древа виджетов? Сейчас можно увидеть большое кол-во различных библиотек и подходов для решения данной задачи. Вопрос обновления данных интерфейса настолько большой, что библиотеки, которые помогают с управлением состояний становятся Архитектурными подходами, паттернами, а статей про то какой подход лучше еще больше. Данное решение подойдет к любому проекту, ему не нужна библиотека и вовсе не обязательно использовать данный виджет.

Как избежать ненужных обновлений?

После долгих поисков, мне под руку попала чудесная библиотека - should_rebuild, которая является ответами на мои вопросы. Она позволяет контролировать "когда нужно" обновить состояние виджета, удобным для меня способом.

Основной виджет этой библиотеки выглядит так:

import 'package:flutter/material.dart';

typedef ShouldRebuildFunction<T> = bool Function(T oldWidget, T newWidget);

class ShouldRebuild<T extends Widget> extends StatefulWidget {
  final T child;
  final ShouldRebuildFunction<T>? shouldRebuild;
  ShouldRebuild({required this.child, this.shouldRebuild});
  @override
  _ShouldRebuildState createState() => _ShouldRebuildState<T>();
}

class _ShouldRebuildState<T extends Widget> extends State<ShouldRebuild> {
  @override
  ShouldRebuild<T> get widget => super.widget as ShouldRebuild<T>;
  T? oldWidget;
  @override
  Widget build(BuildContext context) {
    final T newWidget = widget.child;
    if (this.oldWidget == null ||
        (widget.shouldRebuild == null
            ? true
            : widget.shouldRebuild!(oldWidget!, newWidget))) {
      this.oldWidget = newWidget;
    }
    return oldWidget as T;
  }
}

Изучив его, стало ясно, что таким способом мы можем заблокировать ненужные обновления.*

Ремарка

Да, при использовании "идеального" обновления состояний, в котором все ваши компоненты обернуты в билдеры (или инкапсулированы любым другим способом) и обновляются только те элементы, чьи параметры были изменены в логическом блоке/контролере/классе, вы не должны столкнуться с таким кейсом. Но на практике часто бывает так, что нужно обновить всё состояние виджета, за исключением маленькой детали (например текст, картинка или что иное).

Достаточно простым решением было просто сделать компонент который никогда не обновляется. А выглядит это так:

import 'package:const_widget/components/should_rebuild_widget.dart';
import 'package:flutter/material.dart';

class ConstWidget extends StatelessWidget {
  final Widget child;
  ConstWidget({required this.child});

  @override
  Widget build(BuildContext context) {
    return ShouldRebuild(
      child: child,
      shouldRebuild: (n, o) {
        return false;
      },
    );
  }
}

Тестирование

Для наглядного примера решил взять стартовый Flutter проект. И добавить виджет, цвет которого меняется при вызова метода build, он выглядит так:

import 'dart:math';
import 'package:flutter/material.dart';

class RndColorContainer extends StatefulWidget {
  final double h;
  final double w;

  RndColorContainer({
    this.h = 100,
    this.w = 100,
  });

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

class _RndColorContainerState extends State<RndColorContainer> {
  Color genRandomColor() {
    return Color.fromRGBO(Random().nextInt(255) + 1, Random().nextInt(255) + 1,
        Random().nextInt(255) + 1, 1);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: genRandomColor(),
      ),
      height: widget.h,
      width: widget.w,
    );
  }
}

Ну а дальше просто вставляем его в StatefulWidget (в нашем случае MyHomePage), и при вызове функции setState((){}), цвет нашего контейнера изменяется.

...
   children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            RndColorContainer(),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
...

Но, после того как мы обернем его в написанный нами компонент ConstWidget, состояние тестового элемента станет "почти" статичным. commit*.

...
   children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            ConstWidget(
              child: RndColorContainer(),
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
...

Результаты стресс тестирования

Допустим у нас 1000 таких объектов, вот, что покажет flutter Dev tool с использованием данного виджета, и без его использования.

            Wrap(
              children: [
                ...List.generate(
                  1000,
                  (index) => RndColorContainer(
                    h: 10,
                    w: 10,
                  ),
                ),
              ],
            ),
Без ConstWidget (38 FPS)
Без ConstWidget (38 FPS)

Вот результат, если обернуть Wrap в ConstWidget

ConstWidget(
              child: Wrap(
                children: [
                  ...List.generate(
                    1000,
                    (index) => RndColorContainer(
                      h: 10,
                      w: 10,
                    ),
                  ),
                ],
              ),
            ),
После применения, стало 57 FPS.
После применения, стало 57 FPS.

Дополнительно

Source code
Основано на библиотеке should_rebuild, документация.

P.S.

Надеюсь эта статья была полезной, ранее мне не доводилось встречать такой метод стейт менеджмента во Flutter. После увиденного продолжу изучать этот вопрос. Достижения большей гибкости, удобства и уровня оптимизации в этом вопросе поможет повысить качество написанных мною продуктов. Обязательно напишу продолжения, после того, как продвинусь дальше.

Теги:
Хабы:
-1
Комментарии1

Публикации

Истории

Работа

iOS разработчик
23 вакансии
Swift разработчик
32 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн