Передавать StreamBuilder между вкладками BottomNavigationBar = ›Плохое состояние: поток уже прослушан

Я пытаюсь передать поток с вкладки на другую вкладку, но когда я возвращаюсь в home.dart, могу ли я закрыть / уничтожить поток при изменении вкладки? Когда приложение запускается, данные загружаются правильно, и все в порядке. Проблема появляется только тогда, когда я меняю вкладку. Данные хранятся в базе данных firestore.

Я получаю такую ​​ошибку:

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

Вот мой Home.dart

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);
  Home createState() => Home();
}

class Home extends State<HomePage> {
  int _currentIndex;
  var _tabs = [];

  List<Company> currentCompaniesList = List();
  StreamController<List<Company>> _streamController;
  Stream<List<Company>> companiesStream;

  _getData() async {
    _companyService
        .getCompanies()
        .then((value) => _streamController.sink.add(value));
  }

  @override
  void initState() {
    super.initState();
    _currentIndex = 0;

    _streamController = StreamController<List<Company>>();

    _getData();
    companiesStream = _streamController.stream;
  }
}


@override
  Widget build(BuildContext context) {
    _tabs = [
      CompanyTab(stream: companiesStream),
      MapTab(),
      Center(child: Text('Profile')),
      Center(child: Text('Settings')),
    ];

    return Scaffold(
    ...
        actions: ...,
      body: _tabs[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        backgroundColor: BACKGROUND_COLOR,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          )
        ],
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
      ),
    );
  }
}

Вот мой CompanyTab.dart

class CompanyTab extends StatefulWidget {
  Stream stream;

  CompanyTab({Key key, this.stream}) : super(key: key);

  @override
  _CompanyTabState createState() => _CompanyTabState(stream);
}

class _CompanyTabState extends State<CompanyTab> {
  Stream stream;

  _CompanyTabState(this.stream);

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

  StreamBuilder companyList() {
    return StreamBuilder<List<Company>>(
        initialData: [],
        stream: stream,
        builder: (BuildContext context, AsyncSnapshot<List<Company>> snapshot) {
          if (snapshot.hasError) {
            return Text("Something went wrong");
          }

          if (snapshot.connectionState == ConnectionState.waiting ||
              snapshot.connectionState == ConnectionState.none ||
              snapshot.data == null) {
            return LoadingWidget();
          } else {
            return ListView.builder(
                padding: const EdgeInsets.all(10),
                itemCount: snapshot.data.length,
                itemBuilder: (BuildContext context, int index) {
                  Company company = snapshot.data.elementAt(index);
                  return Padding(
                    padding: const EdgeInsets.symmetric(
                        vertical: 1.0, horizontal: 4.0),
                    child: Card(
                      child: ListTile(
                        onTap: () {},
                        title: Text(company.name),
                        ...
                        ),
                      ),
                    ),
                  );
                });
          }
        });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: companyList(),
      ),
    );
  }
}

person Dome L.    schedule 20.10.2020    source источник


Ответы (1)


Речь идет о жизненном цикле виджетов. Могу предложить вам два варианта.

1. Переместите _streamController и метод _getData () в _CompanyTabState.

По умолчанию BottomNavigationBar уничтожает вкладку, когда вы уходите от одной, и запускает ее снова, когда вы возвращаетесь к ней. Если это желаемое поведение, вам нужно переместить _streamController и _getData() метод в _CompanyTabState. Не забудьте вызвать _streamController.close() внутри dispose() метода _CompanyTabState, это важно. _companyService можно вводить в _CompanyTabState. Это вопрос времени жизни. Должно работать так:

...
class _CompanyTabState extends State<CompanyTab> {
  final _streamController = StreamController<List<Company>>();
  final CompanyService _companyService;

  _CompanyTabState(this._companyService);

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

  StreamBuilder companyList() {
    return StreamBuilder<List<Company>>(
        initialData: [],
        stream: _streamController.stream,
        builder: (BuildContext context, AsyncSnapshot<List<Company>> snapshot) {
          if (snapshot.hasError) {
            return Text("Something went wrong");
          }

          if (snapshot.connectionState == ConnectionState.waiting ||
              snapshot.connectionState == ConnectionState.none ||
              snapshot.data == null) {
            return LoadingWidget();
          } else {
            return ListView.builder(
                padding: const EdgeInsets.all(10),
                itemCount: snapshot.data.length,
                itemBuilder: (BuildContext context, int index) {
                  Company company = snapshot.data.elementAt(index);
                  return Padding(
                    padding: const EdgeInsets.symmetric(
                        vertical: 1.0, horizontal: 4.0),
                    child: Card(
                      child: ListTile(
                        onTap: () {},
                        title: Text(company.name),
                        ...
                        ),
                      ),
                    ),
                  );
                });
          }
        });
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: companyList(),
      ),
    );
  }

  @override
  void dispose() {
    super.dispose();
    _streamController.close();
  }

  void _getData() {
    _companyService
        .getCompanies()
        .then((value) => _streamController.sink.add(value));
  }
}

2. Используйте IndexedStack.

Вы можете сохранить состояние вкладки и данные виджета (например, смещение прокрутки, введенный текст и т. Д.), Когда уходите с вкладки. Это поведение, подобное iOS UITabBarController. Для этого используйте IndexedStack:

...
    return Scaffold(
    ...
        actions: ...,
      body: IndexedStack(
        children: _tabs,
        index: _currentIndex,
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        backgroundColor: BACKGROUND_COLOR,
        type: BottomNavigationBarType.fixed,
        items: [
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          ),
          BottomNavigationBarItem(
            ...
          )
        ],
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
      ),
    );

Какой вариант использовать, решать вам, вы можете использовать оба, если хотите. Но я настоятельно рекомендую переместить _streamController в _CompanyTabState, поскольку их жизненные циклы должны быть такими же.

person Mol0ko    schedule 20.10.2020