caseycrogers / async_list_view

MIT License
1 stars 1 forks source link

Bug: Stream has already been listened to. #1

Closed ynnob closed 1 year ago

ynnob commented 1 year ago

Hey @caseycrogers !

Bug When using a BottomNavigationBar and switching the tab a second time to a screen that contains your AssyncListView the Widget will throw an exception because it tries to listen to the stream again.

Important: I use the package Stacked to handle Business Logic and View seperation in a MVVM Pattern. The ViewModels of the TabViews will not get disposed after the were initialized the first time.

════════ Exception caught by widgets library ═══════════════════════════════════
The following StateError was thrown building SchaedenListView(dependencies: [MediaQuery, _InheritedProviderScope<SchaedenViewModel?>]):
Bad state: Stream has already been listened to.

The relevant error-causing widget was
SchaedenListView      schaeden_view_content.dart:35
When the exception was thrown, this was the stack     
#6      _AsyncListViewState._initializeWithNewStream                async_list_view.dart:241
#7      _AsyncListViewState.initState                                            async_list_view.dart:136
#8      StatefulElement._firstBuild                                                framework.dart:5101
#9      ComponentElement.mount                                              framework.dart:4944
[...]

This only happens when switching the tab. Rebuilding the UI with setstate does not trigger this. I implemented it like you did in your example code:

Business Logic

  int asyncLoadedSchaeden = 0;
  int asyncTotalSchaeden = 0;
  late Stream<SchaedenListViewItemModel> asyncSchaedenStream;

  void initializeSchaedenStream(List<Schaeden> ls) {
    asyncLoadedSchaeden = 0;
    asyncTotalSchaeden = ls.length;
    asyncSchaedenStream = prepareSchaeden(ls).map((schaden) {
      // Increment here because we can't call `setState` from `itemBuilder`.
      // Use map instead of listen because listen would require a broadcast
      // stream and broadcast stream can't pause the stream's underlying source.
      // Pro tip: broadcast stream is not your friend. Avoid it at all costs.

      asyncLoadedSchaeden += 1;
      return schaden;
    });
  }

View

 return AsyncListView(
      physics: const BouncingScrollPhysics(),
      keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
      stream: viewModel.asyncSchaedenStream,
      addAutomaticKeepAlives: true,
      loadingWidget: const LoadingIndicator(),
      itemBuilder: (context, AsyncSnapshot<List<SchaedenListViewItemModel>> snapshot, index) {
        return SchaedenListViewItem(
          //key: ObjectKey(viewModel.lSchadenListItem[index].pk),
          row1Width: row1Width,
          item: snapshot.data![index],
          onTap: () => viewModel.onSchadenTapped(snapshot.data![index].pk),
        );
      },
    );
ynnob commented 1 year ago

Additional Observation: Triggering a rebuild after the Tab-Switch restores the AsyncListView for some reason and it works again.

ynnob commented 1 year ago

Seems to be a problem with the way i handle things. I will use a different approch in my listview.builder