FFAppState

инфо
Про пример

Разбор построен на сгенерированном коде демонстрационного приложения EcommerceFlow. Сам код лежит в репозитории на GitHub.

FFAppState — единая точка хранения глобального состояния приложения. Это синглтон: за всё время работы приложения существует ровно один его экземпляр. Класс наследует ChangeNotifier, поэтому виджеты могут подписываться на изменения и перестраиваться.

Внутри — методы инициализации и обновления сохранённого состояния, а также переменные с парами геттеров и сеттеров.

Каркас класса из демонстрационного магазина:

class FFAppState extends ChangeNotifier {
  static FFAppState _instance = FFAppState._internal();

  factory FFAppState() {
    return _instance;
  }

  FFAppState._internal();

  static void reset() {
    _instance = FFAppState._internal();
  }

  void update(VoidCallback callback) {
    callback();
    notifyListeners();
  }

  // переменная состояния приложения с геттером и сеттером
  bool _enableDarkMode = false;

  bool get enableDarkMode => _enableDarkMode;

  set enableDarkMode(bool value) {
    _enableDarkMode = value;
  }
}

_enableDarkMode здесь — переменная, заведённая разработчиком; геттер и сеттер к ней создаются сами.

Перестроение при обновлении

При обновлении переменной состояния из редактора действий выбирается режим перестроения: Rebuild All Pages, Rebuild Current Page или No Rebuild. Вот что получается в коде.

Rebuild Current Page

Вызывается сеттер, сразу за ним setState(() {}); — перестраивается только текущая страница.

Пример: обновление enableDarkMode в триггере onInitialization страницы ProductListPage.

SchedulerBinding.instance.addPostFrameCallback((_) async {
  FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true);
  setState(() {});
});

Rebuild All Pages

Вызывается сеттер, затем метод update(), который внутри дёргает notifyListeners() — перестраиваются все виджеты, зависящие от этой переменной.

SchedulerBinding.instance.addPostFrameCallback((_) async {
  FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true);
  FFAppState().update(() {});
});
совет
Обновление из собственного кода

В собственных действиях обязательно вызывайте update(), иначе изменения не дойдут до других страниц:

FFAppState().update(() => FFAppState().enableDarkMode = !(FFAppState().enableDarkMode ?? true));

No Rebuild

Вызывается только сеттер: значение меняется, но ничего не перестраивается.

watch<FFAppState>

Когда через редактор действий добавляется Update App State, в метод build соответствующих страниц попадает строка:

@override
Widget build(BuildContext context) {
    context.watch<FFAppState>();
    ...

context.watch<FFAppState>() подписывает виджет на изменения FFAppState: при любом изменении виджет перестраивается и показывает актуальные данные.

Списки в состоянии приложения

Для переменной типа List FlutterFlow генерирует набор вспомогательных методов: геттер, сеттер и операции добавления, удаления и правки элементов. Разберём на примере LatLngList.

late LoggableList<LatLng> _LatLngList =
    LoggableList([LatLng(37.4071594, -122.0775312), LatLng(40.7358633, -73.9910835)]);

List<LatLng> get LatLngList => _LatLngList?..logger = () => debugLogAppState(this);

set LatLngList(List<LatLng> value) {
    if (value != null) {
        _LatLngList = LoggableList(value);
    }

    debugLogAppState(this);
}

void addToLatLngList(LatLng value) {
    LatLngList.add(value);
}

void removeFromLatLngList(LatLng value) {
    LatLngList.remove(value);
}

void removeAtIndexFromLatLngList(int index) {
    LatLngList.removeAt(index);
}

void updateLatLngListAtIndex(
    int index,
    LatLng Function(LatLng) updateFn,
) {
    LatLngList[index] = updateFn(_LatLngList[index]);
}

void insertAtIndexInLatLngList(int index, LatLng value) {
    LatLngList.insert(index, value);
}

Что здесь что:

  • список хранится в приватном поле _LatLngList типа LoggableList — обёртке, добавляющей журналирование;
  • геттер LatLngList отдаёт список остальному приложению;
  • сеттер заменяет список целиком и записывает изменение через debugLogAppState;
  • addToLatLngList добавляет элемент в конец;
  • removeFromLatLngList удаляет конкретный элемент;
  • removeAtIndexFromLatLngList удаляет элемент по позиции;
  • updateLatLngListAtIndex меняет элемент по позиции, применяя к нему функцию updateFn;
  • insertAtIndexInLatLngList вставляет элемент на нужную позицию, сдвигая остальные.
инфо
Как заводить такие переменные

Про работу с состоянием приложения в редакторе — на странице App State.

перевод официальной документации FlutterFlow

обновлено

ESC