Flutter Keyboard показать / закрыть вызвать перестройку виджета?

У меня есть экран, который содержит Form с StreamBuilder когда я загружаю исходные данные из StreamBuilder, TextFormField показать данные, как ожидалось. Когда я нажимаю TextFormField Клавиатура показывает эту причину перестроить виджет, затем снова, после редактирования, клавиатура выключается, эта причина снова перестраивает виджет, и когда виджет перестраивается StreamBuilder снова подписывается и значения текстового поля заменяются начальным значением.

Вот мой код

  Widget _formUI() {

    return StreamBuilder<ConcreteEstimationForm>(
        stream: _bloc.inputObservable(),
        builder: (context, AsyncSnapshot<ConcreteEstimationForm> snapshot) {
          if (snapshot.hasData) {
            ConcreteEstimationForm form = snapshot.data;
            _descriptionController.text = form.description;
            return Column(
              children: <Widget>[
                TextFormField(
                  decoration: const InputDecoration(labelText: "Description"),
                  keyboardType: TextInputType.text,
                  validator: _descriptionValidator,
                  controller: _descriptionController,
                  onSaved: (String value) {
                    _description = value;
                  },
                ),
                ],
            );
          } else {
            return new Center(
              child: new CircularProgressIndicator(),
            );
          }
        });
  }

Кто-нибудь сталкивался с этой проблемой? Как это решить?

Благодарю.

3 ответа

Клавиатура, вызывающая перестроения

Это имеет смысл и ожидается , что открытие программной клавиатуры вызовет перестроение. За кулисами обновляется вставками вида . Эти убедитесь, что ваш пользовательский интерфейс знает о клавиатуре, скрывающей его. Абстрактно, клавиатура, закрывающая экран, вызывает изменение окна и большую часть времени вашего пользовательского интерфейса, что требует изменений в пользовательском интерфейсе - перестроения.

Я могу с уверенностью предположить, что вы используетев вашем приложении Flutter. Как и многие другие виджеты фреймворка, виджеты зависят (см.InheritedWidget) на(который получает данные изWindowсодержащий ваше приложение) с помощью .
ВидетьMediaQueryDataЧтобы получить больше информации.


Все сводится к наличие зависимости от вставок представления. Это позволяет изменять размер при изменении этих вставок представления . По сути, когда клавиатура открыта, вставки обновляются, что позволяет каркасу уменьшаться внизу, удаляя закрытое пространство.

Короче говоря, скаффолд, адаптирующийся к скорректированным вставкам представления, требует перестройки пользовательского интерфейса скаффолда. И поскольку ваши виджеты обязательно являются потомками скаффолда (вероятнее всего, ), ваши виджеты также перестраиваются, когда это происходит.

Вы можете отключить изменение размера вставок представления, используяScaffold.resizeToAvoidBottomInset. Однако это не обязательно остановит перестроения, так как все еще может существовать зависимость от . Ниже я объясню, как вы должны действительно думать об этой проблеме.

Идемпотентные методы сборки

Вы всегда должны создавать свои виджеты Flutter таким образом, чтобы ваши методы были идемпотентными .
Парадигма заключается в том, что вызов сборки может произойти в любой момент времени , до 60 раз в секунду (или больше, если на более высокой частоте обновления).

Что я имею в виду под вызовами идемпотентной сборки , так это то, что когда ничего о конфигурации вашего виджета (в случаеs ) или ничего о вашем состоянии (в случаеs ), результирующее дерево виджетов должно быть строго таким же . Таким образом, вы не хотите обрабатывать какое- либо состояние - его единственная ответственность должна представлять текущую конфигурацию или состояние.


Открытие программной клавиатуры, вызывающее перестроение, — просто хороший пример того, почему это так. Другими примерами являются поворот устройства, изменение размера в Интернете, но на самом деле это может быть что угодно, поскольку ваше дерево виджетов начинает становиться сложным (подробнее об этом ниже).

переподписка на восстановление

Возвращаясь к исходному вопросу: в данном случае ваша проблема в том, что вы неправильно подходите к . Вы не должны кормить его потоком, который воссоздается при каждой сборке.

Строители потоков работают следующим образом: подписка на первоначальный поток и повторная подписка всякий раз, когда поток обновляется. Это означает, что когда свойство виджета отличается между двумя вызовов, построитель потоков отпишется от первого и подпишется на второй (новый) поток.

Вы можете увидеть это вреализация :

      if (oldWidget.stream != widget.stream) {
  if (_subscription != null) {
    _unsubscribe();
    _summary = widget.afterDisconnected(_summary);
  }
  _subscribe();
}

Очевидное решение здесь заключается в том, что вы захотите предоставить один и тот же поток между различными вызовами сборки, когда вы не хотите повторно подписываться. Это восходит к вызовам идемпотентной сборки !


А например, всегда будет возвращать один и тот же поток, что означает, что его можно безопасно использовать в твоей . По сути, все реализации контроллера, субъекта поведения и т. д. должны вести себя таким образом — до тех пор, пока вы не воссоздаете свой поток, позаботится об этом должным образом!

Таким образом, неисправная функция в вашем случае , который каждый раз создает новый поток, а не возвращает один и тот же.

Заметки

Обратите внимание, я сказал, что вызовы сборки могут происходить «в любой момент времени». На самом деле вы можете (технически) точно контролировать, когда каждая сборка будет происходить в вашем приложении. Однако обычное приложение будет настолько сложным, что вы не сможете его контролировать, поэтому вам понадобятся идемпотентные вызовы сборки.
Клавиатура, вызывающая перестроения, является хорошим примером для этого.

Если вы думаете об этом на высоком уровне, это именно то, что вам нужно - фреймворк и его виджет (или виджеты, которые вы создаете) заботятся о том, чтобы реагировать на внешние изменения и перестраиваться, когда это необходимо. Ваши листовые виджеты в дереве не должны заботиться о том, происходит ли перестроение - они должны быть хорошо помещены в любую среду, а фреймворк позаботится о реагировании на изменения в этой среде путем соответствующей перестройки.

Надеюсь, я смог прояснить это для вас :)

Я столкнулся с подобной проблемой в своем приложении. Что решило мою проблему, так это очистить мое «дерево виджетов», как предложил один из программистов на этом форуме.

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

      var datastream;

@override
  void initState() {
    dataStream = _bloc.inputObservable();
    super.initState();
  }

@override
Widget build(BuildContext context) {
  return StreamBuilder(
    stream: dataStream,
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        return TextFormField(
          // ...
        );
      }
      return const Center(
        child: CircularProgressIndicator(),
      );
    },
  );
}

Это можно решить, создав виджет с состоянием, например следующий

      class StatefulWrapper extends StatefulWidget {
  final Function onInit;
  final Widget child;
  const StatefulWrapper({@required this.onInit, @required this.child});
  @override
  _StatefulWrapperState createState() => _StatefulWrapperState();
}

class _StatefulWrapperState extends State<StatefulWrapper> {
  @override
  void initState() {
    if (widget.onInit.call != null) {
      widget.onInit();
    }

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return widget.child;
  }
}

и обертывание виджета без состояния с помощью обертки

      Widget body;

class WidgetStateless extends StatelessWidget {

  WidgetStateless();

  @override
  Widget build(BuildContext context) {
    return StatefulWrapper(
      onInit: () async {
      //Create the body widget in the onInit
      body = Container();
      },
      child : body
    )
  }
Другие вопросы по тегам