TP-84996 | Caching in RN (#13461)

This commit is contained in:
Prajjaval Verma
2024-11-07 15:31:54 +05:30
committed by GitHub
parent 4d8bb91bbe
commit bd287d20f5
12 changed files with 247 additions and 144 deletions

View File

@@ -3,38 +3,75 @@ import { getXTargetHeaderInfo } from "../../../../network/ApiClient";
import { get } from "../../../../network/NetworkService";
import { ActionMetaData } from "../../../common/actions/GenericAction";
import {
AnalyticsEventNameConstants,
AnalyticsFlowNameConstant,
AnalyticsMethodNameConstant,
GI,
} from "../../../common/constants";
import { sendAsAnalyticsEvent } from "../../../common/hooks/useAnalyticsEvent";
import { CtaData } from "../../../common/interface";
import { ScreenData } from "../../../common/interface/widgets/screenData/ScreenData";
import { ScreenState } from "../../../common/screen/BaseScreen";
import { handleErrorData } from "../../../common/screen/ScreenActionHandler";
import {
handleErrorData,
handleResponseData,
} from "../../../common/screen/ScreenActionHandler";
import { ScreenActionTypes } from "../../../common/screen/ScreenActionTypes";
getScreenDataFromCache,
saveScreenDataInCache,
} from "../../../common/utilities/CacheUtils";
import { buildUrlWithParams } from "../../../common/utilities/SerializerUtil";
export const getBenefitPageData = async (
screenMetaData: ActionMetaData,
setScreenData: Dispatch<SetStateAction<ScreenData | null>>,
) => {
const url = "benefits/v2";
return get<ApiResponse<CtaData>>(
const cacheKey = buildUrlWithParams(url, screenMetaData.data);
const cachePromise = getScreenDataFromCache(cacheKey);
const apiPromise = get<ApiResponse<CtaData>>(
url,
getXTargetHeaderInfo(GI.toLocaleUpperCase()),
screenMetaData.data,
)
);
let isScreenLoaded = false;
cachePromise.then(cachedData => {
if (cachedData && !isScreenLoaded) {
setScreenData(cachedData);
isScreenLoaded = true;
}
});
apiPromise
.then(response => {
handleResponseData(setScreenData, response);
const updatedScreenData: ScreenData = {
...(response.data as ScreenData),
screenState: ScreenState.LOADED,
};
if (!isScreenLoaded) {
setScreenData(updatedScreenData);
isScreenLoaded = true;
}
saveScreenDataInCache(cacheKey, updatedScreenData);
})
.catch(error => {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_RN_BENEFIT,
AnalyticsMethodNameConstant.FETCH_BENEFIT_LIST,
);
if (!isScreenLoaded) {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_RN_BENEFIT,
AnalyticsMethodNameConstant.FETCH_BENEFIT_LIST,
);
isScreenLoaded = true;
} else {
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_BACKGROUND_API_FAILED,
properties: {
methodName: AnalyticsFlowNameConstant.GI_RN_BENEFIT,
reason: `${error.message}, axiosError: ${error.axiosCode}`,
statusCode: error.statusCode,
},
});
}
});
return;
};

View File

@@ -3,35 +3,71 @@ import { getXTargetHeaderInfo } from "../../../../network/ApiClient";
import { get } from "../../../../network/NetworkService";
import { ActionMetaData } from "../../../common/actions/GenericAction";
import {
AnalyticsEventNameConstants,
AnalyticsFlowNameConstant,
AnalyticsMethodNameConstant,
GI,
} from "../../../common/constants";
import { sendAsAnalyticsEvent } from "../../../common/hooks/useAnalyticsEvent";
import { CtaData } from "../../../common/interface";
import { ScreenData } from "../../../common/interface/widgets/screenData/ScreenData";
import { ScreenState } from "../../../common/screen/BaseScreen";
import { handleErrorData } from "../../../common/screen/ScreenActionHandler";
import {
handleErrorData,
handleResponseData,
} from "../../../common/screen/ScreenActionHandler";
getScreenDataFromCache,
saveScreenDataInCache,
} from "../../../common/utilities/CacheUtils";
export const getMarketBenefitComparePageData = async (
screenMetaData: ActionMetaData,
setScreenData: Dispatch<SetStateAction<ScreenData | null>>,
) => {
const url = "market-benefit-compare";
return get<ApiResponse<CtaData>>(
const cachePromise = getScreenDataFromCache(url);
const apiPromise = get<ApiResponse<CtaData>>(
url,
getXTargetHeaderInfo(GI.toLocaleUpperCase()),
)
);
let isScreenLoaded = false;
cachePromise.then(cachedData => {
if (cachedData && !isScreenLoaded) {
setScreenData(cachedData);
isScreenLoaded = true;
}
});
apiPromise
.then(response => {
handleResponseData(setScreenData, response);
const updatedScreenData: ScreenData = {
...(response.data as ScreenData),
screenState: ScreenState.LOADED,
};
if (!isScreenLoaded) {
setScreenData(updatedScreenData);
isScreenLoaded = true;
}
saveScreenDataInCache(url, updatedScreenData);
})
.catch(error => {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_RN_BENEFIT_COMPARE,
AnalyticsMethodNameConstant.MARKET_BENEFIT_COMPARE_LIST,
);
if (!isScreenLoaded) {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_RN_BENEFIT_COMPARE,
AnalyticsMethodNameConstant.MARKET_BENEFIT_COMPARE_LIST,
);
isScreenLoaded = true;
} else {
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_BACKGROUND_API_FAILED,
properties: {
methodName: AnalyticsFlowNameConstant.GI_RN_BENEFIT_COMPARE,
reason: `${error.message}, axiosError: ${error.axiosCode}`,
statusCode: error.statusCode,
},
});
}
});
return;
};

View File

@@ -25,7 +25,6 @@ export interface SumInsuredRequestData {
}
export const createQuote = async (
screenData: ScreenData | null | undefined,
screenMetaData: ActionMetaData,
setScreenData: Dispatch<SetStateAction<ScreenData | null>>,
navigation: any,
@@ -40,12 +39,6 @@ export const createQuote = async (
};
return post<ApiResponse<CtaData>>(url, screenMetaData.data, requestConfig)
.then(response => {
if (screenData?.screenState) {
setScreenData({
...screenData,
screenState: ScreenState.LOADING,
});
}
if (response) {
const updatedScreenData: ScreenData = {
...(response.data as ScreenData),

View File

@@ -3,35 +3,71 @@ import { getXTargetHeaderInfo } from "../../../../network/ApiClient";
import { get } from "../../../../network/NetworkService";
import { ActionMetaData } from "../../../common/actions/GenericAction";
import {
AnalyticsEventNameConstants,
AnalyticsFlowNameConstant,
AnalyticsMethodNameConstant,
GI,
} from "../../../common/constants";
import { sendAsAnalyticsEvent } from "../../../common/hooks/useAnalyticsEvent";
import { CtaData } from "../../../common/interface";
import { ScreenData } from "../../../common/interface/widgets/screenData/ScreenData";
import { ScreenState } from "../../../common/screen/BaseScreen";
import { handleErrorData } from "../../../common/screen/ScreenActionHandler";
import {
handleErrorData,
handleResponseData,
} from "../../../common/screen/ScreenActionHandler";
getScreenDataFromCache,
saveScreenDataInCache,
} from "../../../common/utilities/CacheUtils";
export const getWaitingPeriodScreenData = async (
screenMetaData: ActionMetaData,
setScreenData: Dispatch<SetStateAction<ScreenData | null>>,
) => {
const url = "waiting-period";
return get<ApiResponse<CtaData>>(
const cachePromise = getScreenDataFromCache(url);
const apiPromise = get<ApiResponse<CtaData>>(
url,
getXTargetHeaderInfo(GI.toLocaleUpperCase()),
)
);
let isScreenLoaded = false;
cachePromise.then(cachedData => {
if (cachedData && !isScreenLoaded) {
setScreenData(cachedData);
isScreenLoaded = true;
}
});
apiPromise
.then(response => {
handleResponseData(setScreenData, response);
const updatedScreenData: ScreenData = {
...(response.data as ScreenData),
screenState: ScreenState.LOADED,
};
if (!isScreenLoaded) {
setScreenData(updatedScreenData);
isScreenLoaded = true;
}
saveScreenDataInCache(url, updatedScreenData);
})
.catch(error => {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_WAITING_PERIOD,
AnalyticsMethodNameConstant.WAITING_PERIOD_SCREEN,
);
if (!isScreenLoaded) {
handleErrorData(
error,
setScreenData,
screenMetaData,
AnalyticsFlowNameConstant.GI_WAITING_PERIOD,
AnalyticsMethodNameConstant.WAITING_PERIOD_SCREEN,
);
isScreenLoaded = true;
} else {
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_BACKGROUND_API_FAILED,
properties: {
methodName: AnalyticsFlowNameConstant.GI_WAITING_PERIOD,
reason: `${error.message}, axiosError: ${error.axiosCode}`,
statusCode: error.statusCode,
},
});
}
});
return;
};

View File

@@ -13,6 +13,10 @@ export const AnalyticsEventNameConstants = {
HI_RN_BENEFIT_INIT: "hi_rn_benefit_init",
HI_RN_BENEFIT_ERROR_VIEW: "hi_rn_benefit_error_view",
HI_RN_BENEFIT_LOADED_VIEW: "hi_rn_benefit_loaded_view",
HI_RN_BACKGROUND_API_FAILED: "hi_rn_background_api_failed",
HI_RN_CACHE_ERROR: "hi_rn_cache_error",
HI_RN_CACHE_FETCH_SUCCESS: "hi_rn_cache_fetch_success",
HI_RN_CACHE_SAVE_SUCCESS: "hi_rn_cache_save_success",
// Sentry Events
HI_INVALID_SCREEN_CTA: "hi_invalid_screen_cta",
@@ -22,6 +26,7 @@ export const AnalyticsEventNameConstants = {
HI_RN_INVALID_DATA_ERROR: "hi_rn_invalid_data_error",
HI_RN_INVALID_JSON_ERROR: "hi_rn_invalid_json_error",
HI_RN_INVALID_ARITHMETIC_ERROR: "hi_rn_invalid_arithmetic_error",
HI_RN_FETCH_NATIVE_HEADER_ERROR: "hi_rn_fetch_native_header_error",
};
export const AnalyticsEventPropertyConstants = {
@@ -55,6 +60,7 @@ export const AnalyticsMethodNameConstant = {
FETCH_BENEFIT_LIST: "gi_fetch_benefit_screen_error",
WAITING_PERIOD_SCREEN: "gi_waiting_period_screen_error",
HANDLE_CTA_CLICK: "handleCtaClick",
HANDLE_CTA_CLICK_BOTTOMSHEET: "handleCtaClickBottomSheet",
};
export const AnalyticsGlobalErrorTypeConstant = {
@@ -79,6 +85,9 @@ export const EVENT_NAMES = {
SHARED_PREFERENCE_KEY_ERROR: "shared_preference_key_error",
JSON_PARSING_ERROR: "json_parsing_error",
INVALID_TARGET_TYPE_ERROR: "invalid_target_type_error",
INVALID_DATA_IN_CACHE: "invalid_data_in_cache",
INVALID_KEY_OR_DATA: "invalid_key_or_data",
ERROR_CHECKING_ASYNC_STORAGE: "error_checking_async_storage",
};
export const EVENT_PROPERTY_KEYS = {

View File

@@ -52,6 +52,7 @@ export const BUILD_CONFIG_DETAILS = "BUILD_CONFIG_DETAILS";
export const SPACE_UNICODE = "\u00A0";
export const REACT_NATIVE = "rn";
export const NAVIGATION_ERROR = "Navigation Error";
export const NETWORK_ERROR = "Network Error";
export const HOME = "home";
export const ADVERSE = "adverse";
export const HARDWARE_BACK_PRESS = "hardwareBackPress";

View File

@@ -4,33 +4,26 @@ import { View } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import { commonStyles } from "../../Container/Navi-Insurance/Styles";
import { ActionMetaData, GenericActionPayload } from "../actions/GenericAction";
import {
AnalyticsEventNameConstants,
BASE_SCREEN,
EVENT_NAMES,
} from "../constants";
import { sendAsAnalyticsEvent } from "../hooks/useAnalyticsEvent";
import { useBottomSheet } from "../hooks/useBottomSheet";
import useScreenLoadTime from "../hooks/useScreenLoadTime";
import { CtaData } from "../interface";
import { ModalView } from "../interface/modals/ModalView";
import { ScreenData } from "../interface/widgets/screenData/ScreenData";
import { Router } from "../navigator/NavigationRouter";
import { updateCtaData } from "../redux/screens/screenActionCreators";
import { setCurrentScreenName } from "../utilities/AlfredUtils";
import {
getCacheKey,
getScreenDataFromCache,
isScreenWhiteListedForCaching,
saveScreenDataInCache,
} from "../utilities/CacheUtils";
import {
getScreenMapperNameFromCtaData,
getScreenNameFromCtaData,
} from "../utilities/MiscUtils";
import { WidgetActionTypes } from "../widgets/widget-actions/WidgetActionTypes";
import { ScreenMapper } from "./screen-mappers/ScreenMapper";
import useScreenLoadTime from "../hooks/useScreenLoadTime";
import {
AnalyticsEventNameConstants,
BASE_SCREEN,
EVENT_NAMES,
CODEPUSH_METHOD,
} from "../constants";
const BaseScreen: React.FC<{ navigation: any; route: any }> = ({
navigation,
@@ -47,56 +40,20 @@ const BaseScreen: React.FC<{ navigation: any; route: any }> = ({
);
useEffect(() => {
const cacheKey = getCacheKey(screenName, screenKey);
if (!screenData) {
const screenInitialData: ScreenData = {
screenState: ScreenState.LOADING,
};
setScreenData(screenInitialData);
}
if (!!cacheKey && isScreenWhiteListedForCaching(screenName)) {
if (
!(
!!screenData?.errorMetaData ||
screenData?.screenState === ScreenState.ERROR
)
) {
saveScreenDataInCache(screenName, screenData);
}
}
}, [screenData]);
useEffect(() => {
if (!!getScreenNameFromCtaData(route.params?.ctaData)) {
setScreenName(getScreenNameFromCtaData(route.params?.ctaData)!!);
if (!!route.params?.ctaData?.screenKey) {
setScreenKey(route.params?.ctaData?.screenKey);
}
if (isScreenWhiteListedForCaching(screenName)) {
retrieveScreenDataFromCache(
getScreenNameFromCtaData(route.params?.ctaData)!!,
route.params?.ctaData?.screenKey,
);
}
}
}, []);
const dispatch = useDispatch();
const retrieveScreenDataFromCache = (
screenName: string | null | undefined,
screenKey: string | null | undefined,
) => {
let cacheKey = getCacheKey(screenName, screenKey);
if (!!cacheKey) {
getScreenDataFromCache(cacheKey).then(screenData => {
if (!!screenData) {
setScreenData(screenData);
}
});
}
};
const { cta } = useSelector((state: any) => {
const savedCta = state.screenReducer.ctaData;
if (isEqual(savedCta, route.params.ctaData)) {
@@ -105,12 +62,14 @@ const BaseScreen: React.FC<{ navigation: any; route: any }> = ({
return { cta: route.params.ctaData };
});
const dispatch = useDispatch();
useEffect(() => {
const ctaData: CtaData = route.params.ctaData;
if (!isEqual(cta, ctaData)) {
dispatch(updateCtaData({ cta: ctaData, setScreenState: screenState }));
}
}, [route.params.ctaData, screenState]);
useScreenLoadTime(screenName, screenData?.screenState);
const handleActions = (actionPayload?: GenericActionPayload) => {
@@ -138,7 +97,6 @@ const BaseScreen: React.FC<{ navigation: any; route: any }> = ({
if (!!actionPayload) {
Router.handleAction(updatedActionPayload, navigation);
} else {
// handle error
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_PAYLOAD_ERROR,
properties: {

View File

@@ -11,7 +11,6 @@ import { ActionMetaData, BaseActionTypes } from "../actions/GenericAction";
import {
AnalyticsEventNameConstants,
AnalyticsModuleNameConstant,
BASE_SCREEN,
EVENT_NAMES,
} from "../constants";
import {
@@ -38,12 +37,7 @@ export const ScreenActionHandler = {
return fetchComparisonPlanList(screenMetaData, setScreenData);
}
case ScreenActionTypes.FETCH_QUOTE_V4: {
return createQuote(
screenData,
screenMetaData,
setScreenData,
navigation,
);
return createQuote(screenMetaData, setScreenData, navigation);
}
case ScreenActionTypes.FETCH_BENEFIT_COMPARE_LIST: {
return getMarketBenefitComparePageData(screenMetaData, setScreenData);

View File

@@ -2,34 +2,58 @@ import AsyncStorage from "@react-native-async-storage/async-storage";
import { BASE_URL } from "../../../network/NetworkConstant";
import { NetworkConnectorModule } from "../../common/native-module/NativeModules";
import {
BENEFIT_SCREEN,
AnalyticsEventNameConstants,
BUILD_CONFIG_DETAILS,
BUY_INSURANCE_SCREEN,
BuildConfigConstants,
COMPARE_PLAN_SCREEN,
QUOTE_OFFER_SCREEN,
EVENT_NAMES,
} from "../constants";
import { sendAsAnalyticsEvent } from "../hooks/useAnalyticsEvent";
import { CtaData } from "../interface";
import { ScreenData } from "../interface/widgets/screenData/ScreenData";
import { LineItem } from "../interface/widgets/screenData/ScreenMetaData";
export const getScreenDataFromCache = async (
key: string,
): Promise<ScreenData | null> => {
try {
const value = await AsyncStorage.getItem(key);
if (!!value) {
// The key exists in AsyncStorage
const deserializedObject = JSON.parse(value);
try {
const deserializedObject = JSON.parse(value);
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_FETCH_SUCCESS,
properties: {
key: `${key}`,
},
});
return deserializedObject as ScreenData;
} catch (error) {
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_ERROR,
properties: {
error: EVENT_NAMES.JSON_PARSING_ERROR,
reason: `${error}`,
},
});
return null;
}
} else {
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_ERROR,
properties: {
error: EVENT_NAMES.INVALID_DATA_IN_CACHE,
},
});
return null;
}
} catch (error) {
console.error("Error checking AsyncStorage:", error);
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_ERROR,
properties: {
error: EVENT_NAMES.ERROR_CHECKING_ASYNC_STORAGE,
reason: `${error}`,
},
});
return null;
}
};
@@ -40,20 +64,21 @@ export const saveScreenDataInCache = async (
) => {
if (!!key && !!data) {
const serializedObject = JSON.stringify(data);
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_SAVE_SUCCESS,
properties: {
key: `${key}`,
},
});
await AsyncStorage.setItem(key, serializedObject);
} else {
console.error("key or data was invalid");
}
};
export const getCacheKey = (
screenName: string | null | undefined,
screenKey: string | null | undefined,
) => {
if (!!screenKey && !!screenName) {
return screenName + "#" + screenKey;
} else {
return screenName;
sendAsAnalyticsEvent({
name: AnalyticsEventNameConstants.HI_RN_CACHE_ERROR,
properties: {
error: EVENT_NAMES.INVALID_KEY_OR_DATA,
key: `${key}`,
},
});
}
};
@@ -133,16 +158,3 @@ export const getBuildConfigDetailsFromCta = (
});
return buildConfigDetails;
};
export const isScreenWhiteListedForCaching = (
screenName: string | null | undefined,
) => {
return !screensWithCachingDisabled.includes(screenName || "");
};
export const screensWithCachingDisabled = [
BUY_INSURANCE_SCREEN,
QUOTE_OFFER_SCREEN,
COMPARE_PLAN_SCREEN,
BENEFIT_SCREEN,
];

View File

@@ -1,8 +1,4 @@
import {
AnalyticsEventNameConstants,
BASE_SCREEN,
EVENT_NAMES,
} from "../constants";
import { AnalyticsEventNameConstants, EVENT_NAMES } from "../constants";
import { sendAsAnalyticsEvent } from "../hooks/useAnalyticsEvent";
export function parseValue(value: any, targetType: string): any | null {
@@ -48,3 +44,13 @@ export function parseValue(value: any, targetType: string): any | null {
return null;
}
}
export const buildUrlWithParams = (
baseUrl: string,
params: Record<string, any>,
): string => {
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join("&");
return `${baseUrl}?${queryString}`;
};