0

Background: I've tried reading up on this issue, but can't come up with an explanation of what actually happens on iOS devices w.r.t. a react-redux and redux-persist store setup.

References:

Problem:

  1. Our QA team was unable to test out the onboarding experience.
  2. Onboarding experience: 3 intro screens (controlled by isNew flag -> login -> onboarding tutorial (triggered by is_first_time_login: true, hasLoggedIn || hasPinCodeLoggedIn: true, and hasFinishedOnboardTutorial:false
  3. We've tried on both Android and iOS devices, Android works as intended, but for iOS, it doesn't.
  4. So for iOS devices, we've deleted the app, restarted the phone, then installed the test app. The test app is distributed via firebase distribution.
  5. The problem persists, and I'm at a loss on what to do now.

Code setup:

store/index.js

    import { applyMiddleware, compose, createStore } from 'redux';
    import { persistReducer, persistStore, createTransform } from 'redux-persist';
    import createSagaMiddleware from 'redux-saga';
    import logger from 'redux-logger';
    import createSensitiveStorage from 'redux-persist-sensitive-storage';
    import { NODE_ENV } from '@env';
    import isEmpty from 'lodash/isEmpty';
    import RootSaga from 'Sagas';
    import RootReducer from 'Reducers';
    import { UserDetail } from 'Models';
    import ApiUtils from 'Services';
    import { Configs, reactotron } from 'Configs';
    
    const secureStorage = createSensitiveStorage({
      keychainService: 'xxxx',
      sharedPreferencesName: 'xxx'
    });
    
    const PersistStateTransformer = createTransform(
      (inboundState) => {
        return { ...inboundState };
      },
      (outboundState) => {
        if (!isEmpty(outboundState?.token)) {
          ApiUtils.injectToken(outboundState?.token);
        }
        return { ...outboundState, userDetails: new UserDetail(outboundState.userDetails), backgroundTimestamp: 0, hasPinCodeLoggedIn: false };
      },
      {
        whitelist: ['auth'],
      }
    );
    
    const persistConfig = {
      key: 'root',
      storage: secureStorage,
      transforms: [PersistStateTransformer],
      whitelist: ['auth', 'ui'],
    };
    
    // Use to remove all the existing state including auth/ui
    // if (NODE_ENV === 'development') {
    //   AsyncStorage.removeItem('persist:root');
    // }
    
    export const buildStore = (initialState = {}) => {
      const middlewares = [];
      const enhancers = [];
    
      // Connect the sagas to the redux store
      const sagaMiddleware = createSagaMiddleware();
      middlewares.push(sagaMiddleware);
    
      if (NODE_ENV === 'development') {
        middlewares.push(logger);
      }
    
      // do not remove spread operator
      enhancers.push(applyMiddleware(...middlewares));
    
      // Redux persist
      const persistedReducer = persistReducer(persistConfig, RootReducer);
    
      // Reactotron is only needed during dev, in test env it's not necessary
      const store =
        NODE_ENV === 'development'
          ? createStore(persistedReducer, initialState, compose(...enhancers, reactotron.createEnhancer()))
          : createStore(persistedReducer, initialState, compose(...enhancers));
      const persistor = persistStore(store);
    
      // Kick off the root saga
      sagaMiddleware.run(RootSaga);
    
      return { store, persistor };
    };

App.js

import React, { useEffect } from 'react';
import { Platform, UIManager } from 'react-native';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import SplashScreen from 'react-native-splash-screen';
import Navigation from 'Navigations';
import { buildStore } from 'Stores';
import { Loading } from 'Screens';
import { Toast } from 'Components';
import GlobalUiProvider from 'Contexts';
import ApiUtils from 'Services';

const { store, persistor } = buildStore();
ApiUtils.injectStore(store);

const App = () => {
  if (Platform.OS === 'android') {
    UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
  }

  useEffect(() => {
    SplashScreen.hide();
  }, []);

  return (
    <SafeAreaProvider>
      <Provider store={store}>
        <PersistGate loading={<Loading />} persistor={persistor}>
          <GlobalUiProvider>
            <Navigation />
          </GlobalUiProvider>
          <Toast />
        </PersistGate>
      </Provider>
    </SafeAreaProvider>
  );
};

export default App;
export { store, persistor };

publicNavigation.js (snippet)

<PublicStack.Navigator screenOptions={Configs.PUBLIC_SCREEN_OPTIONS}>
      {isNew ? (
        <PublicStack.Screen name="Onboard" component={Onboard} />
      ) : (
          <PublicStack.Screen name="Login" component={Login} />
      )}
    </PublicStack.Navigator>
  );

Let me know if I need to add more details.

mdoxb8
  • 1
  • Hi. What happens if they delete cache and app storage from the settings? – Luka Dumančić Nov 03 '21 at 10:55
  • @LukaDumančić, nothing apparently. :/ the flags `hasLoggedIn || hasPinCodeLoggedIn: true`, and `hasFinishedOnboardTutorial:false` remain as they were from a previous "user" – mdoxb8 Nov 10 '21 at 11:19

0 Answers0