I am trying to build an app that uses GraphQL with the graphql-flutter package and with Blocs. I generated my graphql classes with the graphql_codegen package. In this app a list of events (the ‘History’) is shown. A few items are loaded, and after this ‘infinitive scroll’ will execute the ‘fetch more’ calls. The backend can also create new items, which are retrieved by the app through subscriptions.
This all seems to work fine, until the following happens:
- User opens page, history is loaded
- Server pushes a new item through subscription
- User closes page
- User reopens page –> now the history is shown, but without the items pushed through subscription
It looks like calling the query function (again) directly returns the data from the cache, but the cache did not include the subscription data. Am I supposted to update the cache from both the subscriptions and fetchmore results manually through writeQuery? And if so, which variables do I need to provide to make sure that the normal query will pick up the changes?
I have tried variations of the writeQuery
, with and without variables, but non of them give a good result after reopening the page.
class HistoryCubit extends Cubit<HistoryState> {
HistoryCubit(this._graphQLClient) : super(HistoryStateInitial());
final GraphQLClient _graphQLClient;
Future<void> loadAsync(String id) async {
try {
if (state is! HistoryStateLoading) {
emit(HistoryStateLoading());
await _stopSubscriptionAsync();
final subscription = _startSubscription(id);
final (result, history, hasReachedEnd) =
await _loadPlantHistoryAsync(id);
emit(HistoryStateLoaded(
id, history, hasReachedEnd, result, subscription));
}
} catch (ex) {
emit(HistoryStateError(ex));
}
}
Future<void> fetchMoreAsync() async {
if (state is HistoryStateLoaded) {
final loadedState = state as HistoryStateLoaded;
try {
emit(LoadedHistoryFetchingMoreState(
loadedState.id,
loadedState.history,
loadedState.hasReachedEnd,
loadedState.result,
loadedState.subscription,
));
await _fetchMore();
emit(HistoryStateLoaded(
loadedState.id,
loadedState.history,
loadedState.hasReachedEnd,
loadedState.result,
loadedState.subscription,
));
} catch (ex) {
// TODO: handle
}
}
}
StreamSubscription<QueryResult<Subscription_historyAdded>> _startSubscription(
String id) {
final subscription = _graphQLClient
.subscribe_historyAdded(
Options_Subscription_historyAdded(
variables: Variables_Subscription_historyAdded(plantId: id),
),
)
.listen((event) {});
subscription.onData((data) {
if (data.parsedData?.addedHistory != null) {
final loadedState = state as HistoryStateLoaded;
loadedState.result.parsedData?.listHistory?.items?.insert(
0,
Mappr().convert<Subscription_historyAdded_addedHistory?, History?>(
data.parsedData?.addedHistory,
),
);
emit(
HistoryStateLoaded(
loadedState.id,
(loadedState.result.parsedData?.listHistory?.items?.nonNulls
.toList() ??
[]),
loadedState.hasReachedEnd,
loadedState.result,
loadedState.subscription,
),
);
// Tried to use writeQuery here.
}
});
subscription.onError((data) {
// TODO: handle
});
return subscription;
}
Future _stopSubscriptionAsync() async {
if (state is HistoryStateLoaded) {
final loadedState = state as HistoryStateLoaded;
await loadedState.subscription.cancel();
}
if (state is LoadedHistoryFetchingMoreState) {
final loadedState = state as HistoryStateLoaded;
await loadedState.subscription.cancel();
}
}
Future<
(
QueryResult<Query_listHistory> result,
List<History> history,
bool hasReachedEnd,
)> _loadPlantHistoryAsync(String id) async {
final result = await _graphQLClient.query_listHistory(
Options_Query_listHistory(
variables: Variables_Query_listHistory(
filter: Input_HistoryFilterInput(plantId: id),
),
),
);
return _processResult(result);
}
Future<void> _fetchMore() async {
if (state is HistoryStateLoaded) {
final loadedState = state as HistoryStateLoaded;
if (loadedState.hasReachedEnd != true) {
final options = FetchMoreOptions_Query_listHistory(
updateQuery: (previousResultData, fetchMoreResultData) {
final List<dynamic> historyItems = [
...previousResultData!['listHistory']['items'] as List<dynamic>,
...fetchMoreResultData!['listHistory']['items'] as List<dynamic>
];
fetchMoreResultData['listHistory']['items'] = historyItems;
return fetchMoreResultData;
},
variables: Variables_Query_listHistory(
nextToken: loadedState.result.parsedData?.listHistory?.nextToken,
));
final result = await _graphQLClient.fetchMore(options,
originalOptions: Options_Query_listHistory(
variables: Variables_Query_listHistory(
filter: Input_HistoryFilterInput(plantId: loadedState.id),
),
),
previousResult: loadedState.result);
_processResult(result);
}
}
}
(
QueryResult<Query_listHistory> result,
List<History> history,
bool hasReachedEnd,
) _processResult(QueryResult<Query_listHistory> result) {
return (
result,
result.parsedData?.listHistory?.items?.nonNulls.toList() ?? [],
result.parsedData?.listHistory?.isDone ?? false,
);
}
@override
Future<void> close() async {
await _stopSubscriptionAsync();
super.close();
}
}
I have also opened a discussion in the graphql-flutter package, but that gave no response up till now. https://github.com/zino-hofmann/graphql-flutter/discussions/1371