aerogear / offix

GraphQL Offline Client and Server
https://offix.dev
Apache License 2.0
758 stars 45 forks source link

React Native support #23

Closed evelant closed 4 years ago

evelant commented 5 years ago

It looks like there are just a couple little missing pieces to work with react native. I'm not sure exactly how to integrate them properly into the codebase so I will put the snippets here. We just need a NetworkStatus implementation and to pass AsyncStorage for the storage engine when creating the client. IMO these should just be defaults when react native is the environment.

Detecting react-native

if (typeof navigator != 'undefined' && navigator.product == 'ReactNative') {
  // I'm in react-native, use the ReactNativeNetworkStatus and AsyncStorage
}

React native network status implementation

import {  NetInfo } from "react-native"
import { NetworkStatus, NetworkStatusChangeCallback } from "apollo-offline-client"

class ReactNativeNetworkStatus implements NetworkStatus {
  public onStatusChangeListener(callback: NetworkStatusChangeCallback): void {
    const listener = (connected: boolean) => {
      callback.onStatusChange({online: connected})
    };
    NetInfo.isConnected.addEventListener('connectionChange', listener)
  }

  public async isOffline(): Promise<boolean> {
    const isConnected = await NetInfo.isConnected.fetch()
    return !isConnected
  }
}

Config for react native

import { AsyncStorage } from "react-native"
import ReactNativeNetworkStatus from "./react-native-network-status.ts"

let config = {
  httpUrl: "http://localhost:4000/graphql",
  wsUrl: "ws://localhost:4000/graphql",
  storage: AsyncStorage,
  networkStatus: new ReactNativeNetworkStatus()
} 

edit: oops it looks like AsyncStorage might not be just drop in. Types don't line up but I think it will work regardless.

xtagon commented 5 years ago

Not everyone is using React let alone React Native. I use Ember.js with Apollo, for example, and plan to use Offix with Ember.js as well.

Is there a simple way to support this feature for React Native without introducing a dependency on React Native libraries?

evelant commented 5 years ago

That is why I opened the issue here. I'm not sure how to have easier react native setup without adding a dependency. Perhaps just putting this snippet into the docs would be sufficient?

xtagon commented 5 years ago

I would vote for adding it to the docs. (Note: I'm not the maintainer, just an outside opinion).

If the configuration gets more complicated than that, another idea is to create a separate integration library to handle some of the setup that is specific to React Native. For example, this is what ember-apollo-client does for Ember + Apollo integration, and you could probably do something similar for React Native + Offix integration.

wtrocki commented 5 years ago

@AndrewMorsillo Thank so much for contribution and logging issue. We were thinking about adding official support for react native and providing sample app that will demo all cases. I will add this to roadmap!

I think we can create separate package for react native that will wrap create client and supply react native based dependencies + it will have RNNetworkStatus and AsyncStore connected to config out of the box. Do you have time to drop this 2 files into new package? I will be really happy to assist with that, release package etc. but it will be nice to see if resulting package will be working in your app.

wtrocki commented 5 years ago

Additionally, I will refactor current config to decouple cordova integration and provide a similar package for cordova. After this is done documentation will have separate chapters for each platform and we will avoid having if inside the package.

We do not have any example application now for the React Native so looking for a community for input and end to end testing the package.

wtrocki commented 5 years ago

@AndrewMorsillo I have created new empty package to help with collaboration #24

wtrocki commented 5 years ago

@AndrewMorsillo Did you get any success with running React Native.

We have done lots of improvements inside the package and extended our interface so it should work flawlessly with the React Native, however, we do not have any sample app provided yet. If you know any sample React Native app that uses Apollo GraphQL please let us know. We can extend it with offix and see if we require additional package or docs.

evelant commented 5 years ago

I'm sorry to say my attention has been taken away from apollo/graphql as my team has decided to instead use Firebase to prototype our new project. I may have time to come back to this in the future but in the near term I won't have any time to look into it.

wtrocki commented 5 years ago

Perfect! Thank you for getting back. We will still invest time into getting the React Native in future so stay tunned for updates!

adamlewkowicz commented 5 years ago

I have used the following config and it throws error when I try to execute offline mutation. image React Native 0.60.4 - Android

wtrocki commented 5 years ago

@alk831 Hi. Do you think it will be possible to provide more details on how this is being setup? Console log might have more information on why this failed. We going to work on sample react native application

wtrocki commented 5 years ago

@alk831 Do you use Apollo boost? You might find this issue useful: https://github.com/apollographql/apollo-cache-persist/issues/52

wtrocki commented 5 years ago

I have managed to replicate this issue in https://github.com/wtrocki/offix-react-native-sample and working on checking why this is happening.

wtrocki commented 5 years ago

Moving from react to @react-native-community/async-storage removed the issue. Leaving this open as we need to add more production-ready examples of how to react native integration will be done.

adamlewkowicz commented 5 years ago

Sorry for the delay, since I have decided to use sqlite database, as my app will strongly rely on offline mode. I have been using AsyncStorage from community package and I still had this issue. It was probably caused by using incorrect client, since I have created it from ApolloClient (apollo-boost) and your example shows that client is being returned from offix's init promise.

wtrocki commented 5 years ago

Actually problem was that storage interface on save expected objects where react native expects string. I needed to create extra wrapper like here:

https://github.com/wtrocki/offix-react-native-sample/blob/master/offixapp/App.js#L12-L21

After that app is fully usable, however, I'm not satisfied enough to close this topic. I think we need to

We going to focus on this in near future (docs especially) with package and sample app to follow.

marceloavf commented 5 years ago

Can I use offix with react-native now or it still in development ?

wtrocki commented 5 years ago

@marceloavf Yes. Take look at sample application (really rough one) @mmusil would document support soon so we will have that in docs.

marceloavf commented 5 years ago

Really nice work put in here @wtrocki, I was a little worried about using Apollo for a most time offline project done with react-native, but seing those package codes made me happy, thank you!

wtrocki commented 5 years ago

We are working now on major improvements to library that will also affect react native.

anfen commented 5 years ago

I've been on the GraphQL bandwagon for a while, and came across Offix, which looks very promising indeed. I tried using Offix with react-native, and experienced the "apollo-cache-persist" bug (above) that @alk831 found.

I'm keen to keep the app inside Expo, so the fix to use '@react-native-community/async-storage' won't work, as Expo provides AsyncStorage from 'react-native'. I didn't want to duplicate efforts as @wtrocki has indicated major improvements in the last message above.

Should I standby for imminent support for Expo react-native?

Also, how would I add an API key for the graphql endpoint? This is usually done as an AuthLink like below, but currently I'm hacking \offix-client\dist\LinksBuilder.js#createCompositeLink by adding:

const authLink = setContext((_, { headers }) => {
    return {
    headers: {
        ...headers,
        "x-api-key": "API_KEY_HERE"
    }
    };
});
links.push(authLink);
wtrocki commented 5 years ago

@anfen For auth - you can pass terminating link (http link have headers property). see https://github.com/aerogear/offix/blob/master/docs/ref-release-notes.md#ability-to-customize-apollo-link-chain

The actual storage package do not matter that much. To support react-native AsyncStorage (as an opposed community) the same code can be used in app: https://github.com/wtrocki/offix-react-native-sample/blob/master/offixapp/App.js#L12-L21

What we need to do is to officially document this feature and we holding off as our docs needs to be rewritten.

CC @darahayes

anfen commented 5 years ago

@anfen For auth - you can pass terminating link (http link have headers property). see https://github.com/aerogear/offix/blob/master/docs/ref-release-notes.md#ability-to-customize-apollo-link-chain

The actual storage package do not matter that much. To support react-native AsyncStorage (as an opposed community) the same code can be used in app: https://github.com/wtrocki/offix-react-native-sample/blob/master/offixapp/App.js#L12-L21

What we need to do is to officially document this feature and we holding off as our docs needs to be rewritten.

CC @darahayes

Thanks for the auth explanation, and responding so quick. I tried the AsyncStorage example you referred to, but it returned an error, so I thought I would wait for the major improvements you mentioned.

I can try it again and report back with what I find. Before I do, how far are you with the improvements... should I hold off until then? I'm happy to contribute if there is a need (I'm a react/react-native dev).

wtrocki commented 5 years ago

Actually having error will be really helpfull. I'm not big fan of expo so having supported both expo and rn-cli will be awesome. Once we have it we can instantly add them to docs and close this issue.

wtrocki commented 5 years ago

Worth to note. Our React native support is the same as: https://github.com/apollographql/apollo-cache-persist/ support and their docs can be used to validate it.

anfen commented 5 years ago

I'm going to sound like a broken record, so apologies... "how far are you with the major improvements you mentioned" :)

I'd love to contribute, and Expo has come a long way, but I don't want to fix something that you may be working on.

wtrocki commented 5 years ago

@anfen This is completely unrelated. We are going to release new major change next week, however for both ReactNative support will not change - this will be just oportunity to improve documentation and add some platforms tab that will have:

anfen commented 5 years ago

@anfen This is completely unrelated. We are going to release new major change next week, however for both ReactNative support will not change - this will be just oportunity to improve documentation and add some platforms tab that will have:

  • Web
  • Ionic/Capacitor
  • React Native

Fantastic news. I will identify the Expo\react-native issue, and report back. Have a good weekend!

anfen commented 5 years ago

I used the code from here https://github.com/wtrocki/offix-react-native-sample/blob/master/offixapp/App.js#L12-L21 to test OfflineClient with react-native\Expo.

The storage part of the code uses: import AsyncStorage from '@react-native-community/async-storage'; but Expo must use: import { AsyncStorage } from 'react-native';

The result causes data corruption during setItem(), by saving a stringified object with a prop for each letter\number of the data being saved e.g.:

{"0":"{","1":"\"","2":"F","3":"a","4":"c","5":"t","6":":","7":"6","8":"d","9":"8","10":"6","11":"e","12":"5","13":"7","14":"1","15":"-","16":"e","17":"2","18":"a","19":"4","20":"-","21":"4","22":"5","23":"9","24":"8","25":"-","26":"a","27":"e","28":"5","29":"2","30":"-","31":"0","32":"d","33":"b","34":"b","35":"b","36":"8","37":"b","38":"1","39":"0","40":"b","41":"7","42":"3","43":"\"","44":":","45":"{","46":"\"","47":"","48":"","49":"t","50":"y","51":"p","52":"e","53":"n","54":"a","55":"m","56":"e","57":"\"","58":":","59":"\"","60":"F","61":"a","62":"c","63":"t","64":"\"","65":",","66":"\"","67":"i","68":"d","69":"\"","70":":","71":"\"","72":"6","73":"d","74":"8","75":"6","76":"e","77":"5","78":"7","79":"1","80":"-","81":"e","82":"2","83":"a","84":"4","85":"-","86":"4","87":"5","88":"9","8...

(You can make out Fact:6d86e... if you read every other prop, as the data being saved is Fact:UUID etc.)

I've added a "FIX:" comment below which does resolve this, but may not be the correct solution, as it hardcodes returning an object if a string was saved:

const client = new OfflineClient({
    terminatingLink: concat(authLink, httpLink),
    storage: {
      getItem: async (key) => {                                  
        const data = await AsyncStorage.getItem(key);

        if (typeof data === 'string') //FIX: Required to prevent data becoming corrupt and bloated
          return JSON.parse(data);

          return data;
      },
      setItem: async (key, value) => {
        let valueStr = value;
        if (typeof valueStr === 'object') {
          valueStr = JSON.stringify(value);
        }
        return AsyncStorage.setItem(key, valueStr);
      },
    }
  });

(Sorry about the formatting, the code snippet wouldn't work across the whole code block for some reason) Let me know if you need me to try any changes.

wtrocki commented 5 years ago

Amazing work. I think we need to simply document that use case. I have created separate hacktoberfest issue so you can just put all this information into our docs and get t-shirt for it.

see https://github.com/aerogear/offix/issues/219

EDIT: Fixed formatting by adding ```js

wtrocki commented 4 years ago

Update on this. We have provided docs and example application for react native.

I have found wrappers that will work with both expo and react cli.

wtrocki commented 4 years ago

Docs: https://offix.dev/docs/next/react-native

This is pointing to master. We going to land it in 0.12.0 release