diff --git a/android/app/src/main/java/io/cozy/flagship/mobile/MainApplication.java b/android/app/src/main/java/io/cozy/flagship/mobile/MainApplication.java index 3b26efec0..18e959089 100644 --- a/android/app/src/main/java/io/cozy/flagship/mobile/MainApplication.java +++ b/android/app/src/main/java/io/cozy/flagship/mobile/MainApplication.java @@ -65,15 +65,15 @@ public void onCreate() { WebView.setWebContentsDebuggingEnabled(true); OkHttpClientProvider.setOkHttpClientFactory(new UserAgentClientFactory()); - try { - Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); - field.setAccessible(true); - field.set(null, 50 * 1024 * 1024); // 50MB - } catch (Exception e) { - if (BuildConfig.DEBUG) { - e.printStackTrace(); - } - } + // try { + // Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); + // field.setAccessible(true); + // field.set(null, 50 * 1024 * 1024); // 50MB + // } catch (Exception e) { + // if (BuildConfig.DEBUG) { + // e.printStackTrace(); + // } + // } } /** diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e5db4d523..98be8e217 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -173,6 +173,9 @@ PODS: - MLImage (= 1.0.0-beta2) - MLKitCommon (~> 5.0) - Protobuf (~> 3.12) + - MMKV (1.3.2): + - MMKVCore (~> 1.3.2) + - MMKVCore (1.3.2) - nanopb (2.30908.0): - nanopb/decode (= 2.30908.0) - nanopb/encode (= 2.30908.0) @@ -423,6 +426,9 @@ PODS: - react-native-mlkit-ocr (0.3.0): - GoogleMLKit/TextRecognition (= 2.6.0) - React + - react-native-mmkv (2.5.1): + - MMKV (>= 1.2.13) + - React-Core - react-native-netinfo (9.3.7): - React-Core - react-native-print (0.11.0): @@ -616,6 +622,7 @@ DEPENDENCIES: - "react-native-gzip (from `../node_modules/@fengweichong/react-native-gzip`)" - react-native-idle-timer (from `../node_modules/react-native-idle-timer`) - react-native-mlkit-ocr (from `../node_modules/react-native-mlkit-ocr`) + - react-native-mmkv (from `../node_modules/react-native-mmkv`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-print (from `../node_modules/react-native-print`) - "react-native-receive-sharing-intent (from `../node_modules/@mythologi/react-native-receive-sharing-intent`)" @@ -689,6 +696,8 @@ SPEC REPOS: - MLKitTextRecognition - MLKitTextRecognitionCommon - MLKitVision + - MMKV + - MMKVCore - nanopb - NVHTarGzip - OpenSSL-Universal @@ -756,6 +765,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-idle-timer" react-native-mlkit-ocr: :path: "../node_modules/react-native-mlkit-ocr" + react-native-mmkv: + :path: "../node_modules/react-native-mmkv" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-print: @@ -872,6 +883,8 @@ SPEC CHECKSUMS: MLKitTextRecognition: 8b0e0023a4babc66ca83d8b82864e57931164445 MLKitTextRecognitionCommon: 3e84602c928fe2b775fae81376f2136324cbd763 MLKitVision: e87dc3f2e456a6ab32361ebd985e078dd2746143 + MMKV: f21593c0af4b3f2a0ceb8f820f28bb639ea22bb7 + MMKVCore: 31b4cb83f8266467eef20a35b6d78e409a11060d nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 NVHTarGzip: 74cc227b902e5725900d37eb6d79b57e93005a73 OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b @@ -901,6 +914,7 @@ SPEC CHECKSUMS: react-native-gzip: 5ffb84bf191c7cd135338eca748317bc466d41a1 react-native-idle-timer: f1920a59fe776340d004ff9de13c4a6eedcc8807 react-native-mlkit-ocr: 72cdbde86f8d29cba26cf9fa0a1865fe45c8f8d6 + react-native-mmkv: 69b9c003f10afdd01addf7c6ee784ce42ee2eff3 react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983 react-native-print: f704aef52d931bfce6d1d84351dbb5232d7ecb89 react-native-receive-sharing-intent: 0c21b8e80f629a73341f2566ce9b99df8124bb10 diff --git a/package.json b/package.json index 523f57909..cc509d191 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,8 @@ "react-native-localize": "2.2.6", "react-native-mask-input": "1.2.1", "react-native-mlkit-ocr": "^0.3.0", + "react-native-mmkv": "2.5.1", + "react-native-mmkv-flipper-plugin": "^1.0.0", "react-native-permissions": "^3.9.3", "react-native-play-install-referrer": "^1.1.8", "react-native-print": "0.11.0", diff --git a/src/App.js b/src/App.js index 5aae902a8..fcae628c8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,15 @@ import { NavigationContainer } from '@react-navigation/native' import { decode, encode } from 'base-64' import React, { useEffect, useState } from 'react' -import { StatusBar, StyleSheet, View } from 'react-native' +import { + StatusBar, + StyleSheet, + View, + ActivityIndicator, + InteractionManager +} from 'react-native' +import { MMKV } from 'react-native-mmkv' +import { initializeMMKVFlipper } from 'react-native-mmkv-flipper-plugin' import { Provider } from 'react-redux' import { PersistGate } from 'redux-persist/integration/react' import FlipperAsyncStorage from 'rn-flipper-async-storage-advanced' @@ -55,6 +63,17 @@ import { import LauncherView from '/screens/konnectors/LauncherView' +import { + hasMigratedFromAsyncStorage, + migrateFromAsyncStorage, + storage +} from '/libs/localStore/storage' + +// add this line inside your App.tsx +if (__DEV__) { + initializeMMKVFlipper({ default: storage }) +} + // Polyfill needed for cozy-client connection if (!global.btoa) { global.btoa = encode @@ -207,6 +226,30 @@ const WrappedApp = () => { const Wrapper = () => { const [hasCrypto, setHasCrypto] = useState(false) + const [hasMigrated, setHasMigrated] = useState(hasMigratedFromAsyncStorage) + + useEffect(() => { + if (!hasMigratedFromAsyncStorage) { + InteractionManager.runAfterInteractions(async () => { + try { + await migrateFromAsyncStorage() + setHasMigrated(true) + } catch (e) { + // TODO: fall back to AsyncStorage? Wipe storage clean and use MMKV? Crash app? + } + }) + } + }, []) + + if (!hasMigrated) { + // show loading indicator while app is migrating storage... + return ( + + + + ) + } + return ( <> {__DEV__ && } diff --git a/src/app/domain/backup/services/manageLocalBackupConfig.ts b/src/app/domain/backup/services/manageLocalBackupConfig.ts index 6bea3ee6e..4a23f5e2a 100644 --- a/src/app/domain/backup/services/manageLocalBackupConfig.ts +++ b/src/app/domain/backup/services/manageLocalBackupConfig.ts @@ -52,11 +52,19 @@ const INITIAL_BACKUP_CONFIG: LocalBackupConfig = { export const getLocalBackupConfig = async ( client: CozyClient ): Promise => { + const startTime = performance.now() + const backupConfig = await getUserPersistedData( client, UserPersistedStorageKeys.LocalBackupConfig ) + const endTime = performance.now() + + console.log( + `🟢 getLocalBackupConfig took ${endTime - startTime} milliseconds.` + ) + if (backupConfig === null) { throw new Error(t('services.backup.errors.configNotInitialized')) } @@ -142,7 +150,7 @@ export const setMediaAsBackuped = async ( uri: media.uri, creationDate: media.creationDate, modificationDate: media.modificationDate, - remoteId: documentCreated.id as string, + remoteId: documentCreated.id!, md5: documentCreated.attributes.md5sum } diff --git a/src/libs/localStore/storage.ts b/src/libs/localStore/storage.ts index bc3f3522a..ea3ad499b 100644 --- a/src/libs/localStore/storage.ts +++ b/src/libs/localStore/storage.ts @@ -3,7 +3,9 @@ import { BiometryType } from 'react-native-biometrics' import { logger } from '/libs/functions/logger' const log = logger('storage.ts') +import { MMKV } from 'react-native-mmkv' +export const storage = new MMKV() const { setItem, getItem, removeItem } = AsyncStorage export enum StorageKeys { AutoLockEnabled = '@cozy_AmiralApp_autoLockEnabled', @@ -37,7 +39,27 @@ export const storeData = async ( value: StorageItems[keyof StorageItems] ): Promise => { try { - await setItem(name, JSON.stringify(value)) + const startTime = performance.now() + + const res = JSON.stringify(value) + + const endTime = performance.now() + + console.log(`🔴 storeData parse took ${endTime - startTime} milliseconds.`) + + const startTime2 = performance.now() + + if (name.includes('AmiralAppLocalBackupConfig')) { + storage.set(name, res) + } else { + await setItem(name, res) + } + + const endTime2 = performance.now() + + console.log( + `🔴 storeData write took ${endTime2 - startTime2} milliseconds.` + ) } catch (error) { log.error(`Failed to store key "${name}" to persistent storage`, error) } @@ -45,9 +67,29 @@ export const storeData = async ( export const getData = async (name: StorageKeys): Promise => { try { - const value = await getItem(name) + const startTime = performance.now() + + let value + + if (name.includes('AmiralAppLocalBackupConfig')) { + value = storage.getString(name) + } else { + value = await getItem(name) + } + + const endTime = performance.now() + + console.log(`🟢 getData read took ${endTime - startTime} milliseconds.`) + + const startTime2 = performance.now() - return value !== null ? (JSON.parse(value) as T) : null + const res = value !== null ? (JSON.parse(value) as T) : null + + const endTime2 = performance.now() + + console.log(`🟢 getData parse took ${endTime2 - startTime2} milliseconds.`) + + return res } catch (error) { log.error(`Failed to get key "${name}" from persistent storage`, error) return null @@ -65,3 +107,41 @@ export const clearData = async (): Promise => { log.error(`Failed to clear data from persistent storage`, error) } } + +// TODO: Remove `hasMigratedFromAsyncStorage` after a while (when everyone has migrated) +export const hasMigratedFromAsyncStorage = storage.getBoolean( + 'hasMigratedFromAsyncStorage' +) + +// TODO: Remove `hasMigratedFromAsyncStorage` after a while (when everyone has migrated) +export async function migrateFromAsyncStorage(): Promise { + console.log('Migrating from AsyncStorage -> MMKV...') + const start = global.performance.now() + + const keys = await AsyncStorage.getAllKeys() + + for (const key of keys) { + try { + const value = await AsyncStorage.getItem(key) + + if (value != null) { + if (['true', 'false'].includes(value)) { + storage.set(key, value === 'true') + } else { + storage.set(key, value) + } + } + } catch (error) { + console.error( + `Failed to migrate key "${key}" from AsyncStorage to MMKV!`, + error + ) + throw error + } + } + + storage.set('hasMigratedFromAsyncStorage', true) + + const end = global.performance.now() + console.log(`Migrated from AsyncStorage -> MMKV in ${end - start}ms!`) +} diff --git a/yarn.lock b/yarn.lock index f12e9ec8a..90aee61e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16712,6 +16712,16 @@ react-native-mlkit-ocr@^0.3.0: resolved "https://registry.yarnpkg.com/react-native-mlkit-ocr/-/react-native-mlkit-ocr-0.3.0.tgz#b5b65dbe1fffdca0ca2012b1cab56cca6ea1bf58" integrity sha512-8oNJwNMGsUup41nWlKA01iW6xiNkCcXM3ANDsOKKijvsrd3eWNs7kL0Yhpt3Cq64zSyjoeqkdTdOCDiI6Pv6KA== +react-native-mmkv-flipper-plugin@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/react-native-mmkv-flipper-plugin/-/react-native-mmkv-flipper-plugin-1.0.0.tgz#f87f747d8cea51d2b12a1e711287feff5f212788" + integrity sha512-e3owMIBzXez45Wz8Ac84Vf1FmfwMXFVUpf/gCDWDLq19w7iCipVezQjlZo8ISVza8aQf1G4ggaK/r+qqtGmRlg== + +react-native-mmkv@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.5.1.tgz#29fc462077fab16a5e1b79570fbf8acaac9d87b4" + integrity sha512-5eQu25z3H6zf6w0NkJoTuFEFrbOu6luZxZ6+rK1W+XwY/rjPSFZFQPVtMaz3im90RbILFXXM/KrFGZrpaJJRoQ== + react-native-modal-datetime-picker@^14.0.0: version "14.0.1" resolved "https://registry.yarnpkg.com/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-14.0.1.tgz#d9c6df4ff85bf1cfbe108c756dc26dcca4cc5f2f"