Состояние гонки в виджете с состоянием с параметром - PullRequest
0 голосов
/ 17 октября 2019

У меня есть состояние гонки данных из потока BehaviorSubject, не заполняющего / не обновляющего состояние до возврата функции сборки.

content-detail.dart

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:domain_flutter/application-bloc.dart';
import 'package:domain_flutter/content.dart';
import 'package:domain_flutter/tag_chips.dart';
import 'package:cached_network_image/cached_network_image.dart';

class ContentDetail extends StatefulWidget {
  final String slug;

  ContentDetail({Key key, this.slug}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ContentDetailState();
}

class _ContentDetailState extends State<ContentDetail> {
  Content _content = Content();

  _getContent() {
    print(widget.slug);
      applicationBloc.contentOutput.map( (contents) =>
      contents.where((item) => item.slug == widget.slug).toList())
          .listen((data) => {
                if (this.mounted) {
                  setState(() => _content = data.first)
                }
              });
  }

  @override
  void initState() {
    super.initState();
    _getContent();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: _content != null ? Text(_content.title) : Text(''),
      ),
      body:
      SafeArea(
        child: Card(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Container(
                  child: ListTile(
                    contentPadding: EdgeInsets.all(0),
                title: Text(_content.title,style: TextStyle(fontWeight: FontWeight.bold)),
                subtitle: Text(
                  DateFormat('dd.MM.yyyy').format(_content.changed),
                  style: TextStyle(fontSize: 12),
                ),
              )),
              TagChips(_content.tags),
              CachedNetworkImage(
                  placeholder: (context, url) => CircularProgressIndicator(),
                  imageUrl: 'https://domain.tld/files/${_content.image}'),
            ],
          ),
        ),
      ),
    );
  }
}

Виджетрендерится, но до того, как он рендерится, я получаю ошибку. Если контент не инициализирован, я получаю другую ошибку.

То, что я имею в виду под инициализированным контентом, это

Content _content = Content();

инициализированный контент:

The following assertion was thrown building ContentDetail(dirty, state: _ContentDetailState#15727):
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 269 pos 10: 'data != null'


Either the assertion indicates an error in the framework itself, or we should provide substantially more information in this error message to help you determine and fix the underlying cause.
In either case, please report this assertion by filing a bug on GitHub:
  https://github.com/flutter/flutter/issues/new?template=BUG.md

User-created ancestor of the error-causing widget was: 
  MaterialApp file:///home/darko/AndroidStudioProjects/domain_flutter/lib/main.dart:24:12
When the exception was thrown, this was the stack: 
#2      new Text (package:flutter/src/widgets/text.dart:269:10)
#3      _ContentDetailState.build (package:domain_flutter/content_detail.dart:41:35)
#4      StatefulElement.build (package:flutter/src/widgets/framework.dart:4047:27)
#5      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3941:15)
#6      Element.rebuild (package:flutter/src/widgets/framework.dart:3738:5)
...

неинициализированный контент:

The following NoSuchMethodError was thrown building ContentDetail(dirty, state: _ContentDetailState#2be0b):
The getter 'title' was called on null.
Receiver: null
Tried calling: title

User-created ancestor of the error-causing widget was: 
  MaterialApp file:///home/darko/AndroidStudioProjects/domain_flutter/lib/main.dart:24:12
When the exception was thrown, this was the stack: 
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:51:5)
#1      _ContentDetailState.build (package:domain_flutter/content_detail.dart:53:38)
#2      StatefulElement.build (package:flutter/src/widgets/framework.dart:4047:27)
#3      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3941:15)
#4      Element.rebuild (package:flutter/src/widgets/framework.dart:3738:5)
...

Так что да, нет данных, когда он пытается визуализировать компонент ... или скорее Widget / s.

Я, вероятно, должен показать свой applicationBloc

import 'dart:async';
import 'package:domain_flutter/application-entities.dart';
import 'package:domain_flutter/content.dart';
import 'package:domain_flutter/tag.dart';
import 'package:rxdart/rxdart.dart';

class ApplicationBloc {
  final _applicationEntities = ApplicationEntities();

  Sink<List<Content>> get contentInput => _contentInputController.sink;
  Sink<List<Tag>> get tagInput => _tagInputController.sink;

  Stream<List<Content>> get contentOutput => _contentOutputSubject.stream;
  Stream<List<Tag>> get tagOutput => _tagOutputSubject.stream;

  final _contentInputController = StreamController<List<Content>>();
  final _tagInputController = StreamController<List<Tag>>();

  final _contentOutputSubject = BehaviorSubject<List<Content>>();
  final _tagOutputSubject = BehaviorSubject<List<Tag>>();

  ApplicationBloc() {
    _contentInputController.stream.listen(_handleContentInput);
    _tagInputController.stream.listen(_handleTagInput);
  }

  void dispose() {
    _contentInputController.close();
    _contentOutputSubject.close();
    _tagInputController.close();
    _tagOutputSubject.close();
  }

  void _handleContentInput(List<Content> contentList) {
    _applicationEntities.updateContent(contentList);
    _contentOutputSubject.add(contentList);
  }
  void _handleTagInput(List<Tag> tagList) {
    _applicationEntities.updateTags(tagList);
    _tagOutputSubject.add(tagList);
  }
}

final applicationBloc = ApplicationBloc();

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

Этот класс ContentDetail является почти копией другого компонента, который делает почти то же самое, он фильтрует по тегу slug и отображает список Content. Здесь требуется только 1 элемент из потока, Content с определенным свойством slug.

class Content {
// ...
  final String slug;
}

Как вы можете видеть, я передаю slug в конструкторе ContentDetail и мой_ContentDetailState получает доступ к свойству через widget.slug.

Функция _getContent() класса ContentListByTagSlug в сравнении:

  void _getContent() {
    applicationBloc.contentOutput.map(
            (contents) => contents.where(
                    (item) => item.tags.any((tag) => tag.slug == widget.tag.slug)
            ).toList()
    )
        .listen((data) => {
              if (this.mounted) {
                  setState(() => {content = data})
                }
            });
  }

Это работает, получая только 1 элемент отэто не так (см. 1-й фрагмент кода).

Вот как я определяю FlatButton, чтобы открыть страницу ContentDetail:

            FlatButton(
              child: Text('Read more'),
              onPressed: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) =>
                          ContentDetail(slug: content[index].slug))),
            ),

Это верно, слаг пройденкак показывает результат функции печати. ​​

Я не уверен, что делать, чтобы убедиться, что переменная _content заполняется перед выполнением функции сборки. В Angular создается распознаватель, который затем заполняет данные перед созданием / инициализацией компонента.

В Flutter?

Примечание. Я не использую пакет bloc, но помещаю потоки ввыделенный класс, затем используйте setState для обновления состояния в прослушивающих классах, поскольку пакет bloc кажется чрезмерным для этого сценария. 3 виджета (4, если считать ящик) 1, отображающий нефильтрованный список 2, отображающий список, отфильтрованный по тегу slug 3, отображающий один элемент, отфильтрованный по тегу slug, только у последнего есть ошибки.

update1: я даже удалил if (this.mounted) проверьте _getContent(), и я снова вызываю _getContent() в компоновщике, если _content равно нулю. Затем я изменил _content на Список и получаю _content.first.title, что приводит к

Bad state: No element

Но виджет по-прежнему отображается правильно. Таким образом, кажется, что есть 2 вызова виджета. Тот, который выбрасывает ошибки, который отбрасывается, и тот, который не, который сохраняется. Я не знаком с внутренностями, так что это мое лучшее предположение.

1 Ответ

0 голосов
/ 17 октября 2019

Ответ дан в этом ответе .

Я забираю у него:

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