Flutter setState () не всегда вызывает мой метод сборки - PullRequest
0 голосов
/ 17 мая 2018

Я пробую Flutter, но у меня возникают проблемы с постоянным обновлением интерфейса пользователя.Я хотел бы показать сообщение о состоянии, пока вызывается длительный асинхронный метод, но вызов setState (), который я выполняю непосредственно перед вызовом долговременного метода, не вызывает моего метода build ().

Я создал простой пример, который вычисляет число Фибоначчи для случайно выбранного числа между 25 и 30. В моем примере кода / приложения нажатие кнопки «calc» вызывает _calc ()._calc () выбирает случайное число, устанавливает сообщение о состоянии «Расчет Fib of $ num ...», привязанного к текстовому виджету (_status), и обновляет его с помощью setState ();затем вызывает процедуру async _fib () для вычисления числа;затем обновляет _status с результатом, используя setState ().Кроме того, метод build () выводит значение _status на консоль, которое можно использовать для просмотра при вызове build ().

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

Итак, мой вопрос: что я делаю не так?и как правильно это сделать?Использование псевдо сна, очевидно, не является правильным решением.

Другая, возможно, не слишком важная информация: Моя среда разработки - Android Studio 3.1.2 на компьютере с Win10.Использование Android SDK 27.0.3, с бета-версией Flutter 0.3.2.Мое целевое устройство - эмулятор для pixel2 под управлением Android 8.1.Кроме того, извините, если мое отсутствие «новых» ключевых слов вызывает недоумение, но из того, что я прочитал в примечаниях к выпуску Dart 2, сейчас это обычно не требуется.

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Debug Toy',
      home: MyWidget(),
    );
  }
}

class MyWidget extends StatefulWidget {
  @override
  MyWidgetState createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  String _status = "Initialized";
  final rand = Random();

  Future sleep1() async {
    return new Future.delayed(const Duration(milliseconds: 100),() => "1");
  }

  Future<Null> _resetState() async {
    setState(() { _status = "State Reset"; });
  }

  Future<Null> _calc() async {
    // calculate something that takes a while
    int num = 25 + rand.nextInt(5);
    setState(() { _status = "Calculating Fib of $num..."; });

    //await sleep1(); // without this, the status above does not appear
    int fn = await _fib(num);

    // update the display
    setState(() { _status = "Fib($num) = $fn"; });
  }

  Future<int> _fib(int n) async {
    if (n<=0) return 0;
    if ((n==1) || (n==2)) return 1;
    return await _fib(n-1) + await _fib(n-2);
  }

  @override
  Widget build(BuildContext context) {
    print("Build called with status: $_status");
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Debug Toy')),
      body: Column(
        children: <Widget>[
          Container(
            child: Row(children: <Widget>[
              RaisedButton( child: Text("Reset"), onPressed: _resetState, ),
              RaisedButton( child: Text("Calc"), onPressed: _calc, )
            ]),
          ),
          Text(_status),
        ],
      ),
    );
  }
}

1 Ответ

0 голосов
/ 18 мая 2018

Давайте начнем с одного экстремума и переписаем fib как fibSync

  int fibSync(int n) {
    if (n <= 0) return 0;
    if (n == 1 || n == 2) return 1;
    return fibSync(n - 1) + fibSync(n - 2);
  }

и назовем его

  Future<Null> _calc() async {
    // calculate something that takes a while
    int num = 25 + rand.nextInt(5);
    setState(() {
      _status = "Calculating Fib of $num...";
    });

    //await Future.delayed(Duration(milliseconds: 100));

    int fn = fibSync(num);

    // update the display
    setState(() {
      _status = "Fib($num) = $fn";
    });
  }

Первый setState просто помечает виджет как необходимыйбыть перестроенным и (без 'sleep') продолжаться прямо в расчете, никогда не давая каркасу возможности перестроить Widget, поэтому сообщение 'Calculation' не отображается.Второй метод setState вызывается после вычисления и снова (избыточно) помечает виджет как нуждающийся в восстановлении.

Итак, порядок выполнения:

  1. Установить статус на Расчет,пометить виджет как «грязный»
  2. выполнить синхронный расчет
  3. установить статус «Результат», пометить виджет как «грязный» (избыточно)
  4. Фреймворк наконец-то получил шанс на восстановление;метод сборки называется

Когда мы раскомментируем 'sleep', порядок выполнения меняется на

  1. Установить статус для Calculation, пометить Widget как грязный
  2. «Спящий режим», позволяющий каркасу вызывать сборку
  3. Выполнение синхронного вычисления
  4. Установить статус для Результат, пометить виджет как грязный (снова)
  5. Вызов каркаса построить

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

Давайте еще раз рассмотрим асинхронный расчет,Какова мотивация сделать это асинхронным?Так что интерфейс остается отзывчивым во время расчета?Как вы видели, это не дает желаемого эффекта.У вас все еще есть только один поток выполнения, и вы не допускаете никаких пробелов в выполнении для обратных вызовов и рендеринга.Спящий режим в течение 100 мсек не привязан к вычислениям, поэтому рисование и т. Д. Может происходить.

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

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

(Существуют гораздо менее дорогостоящие способы вычисления выдумок, но я думаю, что мы используем рекурсивную версию в качестве хорошейпример функции O (n ^ 2), верно?)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...