Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CacheAndNetwork and offline #16

Closed
Feduch opened this issue Apr 7, 2020 · 17 comments
Closed

CacheAndNetwork and offline #16

Feduch opened this issue Apr 7, 2020 · 17 comments

Comments

@Feduch
Copy link

Feduch commented Apr 7, 2020

Hi!

In app I use FetchPolicy.CacheAndNetwork for query.

When I disable internet on phone or internet very bad, but data is set in cache, I get this error.

It is normal logic for CacheAndNetwork?

DeepinScreenshot_выберите-область_20200407174812

The getter 'loading' was called on null.
Receiver: null
Tried calling: loading

When I use query without FetchPolicy, data is set in cache and disable internet, it is ok.

@smkhalsa
Copy link
Member

smkhalsa commented Apr 7, 2020

Can you share your dart code where you call this query?

@Feduch
Copy link
Author

Feduch commented Apr 8, 2020

Hi!

BlocBuilder<AuthenticationBloc, AuthenticationState>(
    builder: (context, state) {
  idToken = {'Authorization': 'Bearer null}'};

  if (state is Authenticated) {
    idToken = {
      'Authorization': 'Bearer ${state.authHiveModel.idToken}'
    };
  }

  return Padding(
    padding: const EdgeInsets.only(right: 25.0),
    child: Query(
      client: client,
      queryRequest:
          markersStats(fetchPolicy: FetchPolicy.CacheAndNetwork)
              .copyWith(
        context: Context.fromList(
          [
            HttpLinkHeaders(
              headers: idToken,
            ),
          ],
        ),
      ),
      builder: (BuildContext context,
          QueryResponse<$markersStats> response,
          Object clientError) {
        if (response.loading) {
          return MainBoxForIcons(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                CircularProgressIndicator(),
              ],
            ),
          );
        }

        final dynamic markersStatsData =
            response.data?.markersStats;

        return MainBoxForIcons(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              SvgPicture.asset(
                'assets/icons/icon_med_card.svg',
                height: 26.0,
              ),
              Row(
                mainAxisAlignment:
                    MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  MainRoundedBoxColored(
                    text: markersStatsData.failMarkersCount
                        .toString(),
                    color: Color.fromRGBO(235, 38, 118, 0.6),
                  ),
                  MainRoundedBoxColored(
                    text: markersStatsData.successMarkersCount
                        .toString(),
                    color: Color.fromRGBO(0, 186, 120, 0.6),
                  ),
                  MainRoundedBoxColored(
                    text: markersStatsData.noneMarkersCount
                        .toString(),
                    color: Color.fromRGBO(87, 87, 86, 0.6),
                  ),
                ],
              )
            ],
          ),
        );
      },
    ),
  );
}),

Error in

if (response.loading) {

@Feduch
Copy link
Author

Feduch commented Apr 11, 2020

Hello guys!

Is it bug in ferry or my mistake?

@smkhalsa
Copy link
Member

This should be fixed in Ferry. For now, can you use a null check on response?

E.g.

if (response?.loading ?? true)

@Feduch
Copy link
Author

Feduch commented Apr 11, 2020

@smkhalsa

if (response?.loading ?? true)

If use is check, then not getting error. But if app is offline, then showing CircularProgressIndicator().

Will be cool if when internet is offline, then get data from cache.

@smkhalsa
Copy link
Member

@Feduch so no data is ever returned? That definitely shouldn't be happening. I'll have to investigate futher.

@Feduch
Copy link
Author

Feduch commented Apr 12, 2020

@smkhalsa no data is returned, if internet offline.

In code:

DeepinScreenshot_выберите-область_20200412133547

DeepinScreenshot_выберите-область_20200412133517

@smkhalsa
Copy link
Member

Thanks. I believe the issue here is that the Link throws an error when offline which cancels the stream. We need to improve error handling more generally (#8), but I'll try to prioritize this.

@Feduch Feduch closed this as completed Apr 13, 2020
@smkhalsa
Copy link
Member

@Feduch does #23 fix this issue for you?

@Feduch
Copy link
Author

Feduch commented Apr 23, 2020

@smkhalsa Hi!

Yes, thank you!

Question, now if app is offline, then do not get data from cache.
It is normal logic for CacheAndNetwork?

DeepinScreenshot_выберите-область_20200423152742

@smkhalsa
Copy link
Member

Does "CacheFirst" work as expected for you offline? Also, does "CacheAndNetwork" work for you when you're online?

The issue with CacheAndNetwork is that the cached data will get returned immediately (if any exists), then if the network response fails, the stream will emit another response with the Network Error and no data.

I think we could simply make a change to use the cached data when there is a Network Error. That seems to be what would be expected, but maybe I'm missing an edge case.

@smkhalsa smkhalsa reopened this Apr 23, 2020
@Feduch
Copy link
Author

Feduch commented Apr 23, 2020

Does "CacheFirst" work as expected for you offline?

Yes, it`s work. When app is offline, data get from cache.

The database has data that can change at any time, they need to be updated in the application.
Now I am doing an additional check of the network status (response.networkError) and manually taking data from the cache (client.responseStream(query).....).

Also, does "CacheAndNetwork" work for you when you're online?

Yes, work is very good)

@smkhalsa
Copy link
Member

smkhalsa commented Sep 8, 2020

@Feduch Can you please confirm whether this is still an issue in the latest ferry version?

@smkhalsa
Copy link
Member

@Feduch closing for now. If this is still an issue, feel free to reopen.

@basharh
Copy link

basharh commented Feb 21, 2024

@smkhalsa This might still be an issue. When using FetchPolicy.CacheAndNetwork, an offline response does not contain any data even when the data has been cached on a previous request. This is reproducible with the pokemon example. I was able to reproduce by:

  • changing request on pokemon_list to
          (b) => b
            ..fetchPolicy = FetchPolicy.CacheAndNetwork
            ..vars.limit = 50
            ..vars.offset = 0,
        ),
  • while online run the app -> see the list
  • turn off Wifi -> restart the app -> no data is shown

Expected behavior would be for the response to contain cached data even when response.hasErrors is true. I believe this is what graphql_flutter does.

@knaeckeKami
Copy link
Collaborator

knaeckeKami commented Feb 21, 2024

.CacheAndNetwork will return cached data, and then return the result of the network request.

You can filter the errors from the UI using logic like this (did not run this, this is just the idea):

Stream<OperationResponse><Data,Vars> filterErrorsAfterData<Data, Vars>(Stream<OperationResult<Data,Vars>> inputStream) {

  bool isFirst = true;

  return inputStream.transform(StreamTransformer.fromHandlers(
    handleData: (data, sink) {
      if (isFirst) {
        sink.add(data);
        isFirst = false;
      } else if (!data.hasErrors) {
        sink.add(data); 
      }
    },
    handleDone: (sink) {
      sink.close();
    },
  ));
}

@basharh
Copy link

basharh commented Feb 22, 2024

This is great, thank you. I ended up modifying your solution a bit so I can use the full response object. It would be convenient for the cached data to come back with the second network response though but I can see the logic for both cases.

import 'dart:async';

import 'package:ferry/ferry.dart';

makeRobust<T, D>(Stream<OperationResponse<T, D>> stream) {
  OperationResponse<T, D>? prevResponse = null;

  return stream.transform(
    StreamTransformer.fromHandlers(
        handleData: (response, EventSink<OperationResponse<T, D>> sink) {
      if (response.hasErrors &&
          prevResponse != null &&
          prevResponse!.data != null) {
        sink.add(prevResponse!);
      } else {
        prevResponse = response;
        sink.add(response);
      }
    }, handleDone: (sink) {
      sink.close();
    }),
  );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants