Skip to content

Commit

Permalink
refactor: change mmkv library
Browse files Browse the repository at this point in the history
  • Loading branch information
shrouxm committed Jan 24, 2025
1 parent bc6e0df commit 5df861e
Show file tree
Hide file tree
Showing 15 changed files with 71 additions and 98 deletions.
3 changes: 1 addition & 2 deletions dev-client/jest.integration.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ module.exports = {
setupFilesAfterEnv: [
'@testing-library/jest-native/extend-expect',
'@rnmapbox/maps/setup-jest',
'./node_modules/react-native-mmkv-storage/jest/mmkvJestSetup.js',
'<rootDir>/jest/integration/setup.ts',
],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|react-native-cookies|uuid|react-native-mmkv-storage|react-native-autocomplete-input|expo(nent)?|@expo(nent)?/.*)|expo-constants|@rnmapbox/)',
'node_modules/(?!(react-native|@react-native|react-native-cookies|uuid|react-native-autocomplete-input|expo(nent)?|@expo(nent)?/.*)|expo-constants|@rnmapbox/)',
],
moduleNameMapper: {
'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
Expand Down
6 changes: 1 addition & 5 deletions dev-client/jest.unit.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ module.exports = {
testMatch: ['**/src/**/*.test.[jt]s?(x)'],
preset: 'jest-expo',
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'./node_modules/react-native-mmkv-storage/jest/mmkvJestSetup.js',
'<rootDir>/jest/unit/setup.ts',
],
transformIgnorePatterns: ['/!node_modules\\/react-native-mmkv-storage/'],
setupFilesAfterEnv: ['<rootDir>/jest/unit/setup.ts'],
clearMocks: true,
moduleNameMapper: {
'\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
Expand Down
9 changes: 0 additions & 9 deletions dev-client/jest/integration/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ jest.mock('terraso-mobile-client/config', () => ({
APP_CONFIG: {},
}));

let mmkvMock = require('react-native-mmkv-storage/jest/dist/jest/memoryStore.js');
mmkvMock.mock(); // Mock the storage

setAPIConfig({
terrasoAPIURL: 'http://127.0.0.1:8000',
graphQLEndpoint: '/graphql/',
Expand All @@ -67,9 +64,3 @@ global.console.warn = jest.fn();
// for native base animations. Would be nice to figure out how to disable this
// For now, just silence
global.console.error = jest.fn();

beforeEach(() => {
// Install the in-memory adapter
mmkvMock.unmock(); // Cleanup if already mocked
mmkvMock.mock(); // Mock the storage
});
9 changes: 0 additions & 9 deletions dev-client/jest/unit/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,3 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

let mmkvMock = require('react-native-mmkv-storage/jest/dist/jest/memoryStore.js');
mmkvMock.mock(); // Mock the storage

beforeEach(() => {
// Install the in-memory adapter
mmkvMock.unmock(); // Cleanup if already mocked
mmkvMock.mock(); // Mock the storage
});
18 changes: 8 additions & 10 deletions dev-client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"react-native-gesture-handler": "^2.21.2",
"react-native-get-random-values": "^1.11.0",
"react-native-gradle-plugin": "^0.71.19",
"react-native-mmkv-storage": "^0.11.2",
"react-native-mmkv": "^2.12.2",
"react-native-pager-view": "^6.6.1",
"react-native-paper": "^5.12.5",
"react-native-reanimated": "^3.16.6",
Expand Down
5 changes: 1 addition & 4 deletions dev-client/src/config/featureFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ test('offline feature flag starts on in dev mode and can be disabled', () => {

expect(isFlagEnabled('FF_offline')).toBe(true);

// This assertion fails due to a bug in the react-native-mmkv-storage library
// It can be commented back in when this is fixed:
// https://github.com/ammarahm-ed/react-native-mmkv-storage/issues/360
// expect(willFlagBeEnabledOnReload('FF_offline')).toBe(false);
expect(willFlagBeEnabledOnReload('FF_offline')).toBe(false);
});
});
4 changes: 2 additions & 2 deletions dev-client/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ setAPIConfig({
return value === null ? undefined : value;
},
setToken: (name, value) => {
kvStorage.setStringAsync(name, value);
kvStorage.setString(name, value);
},
removeToken: name => {
kvStorage.removeItem(name);
kvStorage.remove(name);
},
initialToken: null,
},
Expand Down
30 changes: 0 additions & 30 deletions dev-client/src/hooks/useStorage.ts

This file was deleted.

4 changes: 2 additions & 2 deletions dev-client/src/navigation/navigators/RootNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import {
} from 'terraso-client-shared/account/accountSlice';

import {WELCOME_SCREEN_SEEN_KEY} from 'terraso-mobile-client/constants';
import {useKVStorage} from 'terraso-mobile-client/hooks/useStorage';
import {DEFAULT_STACK_NAVIGATOR_OPTIONS} from 'terraso-mobile-client/navigation/constants';
import {
modalScreens,
screens,
} from 'terraso-mobile-client/navigation/screenDefinitions';
import {RootStack} from 'terraso-mobile-client/navigation/types';
import {kvStorage} from 'terraso-mobile-client/persistence/kvStorage';
import {useDispatch, useSelector} from 'terraso-mobile-client/store';

const modalScreenOptions: NativeStackNavigationOptions = {
Expand All @@ -47,7 +47,7 @@ export const RootNavigator = () => {
state => state.account.currentUser.data !== null,
);

const [welcomeScreenAlreadySeen] = useKVStorage(
const [welcomeScreenAlreadySeen] = kvStorage.useBool(
WELCOME_SCREEN_SEEN_KEY,
false,
);
Expand Down
4 changes: 2 additions & 2 deletions dev-client/src/persistence/kvStorage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
import {kvStorage} from 'terraso-mobile-client/persistence/kvStorage';

test('kvStorage mock works', () => {
kvStorage.setMap('map-key', {testKey: 'testValue'});
expect(kvStorage.getMap('map-key')).toEqual({testKey: 'testValue'});
kvStorage.setObject('map-key', {testKey: 'testValue'});
expect(kvStorage.getObject('map-key')).toEqual({testKey: 'testValue'});
});
65 changes: 48 additions & 17 deletions dev-client/src/persistence/kvStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import {MMKVLoader} from 'react-native-mmkv-storage';
import {
MMKV,
useMMKVBoolean,
useMMKVNumber,
useMMKVObject,
useMMKVString,
} from 'react-native-mmkv';

// helpful things to know about MMKV:
//
Expand All @@ -24,36 +30,61 @@ import {MMKVLoader} from 'react-native-mmkv-storage';
// there's the underlying cross-platform native library:
// https://github.com/Tencent/MMKV
// and then there's the react native wrapper library:
// https://www.npmjs.com/package/react-native-mmkv-storage
// there is also a competing react native wrapper library:
// https://www.npmjs.com/package/react-native-mmkv
//
// the library we use simplifies encryption by automatically
// generating an encryption key and storing it on the keychain,
// which we would have to do manually if we were using the other
// library. at time of writing there is not a well-documented
// migration story from one to the other.
// there is also a competing react native wrapper library, which
// we used originally but abandoned due to slow maintenance:
// https://www.npmjs.com/package/react-native-mmkv-storage
//
// MMKV does not necessarily write to disk after every operation,
// it will keep data in memory and only flush it when necessary
// for performance reasons. I'm not sure if the RN API exposes a
// way to manually flush to disk, there's a `clearMemoryCache`
// method which presumably does this but haven't confirmed.
//
// `withEncryption` only has an effect when the device has never
// initialized the MMKV store before. after first usage, the store
// gets initialized with whatever settings it previously used.
// there are additional methods available for changing the encryption
// settings later: https://rnmmkv.vercel.app/#/workingwithencryption
//
// There is discussion on all of the libraries of a potential memory
// use issue:
// https://github.com/ammarahm-ed/react-native-mmkv-storage/issues/286
// https://github.com/mrousavy/react-native-mmkv/issues/440
// https://github.com/Tencent/MMKV/issues/610
// I ran a test in our environment and was unable to reproduce (persisted
// entire redux state to storage in a loop for several hours), but leaving
// here for context. There is also a `clearMemoryCache` method on storage
// here for context. There is also a `trim` method on storage
// object which may help.

export const kvStorage = new MMKVLoader().withEncryption().initialize();
const MMKV_STORAGE_ID = 'mmkv.storage';

const mmkvStorage = new MMKV({id: MMKV_STORAGE_ID});

export const kvStorage = {
setBool: (key: string, value: boolean) => mmkvStorage.set(key, value),
setString: (key: string, value: string) => mmkvStorage.set(key, value),
setNumber: (key: string, value: number) => mmkvStorage.set(key, value),
setObject: (key: string, value: object) =>
mmkvStorage.set(key, JSON.stringify(value)),
getBool: (key: string) => mmkvStorage.getBoolean(key),
getString: (key: string) => mmkvStorage.getString(key),
getNumber: (key: string) => mmkvStorage.getNumber(key),
getObject: <T extends object>(key: string) => {
const str = mmkvStorage.getString(key);
if (str === undefined) return str;
return JSON.parse(str) as T;
},
useBool: (key: string, defaultValue: boolean) => {
const [value, setValue] = useMMKVBoolean(key, mmkvStorage);
return [value ?? defaultValue, setValue] as const;
},
useString: (key: string, defaultValue: string) => {
const [value, setValue] = useMMKVString(key, mmkvStorage);
return [value ?? defaultValue, setValue] as const;
},
useNumber: (key: string, defaultValue: number) => {
const [value, setValue] = useMMKVNumber(key, mmkvStorage);
return [value ?? defaultValue, setValue] as const;
},
useObject: <T>(key: string, defaultValue: T) => {
const [value, setValue] = useMMKVObject<T>(key, mmkvStorage);
return [value ?? defaultValue, setValue] as const;
},
hasKey: mmkvStorage.contains,
remove: mmkvStorage.delete,
};
4 changes: 2 additions & 2 deletions dev-client/src/screens/WelcomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ import {ExternalLink} from 'terraso-mobile-client/components/links/ExternalLink'
import {InternalLink} from 'terraso-mobile-client/components/links/InternalLink';
import {Box, Text} from 'terraso-mobile-client/components/NativeBaseAdapters';
import {WELCOME_SCREEN_SEEN_KEY} from 'terraso-mobile-client/constants';
import {useKVStorage} from 'terraso-mobile-client/hooks/useStorage';
import {useNavigation} from 'terraso-mobile-client/navigation/hooks/useNavigation';
import {kvStorage} from 'terraso-mobile-client/persistence/kvStorage';

export const WelcomeScreen = () => {
const {t} = useTranslation();
const [, setWelcomeScreenAlreadySeen] = useKVStorage(
const [, setWelcomeScreenAlreadySeen] = kvStorage.useBool(
WELCOME_SCREEN_SEEN_KEY,
false,
);
Expand Down
2 changes: 1 addition & 1 deletion dev-client/src/store/persistence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('persistence middleware', () => {
loadPersistedReduxState,
} = require('terraso-mobile-client/store/persistence');

kvStorage.setMap('persisted-redux-state', {counter: 1});
kvStorage.setObject('persisted-redux-state', {counter: 1});
const store = configureStore({
middleware: [persistenceMiddleware],
reducer,
Expand Down
4 changes: 2 additions & 2 deletions dev-client/src/store/persistence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ export const persistenceMiddleware: Middleware = store => next => action => {
const result = next(action);
if (isFlagEnabled('FF_offline')) {
const newState = store.getState();
kvStorage.setMap(PERSISTED_STATE_KEY, newState);
kvStorage.setObject(PERSISTED_STATE_KEY, newState);
}
return result;
};

export const loadPersistedReduxState = () => {
if (isFlagEnabled('FF_offline')) {
return (
kvStorage.getMap<Partial<AppState>>(PERSISTED_STATE_KEY) ?? undefined
kvStorage.getObject<Partial<AppState>>(PERSISTED_STATE_KEY) ?? undefined
);
}
};
Expand Down

0 comments on commit 5df861e

Please sign in to comment.