I am developing a search screen in my flutter app that does searching and filtering by date using algolia - the current UI is just one text box, two buttons that open date pickers for start and end date, and a results list. My issue is that I want the results list to update any time any of the 3 parameters (query, start, or end) are updated but currently the results are not updating when startDate or endDate are changed. But, if I change the start or end values and then update the query then the results will update and the filter will be applied.
All 3 parameters call the same code upon updating:
ref.read(comboSearchQueryNotifierProvider.notifier).setQuery(_controller.text, startDate: _startDate.value, endDate: _endDate.value,);
It seems to me that since changing the query string and the date effectively calls the same code then the behavior should be the same either way. Any guidance on why this is not the case would be greatly appreciated.
Code for my riverpod repo and query notifier are below - my UI widgets (search bar + filters and results list) both call the aforementioned setQuery
method on the onClick event of the buttons and the onChange of the search field and both call final comboListValue = ref.watch(comboSearchResultsProvider);
in their build methods. Happy to provide the full code if it would be helpful.
Riverpod repo:
part 'combo_search_repository.g.dart';
class ComboSearchQuery {
final String query;
final DateTime? startDate;
final DateTime? endDate;
const ComboSearchQuery({
required this.query,
this.startDate,
this.endDate,
});
ComboSearchQuery copyWith({
String? query,
DateTime? startDate,
DateTime? endDate,
}) {
return ComboSearchQuery(
query: query ?? this.query,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ComboSearchQuery &&
other.query == query &&
other.startDate == startDate &&
other.endDate == endDate;
}
@override
int get hashCode => Object.hash(query, startDate, endDate);
}
class ComboSearchRepository {
ComboSearchRepository()
: _multiSearcher = MultiSearcher(
applicationID: Env.algoliaAppId,
apiKey: Env.algoliaSearchKey,
) {
_eventsSearcher = _multiSearcher.addHitsSearcher(
initialState: const SearchState(
indexName: 'tm_events_index',
),
);
_usersSearcher = _multiSearcher.addHitsSearcher(
initialState: const SearchState(
indexName: 'users',
),
);
}
final MultiSearcher _multiSearcher;
late final HitsSearcher _eventsSearcher;
late final HitsSearcher _usersSearcher;
Future<List<dynamic>> search(ComboSearchQuery query) async {
print('Searching in combo repo for: query: ${query.query} startDate: ${query.startDate} endDate: ${query.endDate}');
if (query.query.isEmpty) {
print('Search text is empty, returning []');
return [];
}
List<String> filters = [];
if (query.startDate != null) {
filters.add('date>=${query.startDate!.millisecondsSinceEpoch}');
}
if (query.endDate != null) {
filters.add('date<=${query.endDate!.millisecondsSinceEpoch}');
}
_eventsSearcher.applyState((state) => state.copyWith(
query: query.query,
numericFilters: filters,
));
_usersSearcher.applyState((state) => state.copyWith(
query: query.query
));
final eventResponse = await _eventsSearcher.responses.first;
final userResponse = await _usersSearcher.responses.first;
// process stream results (removed for brevity)
return results;
}
}
@Riverpod(keepAlive: true)
ComboSearchRepository comboSearchRepository(ComboSearchRepositoryRef ref) {
return ComboSearchRepository();
}
@riverpod
Future<List<dynamic>> comboListSearch(ComboListSearchRef ref, ComboSearchQuery query) async {
if (query.query.isNotEmpty || query.startDate != null || query.endDate != null) {
final searchRepository = ref.watch(comboSearchRepositoryProvider);
return searchRepository.search(query);
} else {
return [];
}
}
@riverpod
Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) {
final searchQuery = ref.watch(comboSearchQueryNotifierProvider);
return ref.watch(comboListSearchProvider(searchQuery).future);
}
Query Notifier
part 'combo_search_query_notifier.g.dart';
@riverpod
class ComboSearchQueryNotifier extends _$ComboSearchQueryNotifier {
final _searchQueryController = StreamController<ComboSearchQuery>();
late final StreamSubscription<ComboSearchQuery> _subscription;
@override
ComboSearchQuery build() {
_subscription = _searchQueryController.stream
.debounceTime(const Duration(milliseconds: 200))
.listen(_updateState);
ref.onDispose(() {
_searchQueryController.close();
_subscription.cancel();
});
return ComboSearchQuery(query: '', startDate: null, endDate: null);
}
void _updateState(ComboSearchQuery query) {
state = ComboSearchQuery(
query: query.query,
startDate: query.startDate,
endDate: query.endDate,
);
}
void setQuery(String query, {DateTime? startDate, DateTime? endDate}) {
final newQuery = ComboSearchQuery(
query: query,
startDate: startDate,
endDate: endDate,
);
state = newQuery; // Force Riverpod to detect a state change
}
}
@riverpod
Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) {
final searchQuery = ref.watch(comboSearchQueryNotifierProvider);
debugPrint('Updated search query in comboSearchResults: $searchQuery');
return ref.watch(comboListSearchProvider(searchQuery).future);
}
I am developing a search screen in my flutter app that does searching and filtering by date using algolia - the current UI is just one text box, two buttons that open date pickers for start and end date, and a results list. My issue is that I want the results list to update any time any of the 3 parameters (query, start, or end) are updated but currently the results are not updating when startDate or endDate are changed. But, if I change the start or end values and then update the query then the results will update and the filter will be applied.
All 3 parameters call the same code upon updating:
ref.read(comboSearchQueryNotifierProvider.notifier).setQuery(_controller.text, startDate: _startDate.value, endDate: _endDate.value,);
It seems to me that since changing the query string and the date effectively calls the same code then the behavior should be the same either way. Any guidance on why this is not the case would be greatly appreciated.
Code for my riverpod repo and query notifier are below - my UI widgets (search bar + filters and results list) both call the aforementioned setQuery
method on the onClick event of the buttons and the onChange of the search field and both call final comboListValue = ref.watch(comboSearchResultsProvider);
in their build methods. Happy to provide the full code if it would be helpful.
Riverpod repo:
part 'combo_search_repository.g.dart';
class ComboSearchQuery {
final String query;
final DateTime? startDate;
final DateTime? endDate;
const ComboSearchQuery({
required this.query,
this.startDate,
this.endDate,
});
ComboSearchQuery copyWith({
String? query,
DateTime? startDate,
DateTime? endDate,
}) {
return ComboSearchQuery(
query: query ?? this.query,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ComboSearchQuery &&
other.query == query &&
other.startDate == startDate &&
other.endDate == endDate;
}
@override
int get hashCode => Object.hash(query, startDate, endDate);
}
class ComboSearchRepository {
ComboSearchRepository()
: _multiSearcher = MultiSearcher(
applicationID: Env.algoliaAppId,
apiKey: Env.algoliaSearchKey,
) {
_eventsSearcher = _multiSearcher.addHitsSearcher(
initialState: const SearchState(
indexName: 'tm_events_index',
),
);
_usersSearcher = _multiSearcher.addHitsSearcher(
initialState: const SearchState(
indexName: 'users',
),
);
}
final MultiSearcher _multiSearcher;
late final HitsSearcher _eventsSearcher;
late final HitsSearcher _usersSearcher;
Future<List<dynamic>> search(ComboSearchQuery query) async {
print('Searching in combo repo for: query: ${query.query} startDate: ${query.startDate} endDate: ${query.endDate}');
if (query.query.isEmpty) {
print('Search text is empty, returning []');
return [];
}
List<String> filters = [];
if (query.startDate != null) {
filters.add('date>=${query.startDate!.millisecondsSinceEpoch}');
}
if (query.endDate != null) {
filters.add('date<=${query.endDate!.millisecondsSinceEpoch}');
}
_eventsSearcher.applyState((state) => state.copyWith(
query: query.query,
numericFilters: filters,
));
_usersSearcher.applyState((state) => state.copyWith(
query: query.query
));
final eventResponse = await _eventsSearcher.responses.first;
final userResponse = await _usersSearcher.responses.first;
// process stream results (removed for brevity)
return results;
}
}
@Riverpod(keepAlive: true)
ComboSearchRepository comboSearchRepository(ComboSearchRepositoryRef ref) {
return ComboSearchRepository();
}
@riverpod
Future<List<dynamic>> comboListSearch(ComboListSearchRef ref, ComboSearchQuery query) async {
if (query.query.isNotEmpty || query.startDate != null || query.endDate != null) {
final searchRepository = ref.watch(comboSearchRepositoryProvider);
return searchRepository.search(query);
} else {
return [];
}
}
@riverpod
Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) {
final searchQuery = ref.watch(comboSearchQueryNotifierProvider);
return ref.watch(comboListSearchProvider(searchQuery).future);
}
Query Notifier
part 'combo_search_query_notifier.g.dart';
@riverpod
class ComboSearchQueryNotifier extends _$ComboSearchQueryNotifier {
final _searchQueryController = StreamController<ComboSearchQuery>();
late final StreamSubscription<ComboSearchQuery> _subscription;
@override
ComboSearchQuery build() {
_subscription = _searchQueryController.stream
.debounceTime(const Duration(milliseconds: 200))
.listen(_updateState);
ref.onDispose(() {
_searchQueryController.close();
_subscription.cancel();
});
return ComboSearchQuery(query: '', startDate: null, endDate: null);
}
void _updateState(ComboSearchQuery query) {
state = ComboSearchQuery(
query: query.query,
startDate: query.startDate,
endDate: query.endDate,
);
}
void setQuery(String query, {DateTime? startDate, DateTime? endDate}) {
final newQuery = ComboSearchQuery(
query: query,
startDate: startDate,
endDate: endDate,
);
state = newQuery; // Force Riverpod to detect a state change
}
}
@riverpod
Future<List<dynamic>> comboSearchResults(ComboSearchResultsRef ref) {
final searchQuery = ref.watch(comboSearchQueryNotifierProvider);
debugPrint('Updated search query in comboSearchResults: $searchQuery');
return ref.watch(comboListSearchProvider(searchQuery).future);
}
Share
Improve this question
edited Feb 5 at 20:57
Stalfurion
asked Feb 5 at 0:26
StalfurionStalfurion
311 silver badge13 bronze badges
2
- Can you distill this to an MRE? That might make the problem more obvious, or at least help the people who want to help you. – Randal Schwartz Commented Feb 5 at 0:47
- Appreciate the suggestion @RandalSchwartz - I know its a lot of code, I tried removing what I didnt think was relevant and updating my question a bit try to make the issue clearer, hopefully that helped. Did not want to remove too much more code as I was afraid it could obscure the issue. – Stalfurion Commented Feb 5 at 2:17
2 Answers
Reset to default 0I couldn't reproduce the issue, but I see two different potential issues
First there is two comboSearchResults
in provided code, perhaps you are mix-using them.
Second a lot of provider returns List<dynamic>
, which doesn't have a proper ==
and hashCode
defined, this can cause missed update in some cases.
I ended up changing my user interface to use a tab controller and searching the separate algolia indexes in their own respective tabs. My repository and notifiers are essentially the same as the posted code other than adding an 'indexName' parameter and removing one of the HitsSearchers in the search function. I'm still not sure why the code in my question doesn't work as I thought it should, but for now this update has solved my issue.