react-native-mmkv 4.0.0-beta.0 → 4.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NitroMmkv.podspec +44 -0
- package/README.md +1 -3
- package/android/CMakeLists.txt +31 -30
- package/android/build.gradle +65 -20
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -5
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/mmkv/HybridMMKVPlatformContext.kt +19 -0
- package/android/src/main/java/com/margelo/nitro/mmkv/NitroMmkvPackage.java +33 -0
- package/app.plugin.js +1 -0
- package/cpp/HybridMMKV.cpp +184 -0
- package/cpp/HybridMMKV.hpp +47 -0
- package/cpp/HybridMMKVFactory.cpp +33 -0
- package/cpp/HybridMMKVFactory.hpp +24 -0
- package/cpp/{MmkvTypes.h → MMKVTypes.hpp} +1 -1
- package/cpp/MMKVValueChangedListenerRegistry.cpp +58 -0
- package/cpp/MMKVValueChangedListenerRegistry.hpp +43 -0
- package/cpp/{ManagedMMBuffer.h → ManagedMMBuffer.hpp} +13 -5
- package/ios/HybridMMKVPlatformContext.swift +43 -0
- package/lib/__tests__/hooks.test.d.ts +1 -0
- package/lib/__tests__/hooks.test.js +69 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.d.ts +2 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.js +25 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.mock.d.ts +2 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.mock.js +3 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.web.d.ts +2 -0
- package/lib/addMemoryWarningListener/addMemoryWarningListener.web.js +3 -0
- package/lib/createMMKV/createMMKV.d.ts +3 -0
- package/lib/createMMKV/createMMKV.js +40 -0
- package/lib/createMMKV/createMMKV.mock.d.ts +5 -0
- package/lib/createMMKV/createMMKV.mock.js +74 -0
- package/lib/createMMKV/createMMKV.web.d.ts +3 -0
- package/lib/createMMKV/createMMKV.web.js +117 -0
- package/lib/createMMKV/createMockMMKV.d.ts +5 -0
- package/lib/createMMKV/createMockMMKV.js +74 -0
- package/lib/createMMKV/getDefaultMMKVInstance.d.ts +2 -0
- package/lib/createMMKV/getDefaultMMKVInstance.js +8 -0
- package/lib/expo-plugin/withMMKV.cjs +26 -0
- package/lib/expo-plugin/withMMKV.d.cts +3 -0
- package/lib/expo-plugin/withMMKV.d.ts +3 -0
- package/lib/expo-plugin/withMMKV.js +17 -0
- package/lib/hooks/createMMKVHook.d.ts +2 -0
- package/lib/hooks/createMMKVHook.js +49 -0
- package/lib/hooks/useMMKV.d.ts +11 -0
- package/lib/hooks/useMMKV.js +23 -0
- package/lib/hooks/useMMKVBoolean.d.ts +11 -0
- package/lib/hooks/useMMKVBoolean.js +12 -0
- package/lib/hooks/useMMKVBuffer.d.ts +11 -0
- package/lib/hooks/useMMKVBuffer.js +12 -0
- package/lib/hooks/useMMKVKeys.d.ts +12 -0
- package/lib/hooks/useMMKVKeys.js +33 -0
- package/lib/hooks/useMMKVListener.d.ts +15 -0
- package/lib/hooks/useMMKVListener.js +26 -0
- package/lib/hooks/useMMKVNumber.d.ts +11 -0
- package/lib/hooks/useMMKVNumber.js +12 -0
- package/lib/hooks/useMMKVObject.d.ts +17 -0
- package/lib/hooks/useMMKVObject.js +38 -0
- package/lib/hooks/useMMKVString.d.ts +11 -0
- package/lib/hooks/useMMKVString.js +12 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +11 -0
- package/lib/isTest.d.ts +1 -0
- package/lib/isTest.js +7 -0
- package/lib/specs/MMKV.nitro.d.ts +94 -0
- package/lib/specs/MMKV.nitro.js +1 -0
- package/lib/{typescript/src/NativeMmkv.d.ts → specs/MMKVFactory.nitro.d.ts} +26 -33
- package/lib/specs/MMKVFactory.nitro.js +1 -0
- package/lib/specs/MMKVPlatformContext.nitro.d.ts +18 -0
- package/lib/specs/MMKVPlatformContext.nitro.js +1 -0
- package/lib/{typescript/src → web}/createTextEncoder.d.ts +0 -1
- package/lib/web/createTextEncoder.js +17 -0
- package/nitro.json +28 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroMmkv+autolinking.cmake +80 -0
- package/nitrogen/generated/android/NitroMmkv+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroMmkvOnLoad.cpp +54 -0
- package/nitrogen/generated/android/NitroMmkvOnLoad.hpp +25 -0
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.cpp +52 -0
- package/nitrogen/generated/android/c++/JHybridMMKVPlatformContextSpec.hpp +65 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/mmkv/HybridMMKVPlatformContextSpec.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/mmkv/NitroMmkvOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroMmkv+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Bridge.cpp +32 -0
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Bridge.hpp +77 -0
- package/nitrogen/generated/ios/NitroMmkv-Swift-Cxx-Umbrella.hpp +45 -0
- package/nitrogen/generated/ios/NitroMmkvAutolinking.mm +43 -0
- package/nitrogen/generated/ios/NitroMmkvAutolinking.swift +25 -0
- package/nitrogen/generated/ios/c++/HybridMMKVPlatformContextSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMMKVPlatformContextSpecSwift.hpp +82 -0
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec.swift +50 -0
- package/nitrogen/generated/ios/swift/HybridMMKVPlatformContextSpec_cxx.swift +141 -0
- package/nitrogen/generated/shared/c++/Configuration.hpp +86 -0
- package/nitrogen/generated/shared/c++/HybridMMKVFactorySpec.cpp +23 -0
- package/nitrogen/generated/shared/c++/HybridMMKVFactorySpec.hpp +69 -0
- package/nitrogen/generated/shared/c++/HybridMMKVPlatformContextSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridMMKVPlatformContextSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.cpp +34 -0
- package/nitrogen/generated/shared/c++/HybridMMKVSpec.hpp +83 -0
- package/nitrogen/generated/shared/c++/Listener.hpp +67 -0
- package/nitrogen/generated/shared/c++/Mode.hpp +76 -0
- package/package.json +78 -125
- package/react-native.config.js +2 -15
- package/src/__tests__/hooks.test.tsx +39 -34
- package/src/addMemoryWarningListener/addMemoryWarningListener.mock.ts +5 -0
- package/src/{MemoryWarningListener.ts → addMemoryWarningListener/addMemoryWarningListener.ts} +12 -12
- package/src/addMemoryWarningListener/addMemoryWarningListener.web.ts +5 -0
- package/src/createMMKV/createMMKV.ts +51 -0
- package/src/{createMMKV.web.ts → createMMKV/createMMKV.web.ts} +58 -46
- package/src/createMMKV/createMockMMKV.ts +78 -0
- package/src/createMMKV/getDefaultMMKVInstance.ts +10 -0
- package/src/expo-plugin/withMMKV.cts +31 -0
- package/src/hooks/createMMKVHook.ts +66 -0
- package/src/hooks/useMMKV.ts +45 -0
- package/src/hooks/useMMKVBoolean.ts +15 -0
- package/src/hooks/useMMKVBuffer.ts +15 -0
- package/src/hooks/useMMKVKeys.ts +36 -0
- package/src/hooks/useMMKVListener.ts +33 -0
- package/src/hooks/useMMKVNumber.ts +15 -0
- package/src/hooks/useMMKVObject.ts +53 -0
- package/src/hooks/useMMKVString.ts +15 -0
- package/src/index.ts +15 -3
- package/src/{PlatformChecker.ts → isTest.ts} +2 -2
- package/src/specs/MMKV.nitro.ts +93 -0
- package/src/specs/MMKVFactory.nitro.ts +87 -0
- package/src/specs/MMKVPlatformContext.nitro.ts +17 -0
- package/src/{createTextEncoder.ts → web/createTextEncoder.ts} +7 -7
- package/android/src/main/cpp/AndroidLogger.cpp +0 -16
- package/android/src/main/java/com/mrousavy/mmkv/MmkvPackage.java +0 -44
- package/android/src/main/java/com/mrousavy/mmkv/MmkvPlatformContextModule.java +0 -26
- package/cpp/MmkvHostObject.cpp +0 -360
- package/cpp/MmkvHostObject.h +0 -31
- package/cpp/MmkvLogger.h +0 -35
- package/cpp/NativeMmkvModule.cpp +0 -43
- package/cpp/NativeMmkvModule.h +0 -31
- package/ios/AppleLogger.mm +0 -16
- package/ios/MmkvOnLoad.mm +0 -25
- package/ios/MmkvPlatformContext.h +0 -19
- package/ios/MmkvPlatformContextModule.mm +0 -55
- package/lib/commonjs/MMKV.js +0 -124
- package/lib/commonjs/MMKV.js.map +0 -1
- package/lib/commonjs/MemoryWarningListener.js +0 -31
- package/lib/commonjs/MemoryWarningListener.js.map +0 -1
- package/lib/commonjs/MemoryWarningListener.web.js +0 -11
- package/lib/commonjs/MemoryWarningListener.web.js.map +0 -1
- package/lib/commonjs/ModuleNotFoundError.js +0 -75
- package/lib/commonjs/ModuleNotFoundError.js.map +0 -1
- package/lib/commonjs/NativeMmkv.js +0 -47
- package/lib/commonjs/NativeMmkv.js.map +0 -1
- package/lib/commonjs/NativeMmkvPlatformContext.js +0 -22
- package/lib/commonjs/NativeMmkvPlatformContext.js.map +0 -1
- package/lib/commonjs/PlatformChecker.js +0 -14
- package/lib/commonjs/PlatformChecker.js.map +0 -1
- package/lib/commonjs/Types.js +0 -26
- package/lib/commonjs/Types.js.map +0 -1
- package/lib/commonjs/createMMKV.js +0 -43
- package/lib/commonjs/createMMKV.js.map +0 -1
- package/lib/commonjs/createMMKV.mock.js +0 -43
- package/lib/commonjs/createMMKV.mock.js.map +0 -1
- package/lib/commonjs/createMMKV.web.js +0 -110
- package/lib/commonjs/createMMKV.web.js.map +0 -1
- package/lib/commonjs/createTextEncoder.js +0 -23
- package/lib/commonjs/createTextEncoder.js.map +0 -1
- package/lib/commonjs/hooks.js +0 -198
- package/lib/commonjs/hooks.js.map +0 -1
- package/lib/commonjs/index.js +0 -40
- package/lib/commonjs/index.js.map +0 -1
- package/lib/commonjs/package.json +0 -1
- package/lib/module/MMKV.js +0 -119
- package/lib/module/MMKV.js.map +0 -1
- package/lib/module/MemoryWarningListener.js +0 -27
- package/lib/module/MemoryWarningListener.js.map +0 -1
- package/lib/module/MemoryWarningListener.web.js +0 -6
- package/lib/module/MemoryWarningListener.web.js.map +0 -1
- package/lib/module/ModuleNotFoundError.js +0 -70
- package/lib/module/ModuleNotFoundError.js.map +0 -1
- package/lib/module/NativeMmkv.js +0 -45
- package/lib/module/NativeMmkv.js.map +0 -1
- package/lib/module/NativeMmkvPlatformContext.js +0 -18
- package/lib/module/NativeMmkvPlatformContext.js.map +0 -1
- package/lib/module/PlatformChecker.js +0 -10
- package/lib/module/PlatformChecker.js.map +0 -1
- package/lib/module/Types.js +0 -25
- package/lib/module/Types.js.map +0 -1
- package/lib/module/createMMKV.js +0 -38
- package/lib/module/createMMKV.js.map +0 -1
- package/lib/module/createMMKV.mock.js +0 -38
- package/lib/module/createMMKV.mock.js.map +0 -1
- package/lib/module/createMMKV.web.js +0 -105
- package/lib/module/createMMKV.web.js.map +0 -1
- package/lib/module/createTextEncoder.js +0 -19
- package/lib/module/createTextEncoder.js.map +0 -1
- package/lib/module/hooks.js +0 -189
- package/lib/module/hooks.js.map +0 -1
- package/lib/module/index.js +0 -6
- package/lib/module/index.js.map +0 -1
- package/lib/module/package.json +0 -1
- package/lib/typescript/src/MMKV.d.ts +0 -34
- package/lib/typescript/src/MMKV.d.ts.map +0 -1
- package/lib/typescript/src/MemoryWarningListener.d.ts +0 -3
- package/lib/typescript/src/MemoryWarningListener.d.ts.map +0 -1
- package/lib/typescript/src/MemoryWarningListener.web.d.ts +0 -3
- package/lib/typescript/src/MemoryWarningListener.web.d.ts.map +0 -1
- package/lib/typescript/src/ModuleNotFoundError.d.ts +0 -7
- package/lib/typescript/src/ModuleNotFoundError.d.ts.map +0 -1
- package/lib/typescript/src/NativeMmkv.d.ts.map +0 -1
- package/lib/typescript/src/NativeMmkvPlatformContext.d.ts +0 -20
- package/lib/typescript/src/NativeMmkvPlatformContext.d.ts.map +0 -1
- package/lib/typescript/src/PlatformChecker.d.ts +0 -2
- package/lib/typescript/src/PlatformChecker.d.ts.map +0 -1
- package/lib/typescript/src/Types.d.ts +0 -172
- package/lib/typescript/src/Types.d.ts.map +0 -1
- package/lib/typescript/src/__tests__/hooks.test.d.ts +0 -2
- package/lib/typescript/src/__tests__/hooks.test.d.ts.map +0 -1
- package/lib/typescript/src/createMMKV.d.ts +0 -3
- package/lib/typescript/src/createMMKV.d.ts.map +0 -1
- package/lib/typescript/src/createMMKV.mock.d.ts +0 -3
- package/lib/typescript/src/createMMKV.mock.d.ts.map +0 -1
- package/lib/typescript/src/createMMKV.web.d.ts +0 -3
- package/lib/typescript/src/createMMKV.web.d.ts.map +0 -1
- package/lib/typescript/src/createTextEncoder.d.ts.map +0 -1
- package/lib/typescript/src/hooks.d.ts +0 -86
- package/lib/typescript/src/hooks.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts +0 -4
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/react-native-mmkv.podspec +0 -32
- package/src/MMKV.ts +0 -142
- package/src/MemoryWarningListener.web.ts +0 -5
- package/src/ModuleNotFoundError.ts +0 -95
- package/src/NativeMmkv.ts +0 -118
- package/src/NativeMmkvPlatformContext.ts +0 -38
- package/src/Types.ts +0 -178
- package/src/createMMKV.mock.ts +0 -38
- package/src/createMMKV.ts +0 -42
- package/src/hooks.ts +0 -247
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MMKVValueChangedListenerRegistry.cpp
|
|
3
|
+
// react-native-mmkv
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 21.08.2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "MMKVValueChangedListenerRegistry.hpp"
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro::mmkv {
|
|
11
|
+
|
|
12
|
+
// static members
|
|
13
|
+
std::atomic<ListenerID> MMKVValueChangedListenerRegistry::_listenersCounter = 0;
|
|
14
|
+
std::unordered_map<MMKVID, std::vector<ListenerSubscription>> MMKVValueChangedListenerRegistry::_listeners;
|
|
15
|
+
|
|
16
|
+
ListenerID MMKVValueChangedListenerRegistry::addListener(const std::string& mmkvID,
|
|
17
|
+
const std::function<void(const std::string& /* key */)>& callback) {
|
|
18
|
+
// 1. Get (or create) the listeners array for these MMKV instances
|
|
19
|
+
auto& listeners = _listeners[mmkvID];
|
|
20
|
+
// 2. Get (and increment) the listener ID counter
|
|
21
|
+
auto id = _listenersCounter.fetch_add(1);
|
|
22
|
+
// 3. Add the listener to our array
|
|
23
|
+
listeners.push_back(ListenerSubscription{
|
|
24
|
+
.id = id,
|
|
25
|
+
.callback = callback,
|
|
26
|
+
});
|
|
27
|
+
// 4. Return the ID used to unsubscribe later on
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void MMKVValueChangedListenerRegistry::removeListener(const std::string& mmkvID, ListenerID id) {
|
|
32
|
+
// 1. Get the listeners array for these MMKV instances
|
|
33
|
+
auto entry = _listeners.find(mmkvID);
|
|
34
|
+
if (entry == _listeners.end()) {
|
|
35
|
+
// There's no more listeners for this instance anyways.
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// 2. Remove all listeners where the ID matches. Should only be one.
|
|
39
|
+
auto& listeners = entry->second;
|
|
40
|
+
listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [id](const ListenerSubscription& e) { return e.id == id; }),
|
|
41
|
+
listeners.end());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
void MMKVValueChangedListenerRegistry::notifyOnValueChanged(const std::string& mmkvID, const std::string& key) {
|
|
45
|
+
// 1. Get all listeners for the specific MMKV ID
|
|
46
|
+
auto entry = _listeners.find(mmkvID);
|
|
47
|
+
if (entry == _listeners.end()) {
|
|
48
|
+
// There are no listeners. Return
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// 2. Call each listener.
|
|
52
|
+
auto& listeners = entry->second;
|
|
53
|
+
for (const auto& listener : listeners) {
|
|
54
|
+
listener.callback(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
} // namespace margelo::nitro::mmkv
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MMKVValueChangedListenerRegistry.hpp
|
|
3
|
+
// react-native-mmkv
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 21.08.2025.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "MMKVTypes.hpp"
|
|
9
|
+
#include <atomic>
|
|
10
|
+
#include <unordered_map>
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro::mmkv {
|
|
13
|
+
|
|
14
|
+
using ListenerID = size_t;
|
|
15
|
+
using MMKVID = std::string;
|
|
16
|
+
|
|
17
|
+
struct ListenerSubscription {
|
|
18
|
+
ListenerID id;
|
|
19
|
+
std::function<void(const std::string& /* key */)> callback;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Listeners are tracked across instances - so we need an extra static class for
|
|
24
|
+
* the registry.
|
|
25
|
+
*/
|
|
26
|
+
class MMKVValueChangedListenerRegistry final {
|
|
27
|
+
public:
|
|
28
|
+
MMKVValueChangedListenerRegistry() = delete;
|
|
29
|
+
~MMKVValueChangedListenerRegistry() = delete;
|
|
30
|
+
|
|
31
|
+
public:
|
|
32
|
+
static ListenerID addListener(const std::string& mmkvID, const std::function<void(const std::string& /* key */)>& callback);
|
|
33
|
+
static void removeListener(const std::string& mmkvID, ListenerID id);
|
|
34
|
+
|
|
35
|
+
public:
|
|
36
|
+
static void notifyOnValueChanged(const std::string& mmkvID, const std::string& key);
|
|
37
|
+
|
|
38
|
+
private:
|
|
39
|
+
static std::atomic<ListenerID> _listenersCounter;
|
|
40
|
+
static std::unordered_map<MMKVID, std::vector<ListenerSubscription>> _listeners;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
} // namespace margelo::nitro::mmkv
|
|
@@ -7,18 +7,24 @@
|
|
|
7
7
|
|
|
8
8
|
#pragma once
|
|
9
9
|
|
|
10
|
-
#include "
|
|
11
|
-
#include <
|
|
10
|
+
#include "MMKVTypes.hpp"
|
|
11
|
+
#include <NitroModules/ArrayBuffer.hpp>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
namespace margelo::nitro::mmkv {
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
|
|
16
|
+
An ArrayBuffer subclass that manages MMBuffer memory (by ownership).
|
|
17
17
|
*/
|
|
18
|
-
class ManagedMMBuffer : public
|
|
18
|
+
class ManagedMMBuffer : public ArrayBuffer {
|
|
19
19
|
public:
|
|
20
20
|
explicit ManagedMMBuffer(MMBuffer&& buffer) : _buffer(std::move(buffer)) {}
|
|
21
21
|
|
|
22
|
+
public:
|
|
23
|
+
bool isOwner() const noexcept override {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public:
|
|
22
28
|
uint8_t* data() override {
|
|
23
29
|
return static_cast<uint8_t*>(_buffer.getPtr());
|
|
24
30
|
}
|
|
@@ -30,3 +36,5 @@ public:
|
|
|
30
36
|
private:
|
|
31
37
|
MMBuffer _buffer;
|
|
32
38
|
};
|
|
39
|
+
|
|
40
|
+
} // namespace margelo::nitro::mmkv
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridMMKVPlatformContext.h
|
|
3
|
+
// react-native-mmkv
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 25.03.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import NitroModules
|
|
10
|
+
|
|
11
|
+
class HybridMMKVPlatformContext: HybridMMKVPlatformContextSpec {
|
|
12
|
+
static var directory: FileManager.SearchPathDirectory {
|
|
13
|
+
#if os(tvOS)
|
|
14
|
+
return .cachesDirectory
|
|
15
|
+
#else
|
|
16
|
+
return .documentDirectory
|
|
17
|
+
#endif
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
func getBaseDirectory() throws -> String {
|
|
21
|
+
// Get user documents directory
|
|
22
|
+
let paths = FileManager.default.urls(for: Self.directory, in: .userDomainMask)
|
|
23
|
+
guard let documentsPath = paths.first else {
|
|
24
|
+
throw RuntimeError.error(withMessage: "Cannot find base-path to store MMKV files!")
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// append /mmkv to it
|
|
28
|
+
let basePath = documentsPath.appendingPathComponent("mmkv", conformingTo: .directory)
|
|
29
|
+
return basePath.path
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func getAppGroupDirectory() throws -> String? {
|
|
33
|
+
// Read `AppGroupIdentifier` from `Info.plist`
|
|
34
|
+
guard let appGroupID = Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String else {
|
|
35
|
+
return nil
|
|
36
|
+
}
|
|
37
|
+
// Get the URL for the AppGroup
|
|
38
|
+
guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID) else {
|
|
39
|
+
throw RuntimeError.error(withMessage: "Container for AppGroup \"\(appGroupID)\" not accessible")
|
|
40
|
+
}
|
|
41
|
+
return url.path
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Text } from 'react-native';
|
|
3
|
+
import { act, fireEvent, render, renderHook, screen, cleanup, } from '@testing-library/react-native';
|
|
4
|
+
import { createMMKV, useMMKVNumber, useMMKVString } from '..';
|
|
5
|
+
const mmkv = createMMKV();
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
mmkv.clearAll();
|
|
8
|
+
mmkv.trim();
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanup();
|
|
12
|
+
});
|
|
13
|
+
test('hooks update when the value is changed directly through the instance', () => {
|
|
14
|
+
const { result } = renderHook(() => useMMKVString('string-key', mmkv));
|
|
15
|
+
expect(result.current[0]).toBeUndefined();
|
|
16
|
+
// First, make a "normal" change
|
|
17
|
+
act(() => {
|
|
18
|
+
result.current[1]('value 1');
|
|
19
|
+
});
|
|
20
|
+
expect(result.current[0]).toStrictEqual('value 1');
|
|
21
|
+
// Now, make the change directly through the instance.
|
|
22
|
+
act(() => {
|
|
23
|
+
mmkv.set('string-key', 'value 2');
|
|
24
|
+
});
|
|
25
|
+
expect(result.current[0]).toStrictEqual('value 2');
|
|
26
|
+
});
|
|
27
|
+
test('functional updates to hooks', () => {
|
|
28
|
+
const Component = () => {
|
|
29
|
+
const [state, setState] = React.useState(0);
|
|
30
|
+
const [value, setValue] = useMMKVNumber('number-key', mmkv);
|
|
31
|
+
return (React.createElement(React.Fragment, null,
|
|
32
|
+
React.createElement(Button, { testID: "button", title: "Double Increment Me", onPress: () => {
|
|
33
|
+
// Increment the state value twice, using the function form of useState.
|
|
34
|
+
setState((current) => current + 1);
|
|
35
|
+
setState((current) => current + 1);
|
|
36
|
+
// Increment the MMKV value twice, using the same function form.
|
|
37
|
+
setValue((current) => (current ?? 0) + 1);
|
|
38
|
+
setValue((current) => (current ?? 0) + 1);
|
|
39
|
+
} }),
|
|
40
|
+
React.createElement(Text, { testID: "state-value" },
|
|
41
|
+
"State: ",
|
|
42
|
+
state.toString()),
|
|
43
|
+
React.createElement(Text, { testID: "mmkv-value" },
|
|
44
|
+
"MMKV: ",
|
|
45
|
+
(value ?? 0).toString())));
|
|
46
|
+
};
|
|
47
|
+
render(React.createElement(Component, null));
|
|
48
|
+
const button = screen.getByTestId('button');
|
|
49
|
+
// Why these assertions:
|
|
50
|
+
// https://github.com/mrousavy/react-native-mmkv/issues/599
|
|
51
|
+
fireEvent.press(button);
|
|
52
|
+
expect(screen.getByTestId('state-value').children).toStrictEqual([
|
|
53
|
+
'State: ',
|
|
54
|
+
'2',
|
|
55
|
+
]);
|
|
56
|
+
expect(screen.getByTestId('mmkv-value').children).toStrictEqual([
|
|
57
|
+
'MMKV: ',
|
|
58
|
+
'2',
|
|
59
|
+
]);
|
|
60
|
+
fireEvent.press(button);
|
|
61
|
+
expect(screen.getByTestId('state-value').children).toStrictEqual([
|
|
62
|
+
'State: ',
|
|
63
|
+
'4',
|
|
64
|
+
]);
|
|
65
|
+
expect(screen.getByTestId('mmkv-value').children).toStrictEqual([
|
|
66
|
+
'MMKV: ',
|
|
67
|
+
'4',
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AppState } from 'react-native';
|
|
2
|
+
export function addMemoryWarningListener(mmkv) {
|
|
3
|
+
if (global.WeakRef != null && global.FinalizationRegistry != null) {
|
|
4
|
+
// 1. Weakify MMKV so we can safely use it inside the memoryWarning event listener
|
|
5
|
+
const weakMmkv = new WeakRef(mmkv);
|
|
6
|
+
const listener = AppState.addEventListener('memoryWarning', () => {
|
|
7
|
+
// 0. Everytime we receive a memoryWarning, we try to trim the MMKV instance (if it is still valid)
|
|
8
|
+
weakMmkv.deref()?.trim();
|
|
9
|
+
});
|
|
10
|
+
// 2. Add a listener to when the MMKV instance is deleted
|
|
11
|
+
const finalization = new FinalizationRegistry((l) => {
|
|
12
|
+
// 3. When MMKV is deleted, this listener will be called with the memoryWarning listener.
|
|
13
|
+
l.remove();
|
|
14
|
+
});
|
|
15
|
+
// 2.1. Bind the listener to the actual MMKV instance.
|
|
16
|
+
finalization.register(mmkv, listener);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// WeakRef/FinalizationRegistry is not implemented in this engine.
|
|
20
|
+
// Just add the listener, even if it retains MMKV strong forever.
|
|
21
|
+
AppState.addEventListener('memoryWarning', () => {
|
|
22
|
+
mmkv.trim();
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NitroModules } from 'react-native-nitro-modules';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import { addMemoryWarningListener } from '../addMemoryWarningListener/addMemoryWarningListener';
|
|
4
|
+
import { isTest } from '../isTest';
|
|
5
|
+
import { createMockMMKV } from './createMockMMKV';
|
|
6
|
+
let factory;
|
|
7
|
+
let platformContext;
|
|
8
|
+
export function createMMKV(configuration) {
|
|
9
|
+
if (isTest()) {
|
|
10
|
+
// In a test environment, we mock the MMKV instance.
|
|
11
|
+
return createMockMMKV();
|
|
12
|
+
}
|
|
13
|
+
if (platformContext == null) {
|
|
14
|
+
// Lazy-init the platform-context HybridObject
|
|
15
|
+
platformContext = NitroModules.createHybridObject('MMKVPlatformContext');
|
|
16
|
+
}
|
|
17
|
+
if (factory == null) {
|
|
18
|
+
// Lazy-init the factory HybridObject
|
|
19
|
+
factory = NitroModules.createHybridObject('MMKVFactory');
|
|
20
|
+
const baseDirectory = platformContext.getBaseDirectory();
|
|
21
|
+
factory.initializeMMKV(baseDirectory);
|
|
22
|
+
}
|
|
23
|
+
// Pre-parse the config
|
|
24
|
+
let config = configuration ?? { id: factory.defaultMMKVInstanceId };
|
|
25
|
+
if (Platform.OS === 'ios') {
|
|
26
|
+
if (config.path == null) {
|
|
27
|
+
// If the user set an App Group directory in Info.plist, let's use
|
|
28
|
+
// the App Group as a MMKV path:
|
|
29
|
+
const appGroupDirectory = platformContext.getAppGroupDirectory();
|
|
30
|
+
if (appGroupDirectory != null) {
|
|
31
|
+
config.path = appGroupDirectory;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Creates the C++ MMKV HybridObject
|
|
36
|
+
const mmkv = factory.createMMKV(config);
|
|
37
|
+
// Add a hook that trims the storage when we get a memory warning
|
|
38
|
+
addMemoryWarningListener(mmkv);
|
|
39
|
+
return mmkv;
|
|
40
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock MMKV instance when used in a Jest/Test environment.
|
|
3
|
+
*/
|
|
4
|
+
export function createMockMMKV() {
|
|
5
|
+
const storage = new Map();
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
const notifyListeners = (key) => {
|
|
8
|
+
listeners.forEach((listener) => {
|
|
9
|
+
listener(key);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
clearAll: () => {
|
|
14
|
+
const keysBefore = storage.keys();
|
|
15
|
+
storage.clear();
|
|
16
|
+
// Notify all listeners for all keys that were cleared
|
|
17
|
+
for (const key of keysBefore) {
|
|
18
|
+
notifyListeners(key);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
remove: (key) => {
|
|
22
|
+
const deleted = storage.delete(key);
|
|
23
|
+
if (deleted) {
|
|
24
|
+
notifyListeners(key);
|
|
25
|
+
}
|
|
26
|
+
return deleted;
|
|
27
|
+
},
|
|
28
|
+
set: (key, value) => {
|
|
29
|
+
storage.set(key, value);
|
|
30
|
+
notifyListeners(key);
|
|
31
|
+
},
|
|
32
|
+
getString: (key) => {
|
|
33
|
+
const result = storage.get(key);
|
|
34
|
+
return typeof result === 'string' ? result : undefined;
|
|
35
|
+
},
|
|
36
|
+
getNumber: (key) => {
|
|
37
|
+
const result = storage.get(key);
|
|
38
|
+
return typeof result === 'number' ? result : undefined;
|
|
39
|
+
},
|
|
40
|
+
getBoolean: (key) => {
|
|
41
|
+
const result = storage.get(key);
|
|
42
|
+
return typeof result === 'boolean' ? result : undefined;
|
|
43
|
+
},
|
|
44
|
+
getBuffer: (key) => {
|
|
45
|
+
const result = storage.get(key);
|
|
46
|
+
return result instanceof ArrayBuffer ? result : undefined;
|
|
47
|
+
},
|
|
48
|
+
getAllKeys: () => Array.from(storage.keys()),
|
|
49
|
+
contains: (key) => storage.has(key),
|
|
50
|
+
recrypt: () => {
|
|
51
|
+
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
52
|
+
},
|
|
53
|
+
get size() {
|
|
54
|
+
return storage.size;
|
|
55
|
+
},
|
|
56
|
+
isReadOnly: false,
|
|
57
|
+
trim: () => {
|
|
58
|
+
// no-op
|
|
59
|
+
},
|
|
60
|
+
name: 'MMKV',
|
|
61
|
+
dispose: () => { },
|
|
62
|
+
equals: () => {
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
65
|
+
addOnValueChangedListener: (listener) => {
|
|
66
|
+
listeners.add(listener);
|
|
67
|
+
return {
|
|
68
|
+
remove: () => {
|
|
69
|
+
listeners.delete(listener);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createTextEncoder } from '../web/createTextEncoder';
|
|
2
|
+
const canUseDOM = typeof window !== 'undefined' && window.document?.createElement != null;
|
|
3
|
+
const hasAccessToLocalStorage = () => {
|
|
4
|
+
try {
|
|
5
|
+
// throws ACCESS_DENIED error
|
|
6
|
+
window.localStorage;
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const KEY_WILDCARD = '\\';
|
|
14
|
+
const inMemoryStorage = new Map();
|
|
15
|
+
export function createMMKV(config = { id: 'mmkv.default' }) {
|
|
16
|
+
if (config.encryptionKey != null) {
|
|
17
|
+
throw new Error("MMKV: 'encryptionKey' is not supported on Web!");
|
|
18
|
+
}
|
|
19
|
+
if (config.path != null) {
|
|
20
|
+
throw new Error("MMKV: 'path' is not supported on Web!");
|
|
21
|
+
}
|
|
22
|
+
// canUseDOM check prevents spam in Node server environments, such as Next.js server side props.
|
|
23
|
+
if (!hasAccessToLocalStorage() && canUseDOM) {
|
|
24
|
+
console.warn('MMKV: LocalStorage has been disabled. Your experience will be limited to in-memory storage!');
|
|
25
|
+
}
|
|
26
|
+
const storage = () => {
|
|
27
|
+
if (!canUseDOM) {
|
|
28
|
+
throw new Error('Tried to access storage on the server. Did you forget to call this in useEffect?');
|
|
29
|
+
}
|
|
30
|
+
if (!hasAccessToLocalStorage()) {
|
|
31
|
+
return {
|
|
32
|
+
getItem: (key) => inMemoryStorage.get(key) ?? null,
|
|
33
|
+
setItem: (key, value) => inMemoryStorage.set(key, value),
|
|
34
|
+
removeItem: (key) => inMemoryStorage.delete(key),
|
|
35
|
+
clear: () => inMemoryStorage.clear(),
|
|
36
|
+
length: inMemoryStorage.size,
|
|
37
|
+
key: (index) => Object.keys(inMemoryStorage).at(index) ?? null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const domStorage = global?.localStorage ?? window?.localStorage ?? localStorage;
|
|
41
|
+
if (domStorage == null) {
|
|
42
|
+
throw new Error(`Could not find 'localStorage' instance!`);
|
|
43
|
+
}
|
|
44
|
+
return domStorage;
|
|
45
|
+
};
|
|
46
|
+
const textEncoder = createTextEncoder();
|
|
47
|
+
const listeners = new Set();
|
|
48
|
+
if (config.id.includes(KEY_WILDCARD)) {
|
|
49
|
+
throw new Error('MMKV: `id` cannot contain the backslash character (`\\`)!');
|
|
50
|
+
}
|
|
51
|
+
const keyPrefix = `${config.id}${KEY_WILDCARD}`; // mmkv.default\\
|
|
52
|
+
const prefixedKey = (key) => {
|
|
53
|
+
if (key.includes('\\')) {
|
|
54
|
+
throw new Error('MMKV: `key` cannot contain the backslash character (`\\`)!');
|
|
55
|
+
}
|
|
56
|
+
return `${keyPrefix}${key}`;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
clearAll: () => {
|
|
60
|
+
const keys = Object.keys(storage());
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
if (key.startsWith(keyPrefix)) {
|
|
63
|
+
storage().removeItem(key);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
remove: (key) => storage().removeItem(prefixedKey(key)) ?? false,
|
|
68
|
+
set: (key, value) => {
|
|
69
|
+
storage().setItem(prefixedKey(key), value.toString());
|
|
70
|
+
},
|
|
71
|
+
getString: (key) => storage().getItem(prefixedKey(key)) ?? undefined,
|
|
72
|
+
getNumber: (key) => {
|
|
73
|
+
const value = storage().getItem(prefixedKey(key));
|
|
74
|
+
if (value == null)
|
|
75
|
+
return undefined;
|
|
76
|
+
return Number(value);
|
|
77
|
+
},
|
|
78
|
+
getBoolean: (key) => {
|
|
79
|
+
const value = storage().getItem(prefixedKey(key));
|
|
80
|
+
if (value == null)
|
|
81
|
+
return undefined;
|
|
82
|
+
return value === 'true';
|
|
83
|
+
},
|
|
84
|
+
getBuffer: (key) => {
|
|
85
|
+
const value = storage().getItem(prefixedKey(key));
|
|
86
|
+
if (value == null)
|
|
87
|
+
return undefined;
|
|
88
|
+
return textEncoder.encode(value).buffer;
|
|
89
|
+
},
|
|
90
|
+
getAllKeys: () => {
|
|
91
|
+
const keys = Object.keys(storage());
|
|
92
|
+
return keys
|
|
93
|
+
.filter((key) => key.startsWith(keyPrefix))
|
|
94
|
+
.map((key) => key.slice(keyPrefix.length));
|
|
95
|
+
},
|
|
96
|
+
contains: (key) => storage().getItem(prefixedKey(key)) != null,
|
|
97
|
+
recrypt: () => {
|
|
98
|
+
throw new Error('`recrypt(..)` is not supported on Web!');
|
|
99
|
+
},
|
|
100
|
+
size: 0,
|
|
101
|
+
isReadOnly: false,
|
|
102
|
+
trim: () => {
|
|
103
|
+
// no-op
|
|
104
|
+
},
|
|
105
|
+
dispose: () => { },
|
|
106
|
+
equals: () => false,
|
|
107
|
+
name: 'MMKV',
|
|
108
|
+
addOnValueChangedListener: (listener) => {
|
|
109
|
+
listeners.add(listener);
|
|
110
|
+
return {
|
|
111
|
+
remove: () => {
|
|
112
|
+
listeners.delete(listener);
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock MMKV instance when used in a Jest/Test environment.
|
|
3
|
+
*/
|
|
4
|
+
export function createMockMMKV() {
|
|
5
|
+
const storage = new Map();
|
|
6
|
+
const listeners = new Set();
|
|
7
|
+
const notifyListeners = (key) => {
|
|
8
|
+
listeners.forEach((listener) => {
|
|
9
|
+
listener(key);
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
clearAll: () => {
|
|
14
|
+
const keysBefore = storage.keys();
|
|
15
|
+
storage.clear();
|
|
16
|
+
// Notify all listeners for all keys that were cleared
|
|
17
|
+
for (const key of keysBefore) {
|
|
18
|
+
notifyListeners(key);
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
remove: (key) => {
|
|
22
|
+
const deleted = storage.delete(key);
|
|
23
|
+
if (deleted) {
|
|
24
|
+
notifyListeners(key);
|
|
25
|
+
}
|
|
26
|
+
return deleted;
|
|
27
|
+
},
|
|
28
|
+
set: (key, value) => {
|
|
29
|
+
storage.set(key, value);
|
|
30
|
+
notifyListeners(key);
|
|
31
|
+
},
|
|
32
|
+
getString: (key) => {
|
|
33
|
+
const result = storage.get(key);
|
|
34
|
+
return typeof result === 'string' ? result : undefined;
|
|
35
|
+
},
|
|
36
|
+
getNumber: (key) => {
|
|
37
|
+
const result = storage.get(key);
|
|
38
|
+
return typeof result === 'number' ? result : undefined;
|
|
39
|
+
},
|
|
40
|
+
getBoolean: (key) => {
|
|
41
|
+
const result = storage.get(key);
|
|
42
|
+
return typeof result === 'boolean' ? result : undefined;
|
|
43
|
+
},
|
|
44
|
+
getBuffer: (key) => {
|
|
45
|
+
const result = storage.get(key);
|
|
46
|
+
return result instanceof ArrayBuffer ? result : undefined;
|
|
47
|
+
},
|
|
48
|
+
getAllKeys: () => Array.from(storage.keys()),
|
|
49
|
+
contains: (key) => storage.has(key),
|
|
50
|
+
recrypt: () => {
|
|
51
|
+
console.warn('Encryption is not supported in mocked MMKV instances!');
|
|
52
|
+
},
|
|
53
|
+
get size() {
|
|
54
|
+
return storage.size;
|
|
55
|
+
},
|
|
56
|
+
isReadOnly: false,
|
|
57
|
+
trim: () => {
|
|
58
|
+
// no-op
|
|
59
|
+
},
|
|
60
|
+
name: 'MMKV',
|
|
61
|
+
dispose: () => { },
|
|
62
|
+
equals: () => {
|
|
63
|
+
return false;
|
|
64
|
+
},
|
|
65
|
+
addOnValueChangedListener: (listener) => {
|
|
66
|
+
listeners.add(listener);
|
|
67
|
+
return {
|
|
68
|
+
remove: () => {
|
|
69
|
+
listeners.delete(listener);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|