Поразительное поведение потока Flutter: вызывается setState, но не перестраивается дерево виджетов - PullRequest
0 голосов
/ 19 апреля 2020

Пытаясь увеличить asp использование потоков во Flutter, я последовал интересному примеру, найденному здесь https://github.com/tensor-programming/flutter_streams, который показывает, как получить данные из http-вызова и заполнить лениво (очень большой) список фото объектов / виджетов.

Все сводится к потоку и подписке на него, который вызывает setState для каждого события в потоке, добавляя элемент в список элементов [для отладка, я поставил оператор print("ADD!"), чтобы убедиться, что вызов работает как задумано. И это так].

Поток «заполняется» после вызова http, который предоставляет большой список необработанных демонстрационных данных. И чтобы увидеть, когда Flutter (пере) строит основную часть виджета PhotoList, я положил хороший print("BUILD!!!") как раз перед возвращением Scaffold.

Весь код программы флаттера таков:

import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';

import 'package:http/http.dart' as http;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Photo Streamer',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      home: PhotoList(),
    );
  }
}

class PhotoList extends StatefulWidget {
  @override
  PhotoListState createState() => PhotoListState();
}

class PhotoListState extends State<PhotoList> {
  StreamController<Photo> streamController;
  List<Photo> list = [];

  @override
  void initState() {
    super.initState();
    streamController = StreamController.broadcast();

    streamController.stream.listen((p) => {
          setState(() {
            list.add(p);
            print("ADD!");
          })
        });

    load(streamController);
  }

  load(StreamController<Photo> sc) async {
    String url = "https://jsonplaceholder.typicode.com/photos";
    var client = new http.Client();

    var req = new http.Request('get', Uri.parse(url));

    var streamedRes = await client.send(req);

    streamedRes.stream
        .transform(utf8.decoder)
        .transform(json.decoder)
        .expand((e) => e)
        .map((map) => Photo.fromJsonMap(map))
        .pipe(sc);
  }

  @override
  void dispose() {
    super.dispose();
    streamController?.close();
    streamController = null;
  }

  @override
  Widget build(BuildContext context) {
    print("BUILD!!!");
    return Scaffold(
      appBar: AppBar(
        title: Text("Photo Streams"),
      ),
      body: Center(
        child: ListView.builder(
          scrollDirection: Axis.horizontal,
          itemBuilder: (BuildContext context, int index) => _makeElement(index),
        ),
      ),
    );
  }

  Widget _makeElement(int index) {
    if (index >= list.length) {
      return null;
    }

    return Container(
        padding: EdgeInsets.all(5.0),
        child: Padding(
          padding: EdgeInsets.only(top: 200.0),
          child: Column(
            children: <Widget>[
              Image.network(list[index].url, width: 150.0, height: 150.0),
              Text(list[index].title),
            ],
          ),
        ));
  }
}

class Photo {
  final String title;
  final String url;

  Photo.fromJsonMap(Map map)
      : title = map['title'],
        url = map['url'];
}

Я ожидал, что каждый раз вызывается setState (добавление элемента в список отображаемых объектов) новая перестройка дерева виджетов будет называться , с отпечатками, похожими на: ДОБАВИТЬ! BUILD !!! ДОБАВИТЬ! BUILD !!! ... (et c.)

Но это не происходит. Что я вижу это: BUILD !!!! ДОБАВИТЬ! ДОБАВИТЬ! ... ... ДОБАВИТЬ! BUILD !!!!

И вот мои вопросы:

1) почему Flutter перестраивает виджеты всего два раза, даже если setState вызывается каждый раз, когда элемент добавляется в объекты списка?

2) если весь список заполняется одним выстрелом перед перерисовкой, какой смысл использовать поток?

и, что не менее важно, в общем случае:

3) если вызов http возвращает ВСЕ ДАННЫЕ сразу (как это происходит), то какой смысл использовать поток для заполнения (длинного) списка элементов, а не для заполнения списка напрямую - после асинхронной / ждать событие было запущено?

[Я старый алгоритмист, и эти вещи сводят меня с ума :)].

Я буду вечно благодарен тому, кто осветит их вопросы! Спасибо.

1 Ответ

1 голос
/ 19 апреля 2020

После предложения @pskink выясняется, что все действия, связанные с потоком, используемые в примере, все управляются в одном кадре . Это как-то объясняет, почему setState не запускает метод сборки элемента PhotoListState. Таким образом, на вопрос 1 теперь есть ответ.

...