I am loading data from Firebase and displaying it, this works perfectly fine. Now I want to let the user filter the results on the client-side. This is what doesn’t work.
I am using a StreamController
and use the add
method to send new user events. Somehow this works fine when getting data from Firebase, but not when I try to switch to the filtered results manually.
Context
Here is the normal code for the initial loading from Firebase:
class UserViewModel {
final UserRepository _userRepo;
final StreamController<List<AppUser>> _allUsersStreamcontroller =
StreamController<List<AppUser>>.broadcast();
List<AppUser> allUsersList = List.empty(growable: true);
// ... other code ...
Stream<List<AppUser>> getUserStream() {
return _allUsersStreamcontroller.stream;
}
Future<bool> loadAllUsersInRadius() async {
try {
await _userRepo.getCurrentUserLocation();
} catch (e) {
return Future.error(e);
}
// Basically load users & auto-filter some results
if (_userRepo.currentLocation != null) {
final subcription = _userRepo
.getAllUsersInRadius(radius: searchRadius)
.listen((documentList) {
final filteredUserList =
_applyAutomaticFilters(documentList, userRelationIDs);
allUsersList = filteredUserList
.map((docSnapshot) => _createUserFromSnapshot(docSnapshot))
.toList();
_allUsersStreamcontroller.add(allUsersList); // <- Important part, adding users to stream (this works)
});
subscriptions.add(subcription);
}
return true;
}
// ... Other code ...
And StreamBuilder
in the UI:
StreamBuilder<List<AppUser>>(
// Added random key because otherwise Widget didn't update reliably (suggestion from another SO question)
key: Key(Random().nextInt(10).toString()),
stream: userViewModel.getUserStream(), // <- getting stream data
builder: (context, snapshot) {
if (snapshot.hasData) {
if (_swiperEnded || (snapshot.data?.isEmpty ?? false)) {
// ... display some hint for user about empty list ...
} else {
// ... display main UI content ... <- Works fine normally
}
} else if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); // <- This is where it gets stuck after I apply manual filters
} else if (snapshot.hasError || _errorMessage != null) {
// Display error
} else {
// Unimportant
}
})
Now here is the part where it doesn’t work. I take the user list and filter the contents based on the filter values. Then I try to add this new list to the same StreamController
.
void applyManualFilters() {
Iterable<AppUser> filteredUserList = List.from(allUsersList);
if (isTimeZoneFilterActive)
filteredUserList = _filterByTimeZoneOffset(filteredUserList);
if (isAvailabilityFilterActive)
filteredUserList = _filterByAvailability(filteredUserList);
if (isAgeFilterActive)
filteredUserList = _filterByAgeRange(filteredUserList);
_allUsersStreamcontroller.add(filteredUserList.toList()); // <- setting filtered data here, does NOT work
}
My Problem
For some reason when I add the filteredUserList
to the stream, it gets stuck on ConnectionState.waiting
and the emulator starts eating resources like crazy (laptop starts getting very loud). It seems to get stuck in an infinite loop?
What I’ve tried: I tried using two different streams instead and then switching between them in the StreamBuilder
based on a bool flag, but that ended the same.
I saw many other questions related to switching streams or similar ideas (1, 2, 3) but all the answers there sit at 0 points or don’t help at all.
I’ve seen one suggestion in this SO question to use BehaviorSubject
from RxDart which seems like it’d work, but I’d really prefer not to import a whole state management package just for this one instance.