Flutter: Можно ли узнать, что вы сейчас вне сцены?

У меня есть несколько страниц в моем приложении, завернутые в Offstage виджеты. Каждая страница использует пакет провайдера для отображения на основе обновлений состояния (например, пользователь что-то делает, мы делаем сетевой вызов и отображаем результат).

Поскольку страницы завернуты в виджеты Offstage, методы build() (и последующие сетевые вызовы) вызываются, даже если это не текущая страница.

Есть ли способ внутри метода build() узнать, находится ли виджет в данный момент вне сцены (и если да, то пропустить любую дорогостоящую логику)?

Я предполагаю, что могу работать с глобальным состоянием и т. д., но мне было интересно, есть ли что-нибудь встроенное в отношении самого виджета Offstage, похожее на mounted

Нужно ли вообще использовать Offstage? может быть есть лучшее решение без него

Ivo 08.11.2022 15:50

Это нужно для того, чтобы сохранить состояние страницы. Я тоже смотрел на Visibility, но, видимо, он использует Offstage за кулисами, если вы хотите сохранить состояние.

divillysausages 08.11.2022 16:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
171
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Возможно, это неприменимо к вам, но вы можете решить эту проблему, просто не используя Offstage. Рассмотрим это приложение:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {

  MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool showFirst = true;

  void switchPage() {
    setState(() {
      showFirst = !showFirst;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
                child: Stack(children: [
                  Offstage(offstage: !showFirst,child: A("first", switchPage)),
                  Offstage(offstage: showFirst,child: A("second", switchPage)),
                ]))));
  }
}

class A extends StatelessWidget {
  final String t;
  final Function onTap;
  const A(this.t, this.onTap, {Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    print('$t is building');
    return TextButton(onPressed: ()=> onTap(), child: Text(t));
  }
}

По отпечаткам вы заметите, что обе страницы построены. Но если переписать вот так без Offstage, то строится только видимый:

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
                child: Stack(children: [
                  if (showFirst) A("first", switchPage),
                  if (!showFirst)  A("second", switchPage),
                ]))));
  }

Спасибо за предложение, но - поправьте меня, если я ошибаюсь - это не поддерживает состояние страницы? Таким образом, если на каждой странице есть дополнительная навигация, вы потеряете свою позицию и т. д.

divillysausages 08.11.2022 16:10

@divillysausages ах да, это, вероятно, не поддерживает государство, нет

Ivo 09.11.2022 07:57

Если вы хотите просто поддерживать состояние своих страниц, вы можете использовать https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html , вы можете проверить этот блог, например, использование, https: //medium.com/manabie/flutter-simple-cheatsheet-4370a68f98b3

Это интересно, хотя строка their build methods must call super.build (though the return value should be ignored) подразумевала бы, что build() все равно вызывается, просто она не используется?

divillysausages 12.11.2022 01:02

Я ценю ответ, кстати, хотя мой вопрос скорее "как я могу узнать, если я не на сцене", а не "как я могу сохранить состояние живым"

divillysausages 12.11.2022 01:03

Я сделал индивидуальный механизм для цели, которую вы хотите достичь:

Во-первых, я объявляю новый Map<String, bool> в отдельном файле, который будет содержать значение offStage bool с ключом каждого виджета класса.

Map<String, bool> offStageMap = {};

затем в реализации StatefulWidget, где находится виджет вне сцены:

 class ExampleWidget extends StatefulWidget {
 ExampleWidget({super.key}) {
    widgetMapKey = runtimeType.toString();
  }
late final String widgetMapKey;
  @override
  State<ExampleWidget> createState() => _ExampleWidgetState();
}

class _ExampleWidgetState extends State<ExampleWidget> {
  final bool defaultIsOffStaged = false;
  bool? localStateIsOffStages;

  @override
  void initState() {
    offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        bool previousIsOffStaged = offStageMap[widget.widgetMapKey]!;
        setState(() {
          localStateIsOffStages =
              offStageMap[widget.widgetMapKey] = !previousIsOffStaged;
        });
      },
      child: Offstage(
        offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
        child: Container(),
      ),
    );
  }
}   },
          child: Offstage(
            offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,
            child: Container(),
          ),
        );
      }
    }

позвольте мне объяснить, о чем идет речь.

сначала я объявил defaultIsOffStaged, где оно должно быть начальным значением offStage, когда на этой карте ничего не сохраняется.

когда этот виджет вставляется в дерево виджетов (вызывается initState()), widget.widgetMapKey виджета ExampleWidget будет сохранен на этой карте со значением по умолчанию, которое равно defaultIsOffStaged.

  offStageMap[widget.widgetMapKey] ??= defaultIsOffStaged;

в свойстве вне сцены виджета OffStage, в этой строке:

   offstage: localStateIsOffStages ?? offStageMap[widget.widgetMapKey]!,

значение nullable localStateIsOffStages будет нулевым в первый раз, поскольку оно еще не имеет значения, поэтому offStageMap[widget.widgetMapKey]!, равное defaultIsOffStaged, будет логическим значением вне сцены.

до сих пор то, что у нас есть, это карта, содержащая ключ, который принадлежит только ExampleWidget, который является его widget.widgetMapKey со значением offStage, верно?

теперь из всех мест вашего приложения вы можете получить значение offStage этого виджета с его widgetMapKey следующим образом:

print(offStageMap[ExampleWidget().widgetMapKey]); // true

теперь предположим, что вы хотите изменить свойство вне сцены этого виджета, в своем коде я использовал простой пример GestureDetector, поэтому, когда мы нажимаем в области Text("toggle offstage"), он переключается вне сцены, вот что происходит:

мы получили существующее значение в карте: bool предыдущаяIsOffStaged = offStageMap[widget.widgetMapKey]!;

затем назначьте противоположное этому ключу виджета на карте и логической переменной localStateIsOffStages, которая была обнуляемой, теперь она имеет значение.

и, как обычно, состояние обновляется, я завернул его в SetState(() {})

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

localStateIsOffStages я объявил просто удерживать локальное состояние, когда это происходит, пока состояние StatefulWidget обновляется.

после того, как StatefulWidget будет удален (когда вы выберете маршрут в качестве примера) и снова откроете этот маршрут, initState() будет выполнен, но поскольку теперь у нас есть запись на карте, она не равна нулю, поэтому внутри initState() ничего не произойдет.

localStateIsOffStages будет нулевым, поэтому свойство offStage виджета Offstage будет значением из карты, которое является предыдущим значением перед удалением виджета.

вот и все, из других мест вы можете проверить значение вне сцены этого конкретного виджета следующим образом:

 print(offStageMap[ExampleWidget().widgetMapKey])

вы можете сделать это для всех ваших страниц виджетов, так что у вас будет карта, содержащая все значения offStage.

Я сделал еще один шаг вперед и сделал те методы, которые, я думаю, помогут:

это вернет List со страницами, где значение истинно.

    List<String> offstagedPages() {
  List<String> isOffStagedPages = [];

  offStageMap.forEach((runtimeType, isOffStaged) {
    if (isOffStaged) {
      isOffStagedPages.add(runtimeType);
    }
  });
  return isOffStagedPages;
}

это вернет true, если страница не подготовлена, и false, если нет:

    bool isPageWidgetOffStaged(String runtimeType) {
  if (offStageMap.containsKey(runtimeType)) {
    return offStageMap[runtimeType]!;
  }
  return false;
}

Надеюсь, это немного поможет.

Если вы используете Navigator, вы можете просто расширить NavigatorObserver. Затем вы получите didpush и didpop, используйте состояние для управления жизненным циклом элемента, вы получите удовольствие от страницы onPause и onResume.

Ответ принят как подходящий

Вы можете попробовать найти родительский виджет OffStage и посмотреть, является ли свойство вне сцены истинным или ложным.

  @override
  Widget build(BuildContext context) {
    final offstageParent = context.findAncestorWidgetOfExactType<Offstage>();
    if (offstageParent != null && offstageParent.offstage == false) {
      // widget is currently offstage.
      print('offstaged child');
    } else {
      // widget is not offstage
      print('non-offstaged child');
    }
    return const Text('Example Widget');
  }

Другие вопросы по теме