Skip to content

Commit 7215cda

Browse files
feat: migrate react-native-mmkv-storage to react-native-mmkv (#6744)
Co-authored-by: OtavioStasiak <[email protected]> Co-authored-by: Diego Mello <[email protected]>
1 parent bddcf52 commit 7215cda

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2036
-695
lines changed

__mocks__/react-native-mmkv-storage.js

Lines changed: 0 additions & 25 deletions
This file was deleted.

__mocks__/react-native-mmkv.js

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Mock for react-native-mmkv
2+
const { useState, useEffect } = require('react');
3+
4+
// Shared storage between instances with the same id
5+
const storageInstances = new Map();
6+
7+
export const Mode = {
8+
SINGLE_PROCESS: 1,
9+
MULTI_PROCESS: 2
10+
};
11+
12+
export class MMKV {
13+
constructor(config = {}) {
14+
const { id = 'default', mode, path } = config;
15+
this.id = id;
16+
this.mode = mode;
17+
this.path = path;
18+
19+
// Share storage between instances with the same id
20+
if (!storageInstances.has(this.id)) {
21+
storageInstances.set(this.id, {
22+
storage: new Map(),
23+
listeners: []
24+
});
25+
}
26+
27+
const instance = storageInstances.get(this.id);
28+
this.storage = instance.storage;
29+
this.listeners = instance.listeners;
30+
}
31+
32+
set(key, value) {
33+
this.storage.set(key, value);
34+
this.notifyListeners(key);
35+
}
36+
37+
getString(key) {
38+
const value = this.storage.get(key);
39+
return typeof value === 'string' ? value : undefined;
40+
}
41+
42+
getNumber(key) {
43+
const value = this.storage.get(key);
44+
return typeof value === 'number' ? value : undefined;
45+
}
46+
47+
getBoolean(key) {
48+
const value = this.storage.get(key);
49+
return typeof value === 'boolean' ? value : undefined;
50+
}
51+
52+
contains(key) {
53+
return this.storage.has(key);
54+
}
55+
56+
delete(key) {
57+
const deleted = this.storage.delete(key);
58+
if (deleted) {
59+
this.notifyListeners(key);
60+
}
61+
return deleted;
62+
}
63+
64+
getAllKeys() {
65+
return Array.from(this.storage.keys());
66+
}
67+
68+
clearAll() {
69+
this.storage.clear();
70+
// Notify about clear (pass undefined to indicate clear all)
71+
this.notifyListeners(undefined);
72+
}
73+
74+
addOnValueChangedListener(callback) {
75+
this.listeners.push(callback);
76+
return {
77+
remove: () => {
78+
const index = this.listeners.indexOf(callback);
79+
if (index > -1) {
80+
this.listeners.splice(index, 1);
81+
}
82+
}
83+
};
84+
}
85+
86+
notifyListeners(key) {
87+
this.listeners.forEach((listener) => {
88+
try {
89+
listener(key);
90+
} catch (error) {
91+
console.error('Error in MMKV listener:', error);
92+
}
93+
});
94+
}
95+
}
96+
97+
// Export Configuration type for TypeScript
98+
export const Configuration = {};
99+
100+
// React hooks for MMKV
101+
export function useMMKVString(key, mmkvInstance) {
102+
const [value, setValue] = useState(() => mmkvInstance.getString(key));
103+
104+
useEffect(() => {
105+
const listener = mmkvInstance.addOnValueChangedListener((changedKey) => {
106+
if (changedKey === key || changedKey === undefined) {
107+
setValue(mmkvInstance.getString(key));
108+
}
109+
});
110+
return () => listener.remove();
111+
}, [key, mmkvInstance]);
112+
113+
const setStoredValue = (newValue) => {
114+
if (newValue === undefined) {
115+
mmkvInstance.delete(key);
116+
} else {
117+
mmkvInstance.set(key, newValue);
118+
}
119+
setValue(newValue);
120+
};
121+
122+
return [value, setStoredValue];
123+
}
124+
125+
export function useMMKVNumber(key, mmkvInstance) {
126+
const [value, setValue] = useState(() => mmkvInstance.getNumber(key));
127+
128+
useEffect(() => {
129+
const listener = mmkvInstance.addOnValueChangedListener((changedKey) => {
130+
if (changedKey === key || changedKey === undefined) {
131+
setValue(mmkvInstance.getNumber(key));
132+
}
133+
});
134+
return () => listener.remove();
135+
}, [key, mmkvInstance]);
136+
137+
const setStoredValue = (newValue) => {
138+
if (newValue === undefined) {
139+
mmkvInstance.delete(key);
140+
} else {
141+
mmkvInstance.set(key, newValue);
142+
}
143+
setValue(newValue);
144+
};
145+
146+
return [value, setStoredValue];
147+
}
148+
149+
export function useMMKVBoolean(key, mmkvInstance) {
150+
const [value, setValue] = useState(() => mmkvInstance.getBoolean(key));
151+
152+
useEffect(() => {
153+
const listener = mmkvInstance.addOnValueChangedListener((changedKey) => {
154+
if (changedKey === key || changedKey === undefined) {
155+
setValue(mmkvInstance.getBoolean(key));
156+
}
157+
});
158+
return () => listener.remove();
159+
}, [key, mmkvInstance]);
160+
161+
const setStoredValue = (newValue) => {
162+
if (newValue === undefined) {
163+
mmkvInstance.delete(key);
164+
} else {
165+
mmkvInstance.set(key, newValue);
166+
}
167+
setValue(newValue);
168+
};
169+
170+
return [value, setStoredValue];
171+
}
172+
173+
export function useMMKVObject(key, mmkvInstance) {
174+
const [value, setValue] = useState(() => {
175+
const stored = mmkvInstance.getString(key);
176+
return stored ? JSON.parse(stored) : undefined;
177+
});
178+
179+
useEffect(() => {
180+
const listener = mmkvInstance.addOnValueChangedListener((changedKey) => {
181+
if (changedKey === key || changedKey === undefined) {
182+
const stored = mmkvInstance.getString(key);
183+
setValue(stored ? JSON.parse(stored) : undefined);
184+
}
185+
});
186+
return () => listener.remove();
187+
}, [key, mmkvInstance]);
188+
189+
const setStoredValue = (newValue) => {
190+
if (newValue === undefined) {
191+
mmkvInstance.delete(key);
192+
} else {
193+
mmkvInstance.set(key, JSON.stringify(newValue));
194+
}
195+
setValue(newValue);
196+
};
197+
198+
return [value, setStoredValue];
199+
}

android/app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ dependencies {
151151
implementation "com.google.code.gson:gson:2.8.9"
152152
implementation "com.tencent:mmkv-static:1.2.10"
153153
implementation 'com.facebook.soloader:soloader:0.10.4'
154+
155+
// For SecureKeystore (EncryptedSharedPreferences)
156+
implementation 'androidx.security:security-crypto:1.1.0'
154157
}
155158

156159
apply plugin: 'com.google.gms.google-services'

android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import com.wix.reactnativenotifications.core.notification.IPushNotification
2626
import com.bugsnag.android.Bugsnag
2727
import expo.modules.ApplicationLifecycleDispatcher
2828
import chat.rocket.reactnative.networking.SSLPinningTurboPackage;
29+
import chat.rocket.reactnative.storage.MMKVKeyManager;
30+
import chat.rocket.reactnative.storage.SecureStoragePackage;
2931
import chat.rocket.reactnative.notification.CustomPushNotification;
3032

3133
open class MainApplication : Application(), ReactApplication, INotificationsApplication {
@@ -36,6 +38,7 @@ open class MainApplication : Application(), ReactApplication, INotificationsAppl
3638
PackageList(this).packages.apply {
3739
add(SSLPinningTurboPackage())
3840
add(WatermelonDBJSIPackage())
41+
add(SecureStoragePackage())
3942
}
4043

4144
override fun getJSMainModuleName(): String = "index"
@@ -53,6 +56,10 @@ open class MainApplication : Application(), ReactApplication, INotificationsAppl
5356
super.onCreate()
5457
SoLoader.init(this, OpenSourceMergedSoMapping)
5558
Bugsnag.start(this)
59+
60+
// Initialize MMKV encryption - reads existing key or generates new one
61+
// Must run before React Native starts to avoid race conditions
62+
MMKVKeyManager.initialize(this)
5663

5764
// Load the native entry point for the New Architecture
5865
load()

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ private void notificationDismiss(Notification.Builder notification, int notifica
652652

653653
private void notificationLoad(Ejson ejson, Callback callback) {
654654
LoadNotification loadNotification = new LoadNotification();
655-
loadNotification.load(reactApplicationContext, ejson, callback);
655+
loadNotification.load(ejson, callback);
656656
}
657657

658658
/**

0 commit comments

Comments
 (0)