Flutter DataStream не закрывается и не перестраивается должным образом. [Плохое состояние: поток уже прослушан.]

Хорошо, я знаю, что поток может быть создан для прослушивания потока более одного раза, используя систему широковещания, но это конкретно НЕ то, что я пытаюсь сделать здесь.

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

Фактически по какой-то причине код, который у меня есть, не удаляет поток полностью, и при повторном использовании он пытается повторно прослушать тот же поток, который уже был прослушан и закрыт, ни один из которых не работает (очевидно ). Вместо того, чтобы пытаться снова прослушать тот же поток, я пытаюсь создать НОВЫЙ поток для прослушивания. (Удаление и очистка всей информации из исходного первого потока).

Исходный пост продолжается ниже:

Я использую шаблон DataStream для потоковой передачи данных в различные части моей программы и / или из них, и я не совсем уверен, как это исправить. Я уверен, что это глупая ошибка newb, но я недостаточно использовал DataStreams, чтобы понять, почему это происходит.

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

Плохое состояние: поток уже прослушан.

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

Изменить: минимальный воспроизводимый пример для подражания

Файл 1 (main.dart)

import 'package:flutter/cupertino.dart';
import 'dart:async';
import './page2.dart';
import './stream.dart';

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

DataStream stream = DataStream();


class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Splash Test',
      theme: CupertinoThemeData(
        primaryColor: Color.fromARGB(255, 0, 0, 255),
      ),
      home: MyHomePage(title: 'Splash Test Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool textBool = false;
  int counter = 0;

  void changeTest(context) async {
    int counter = 0;
    Timer.periodic(Duration (seconds: 2), (Timer t) {
      counter++;
      stream.dataSink.add(true);
      if (counter >= 3) {
        t.cancel();
        stream.dispose();
        Navigator.pop(context);
      } 
    },);
    Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
  }



  @override
  Widget build(BuildContext context) {

    return CupertinoPageScaffold(
      child: Center(
        child: CupertinoButton(
          child: Text('To Splash'),
          onPressed: () => changeTest(context),
        ),
      ), 
    );
  }
}

Файл 2 (stream.dart)

import 'dart:async';

class DataStream {
  StreamController _streamController;

    StreamSink<bool> get dataSink =>
      _streamController.sink;

  Stream<bool> get dataStream =>
      _streamController.stream;

  DataStream() {
    _streamController = StreamController<bool>();
  }

  dispose() {
    _streamController?.close();
  }

}

Файл 3 (page2.dart)

import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';

import './main.dart';
import './stream.dart';


class Page2 extends StatefulWidget {

  DataStream stream;
  Page2({this.stream});

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

class PageState extends State<Page2> {


bool textChanger = false;
bool firstText = true;

Text myText() {
  if (textChanger) {
    Text text1 = new Text('Text One', 
      style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
    return text1;
  } else {
    Text text1 = new Text('Text Two', 
      style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
    return text1;
  }
}

void changeText() {
  if (!firstText) {
    if (textChanger) {
      print('Change One');
      setState(() { 
        textChanger = false;      
      });
    } else {
      print('Change Two');
      setState(() {  
        textChanger = true;    
      });
    }  
  } else {
    firstText = false;
  }
}


  @override 
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
        child: Center(
          child: myText()
        ) 
      ),);
  }

@override
  void initState() {
    super.initState();
    widget.stream.dataStream.listen((onData) {
      changeText();
    });
  }


}

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

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

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

Почему? И как мне это исправить?


person ArthurEKing    schedule 06.02.2020    source источник
comment
Каков объем stream во втором блоке кода? из имеющегося у вас фрагмента, похоже, он никогда не будет повторно создан?   -  person emerssso    schedule 12.02.2020
comment
Что вы имеете в виду под «циклом моей программы»? Это может быть связано с навигацией и тем, как SplashPage выталкивается / выталкивается в следующем «цикле». Вы можете попробовать добавить ключ к SplashPage.   -  person kuhnroyal    schedule 12.02.2020
comment
@emerssso Мне кажется, это может быть одна из моих проблем, но я не знаю, как ее решить. Я намерен создать экземпляр потока и прослушивать его, когда это необходимо, и удалить, как только он выполнит назначенную ему задачу. И если это необходимо, создается совершенно новый. Я не думаю, что делаю это, но я не обязательно знаю, как это сделать.   -  person ArthurEKing    schedule 12.02.2020
comment
@kuhnroyal Под «Циклом» этой программы я подразумеваю, что программа предназначена для выполнения одной задачи, и пользователь будет выполнять серию подсказок, чтобы завершить этот процесс. После того, как она завершит процесс один раз, мне нужно, чтобы программа полностью забыла все, что произошло изначально, и начала все заново. Если он пытается повторно использовать исходную информацию, это вызывает проблемы.   -  person ArthurEKing    schedule 12.02.2020
comment
Если поток нужен только в SplashPage, просто создайте его там, иначе у вас определенно будет несколько подписчиков.   -  person kuhnroyal    schedule 12.02.2020
comment
@kuhnroyal Я еще не делаю этого? На странице-заставке отображается поток DataStream; создание переменной прямо здесь. И да, это единственное место, где он когда-либо использовался.   -  person ArthurEKing    schedule 12.02.2020
comment
Вы передаете его как параметр конструктору.   -  person kuhnroyal    schedule 12.02.2020
comment
Я полагаю, что вы правы, и, честно говоря, не знаю. (Эта конкретная команда уже неделю вызывает у меня проблемы, и я просто не могу осмыслить ее). Это все еще не объясняет, как именно я должен это решить или что я должен делать. Я имею в виду, что то, что вы видите в коде, - это все, что я написал, что имеет какое-либо отношение к этому потоку. У него ровно одна работа, и все.   -  person ArthurEKing    schedule 12.02.2020
comment
Позвольте нам продолжить это обсуждение в чате.   -  person kuhnroyal    schedule 12.02.2020


Ответы (4)


Конструктор по умолчанию StreamController создает поток, который допускает только одного слушателя.

StreamController({void onListen(), void onPause(), void onResume(), dynamic onCancel(), bool sync: false })
A controller with a stream that supports only one single subscriber. [...]

Если вы хотите иметь прослушиватель мультиплексора, используйте broadcast именованный конструктор.

factory StreamController.broadcast({void onListen(), void onCancel(), bool sync: false })
A controller where stream can be listened to more than once. [...]

Если вы хотите, чтобы у вашего потока был только один подписчик, не забудьте отменить подписку в dispose методе виджета.

DataStream stream;
StreamSubscription subscription;

@override
void initState() {
  super.initState();
  subsription = widget.stream.listen((onData) {
    changeText();
  });
}

@override
void dispose() {
  subscription?.cancel();
  super.dispose();
}

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

person Karol Lisiewicz    schedule 06.02.2020
comment
Хорошо ... Спасибо за возможный ответ, но что мне делать, если я НЕ хочу, чтобы несколько слушателей? (Это было моим намерением. Может, я не смогу сделать так, как хочу, но посмотрим). Я имею в виду, что я хочу полностью обновить свою программу, как будто у меня никогда не было потока, поэтому у него не должно быть этой проблемы. - person ArthurEKing; 06.02.2020
comment
Хорошо, в том-то и дело. Ключевой факт в том, что всякий раз, когда вы начинаете слушать поток stream.listen((onData() {});, вы получаете объект с именем StreamSubscription. Вам необходимо сохранить ссылку на него и cancel подписку в dispose методе виджета. - person Karol Lisiewicz; 06.02.2020
comment
Я думаю, что это может быть частью моей проблемы, поскольку я использую метод dipose () (и я проверил, и он вызывается), но я не думаю, что создаю новый поток, и я не уверен, почему он не создает новый поток и не слушает его. - person ArthurEKing; 06.02.2020
comment
Замечательно, что вы удаляете поток, но отменяете ли вы подписку, когда виджет удален? - person Karol Lisiewicz; 06.02.2020
comment
Как мне это сделать? ржу не могу. (Как я уже сказал, новичок с этой командой) - person ArthurEKing; 06.02.2020
comment
Хорошо, как ни странно, мой скрипт вообще не включает подписки. Работает отлично, но подписок нет (см. Выше). Так что я не совсем уверен, как это выключить. - person ArthurEKing; 07.02.2020
comment
Давайте продолжим это обсуждение в чате. - person Karol Lisiewicz; 07.02.2020

Что бы я сделал, так это переместил поток на StatefulWidget и воссоздал его, нажав "В Splash"

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

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

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      title: 'Splash Test',
      theme: CupertinoThemeData(
        primaryColor: Color.fromARGB(255, 0, 0, 255),
      ),
      home: MyHomePage(title: 'Splash Test Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool textBool = false;
  int counter = 0;

  DataStream stream = DataStream();

  void changeTest(context) async {
    setState(() {
      stream = DataStream();
    });
    int counter = 0;
    Timer.periodic(Duration (seconds: 2), (Timer t) {
      counter++;
      stream.dataSink.add(true);
      if (counter >= 3) {
        t.cancel();
        stream.dispose();
        Navigator.pop(context);
      }
    },);
    Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      child: Center(
        child: CupertinoButton(
          child: Text('To Splash'),
          onPressed: () => changeTest(context),
        ),
      ),
    );
  }
}



class DataStream {
  StreamController _streamController;

  StreamSink<bool> get dataSink =>
      _streamController.sink;

  Stream<bool> get dataStream =>
      _streamController.stream;

  DataStream() {
    _streamController = StreamController<bool>();
  }

  dispose() {
    _streamController?.close();
  }

}



class Page2 extends StatefulWidget {

  DataStream stream;
  Page2({this.stream});

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

class PageState extends State<Page2> {


  bool textChanger = false;
  bool firstText = true;

  Text myText() {
    if (textChanger) {
      Text text1 = new Text('Text One',
          style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
      return text1;
    } else {
      Text text1 = new Text('Text Two',
          style: TextStyle(color: Color.fromARGB(255, 0, 0, 0)));
      return text1;
    }
  }

  void changeText() {
    if (!firstText) {
      if (textChanger) {
        print('Change One');
        setState(() {
          textChanger = false;
        });
      } else {
        print('Change Two');
        setState(() {
          textChanger = true;
        });
      }
    } else {
      firstText = false;
    }
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Container(
          child: Center(
              child: myText()
          )
      ),);
  }

  @override
  void initState() {
    super.initState();
    widget.stream.dataStream.listen((onData) {
      changeText();
    });
  }


}
person Filipkowicz    schedule 18.02.2020

Не имея возможности полностью понять вашу проблему, я просто хотел бы заявить, что у меня были головные боли практически каждый раз, когда я сам использовал "нормальные" потоки и RxDart был для меня как аспирин в мире потоков :) Не уверен, что это тот ответ, который вы ищете, но я подумал все равно опубликуйте - мало ли!

person kazume    schedule 18.02.2020

Ага! Мне удалось в этом разобраться. (Спасибо всем за попытку помочь, это действительно очень ценно). На самом деле это была действительно глупая ошибка новичков: как только я сделал шаг назад, я увидел это.

Вы заметите, что в файле main.dart у меня есть строка

DataStream stream = DataStream();

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

Я изменил его на:

DataStream stream;

А потом в моем файле main.dart, незадолго до нажатия навигатора, я добавляю строку

stream = new DataStream();
Navigator.push(context, CupertinoPageRoute(builder: (context) => Page2(stream: stream)));

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

person ArthurEKing    schedule 20.02.2020