react-native-mmkv 4.0.0-beta.6 → 4.0.0-beta.8

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.
@@ -12,7 +12,7 @@ class HybridMMKVPlatformContext: HybridMMKVPlatformContextSpec() {
12
12
  return context.filesDir.absolutePath + "/mmkv";
13
13
  }
14
14
 
15
- override fun getAppGroupDirectory(): String {
15
+ override fun getAppGroupDirectory(): String? {
16
16
  // AppGroups do not exist on Android. It's iOS only.
17
17
  throw Error("getAppGroupDirectory() is not supported on Android! It's iOS only.")
18
18
  }
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./lib/commonjs/expo-plugin/withMMKV')
@@ -5,6 +5,7 @@
5
5
  // Created by Marc Rousavy on 25.03.24.
6
6
  //
7
7
 
8
+ import Foundation
8
9
  import NitroModules
9
10
 
10
11
  class HybridMMKVPlatformContext: HybridMMKVPlatformContextSpec {
@@ -17,16 +18,26 @@ class HybridMMKVPlatformContext: HybridMMKVPlatformContextSpec {
17
18
  }
18
19
 
19
20
  func getBaseDirectory() throws -> String {
20
- let paths = NSSearchPathForDirectoriesInDomains(Self.directory, .userDomainMask, true)
21
- if let documentPath = paths.first {
22
- let basePath = documentPath + "/mmkv"
23
- return basePath
24
- } else {
21
+ // Get user documents directory
22
+ let paths = FileManager.default.urls(for: Self.directory, in: .userDomainMask)
23
+ guard let documentsPath = paths.first else {
25
24
  throw RuntimeError.error(withMessage: "Cannot find base-path to store MMKV files!")
26
25
  }
26
+
27
+ // append /mmkv to it
28
+ let basePath = documentsPath.appendingPathComponent("mmkv", conformingTo: .directory)
29
+ return basePath.path
27
30
  }
28
31
 
29
- func getAppGroupDirectory() -> String {
30
- return ""
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
31
42
  }
32
43
  }
@@ -1,12 +1,15 @@
1
1
  import React from 'react';
2
2
  import { Button, Text } from 'react-native';
3
- import { act, fireEvent, render, renderHook, screen, } from '@testing-library/react-native';
3
+ import { act, fireEvent, render, renderHook, screen, cleanup, } from '@testing-library/react-native';
4
4
  import { createMMKV, useMMKVNumber, useMMKVString } from '..';
5
5
  const mmkv = createMMKV();
6
6
  beforeEach(() => {
7
7
  mmkv.clearAll();
8
8
  mmkv.trim();
9
9
  });
10
+ afterEach(() => {
11
+ cleanup();
12
+ });
10
13
  test('hooks update when the value is changed directly through the instance', () => {
11
14
  const { result } = renderHook(() => useMMKVString('string-key', mmkv));
12
15
  expect(result.current[0]).toBeUndefined();
@@ -4,10 +4,30 @@
4
4
  export function createMockMMKV() {
5
5
  const storage = new Map();
6
6
  const listeners = new Set();
7
+ const notifyListeners = (key) => {
8
+ listeners.forEach((listener) => {
9
+ listener(key);
10
+ });
11
+ };
7
12
  return {
8
- clearAll: () => storage.clear(),
9
- remove: (key) => storage.delete(key),
10
- set: (key, value) => storage.set(key, value),
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
+ },
27
+ set: (key, value) => {
28
+ storage.set(key, value);
29
+ notifyListeners(key);
30
+ },
11
31
  getString: (key) => {
12
32
  const result = storage.get(key);
13
33
  return typeof result === 'string' ? result : undefined;
@@ -0,0 +1,3 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins';
2
+ declare const _default: ConfigPlugin<{}>;
3
+ export default _default;
@@ -0,0 +1,17 @@
1
+ import { createRunOncePlugin, withGradleProperties } from '@expo/config-plugins';
2
+ const pkg = require('../../package.json');
3
+ const withMMKV = (config) => {
4
+ // remove 32-bit architectures from gradle.properties
5
+ return withGradleProperties(config, (cfg) => {
6
+ // Drop any existing entry…
7
+ cfg.modResults = cfg.modResults.filter((p) => !(p.type === 'property' && p.key === 'reactNativeArchitectures'));
8
+ // …and force 64-bit only.
9
+ cfg.modResults.push({
10
+ type: 'property',
11
+ key: 'reactNativeArchitectures',
12
+ value: 'arm64-v8a,x86_64',
13
+ });
14
+ return cfg;
15
+ });
16
+ };
17
+ export default createRunOncePlugin(withMMKV, pkg.name, pkg.version);
@@ -15,8 +15,8 @@ export interface Configuration {
15
15
  *
16
16
  * @example
17
17
  * ```ts
18
- * const userStorage = new MMKV({ id: `user-${userId}-storage` })
19
- * const globalStorage = new MMKV({ id: 'global-app-storage' })
18
+ * const userStorage = createMMKV({ id: `user-${userId}-storage` })
19
+ * const globalStorage = createMMKV({ id: 'global-app-storage' })
20
20
  * ```
21
21
  *
22
22
  * @default 'mmkv.default'
@@ -27,7 +27,7 @@ export interface Configuration {
27
27
 
28
28
  * @example
29
29
  * ```ts
30
- * const temporaryStorage = new MMKV({ path: '/tmp/' })
30
+ * const temporaryStorage = createMMKV({ path: '/tmp/' })
31
31
  * ```
32
32
  *
33
33
  * @note On iOS, if an `AppGroup` is set in `Info.plist` and `path` is `undefined`, MMKV will use the `AppGroup` directory.
@@ -44,7 +44,7 @@ export interface Configuration {
44
44
  *
45
45
  * @example
46
46
  * ```ts
47
- * const secureStorage = new MMKV({ encryptionKey: 'my-encryption-key!' })
47
+ * const secureStorage = createMMKV({ encryptionKey: 'my-encryption-key!' })
48
48
  * ```
49
49
  *
50
50
  * @default undefined
@@ -9,7 +9,10 @@ export interface MMKVPlatformContext extends HybridObject<{
9
9
  getBaseDirectory(): string;
10
10
  /**
11
11
  * Get the MMKV AppGroup's directory.
12
+ * The AppGroup can be set in your App's `Info.plist`, and will enable
13
+ * data sharing between main app, companions (e.g. watch app) and extensions.
12
14
  * @platform iOS
15
+ * @default undefined
13
16
  */
14
- getAppGroupDirectory(): string;
17
+ getAppGroupDirectory(): string | undefined;
15
18
  }
@@ -10,6 +10,7 @@
10
10
 
11
11
 
12
12
  #include <string>
13
+ #include <optional>
13
14
 
14
15
  namespace margelo::nitro::mmkv {
15
16
 
@@ -42,10 +43,10 @@ namespace margelo::nitro::mmkv {
42
43
  auto __result = method(_javaPart);
43
44
  return __result->toStdString();
44
45
  }
45
- std::string JHybridMMKVPlatformContextSpec::getAppGroupDirectory() {
46
+ std::optional<std::string> JHybridMMKVPlatformContextSpec::getAppGroupDirectory() {
46
47
  static const auto method = javaClassStatic()->getMethod<jni::local_ref<jni::JString>()>("getAppGroupDirectory");
47
48
  auto __result = method(_javaPart);
48
- return __result->toStdString();
49
+ return __result != nullptr ? std::make_optional(__result->toStdString()) : std::nullopt;
49
50
  }
50
51
 
51
52
  } // namespace margelo::nitro::mmkv
@@ -54,7 +54,7 @@ namespace margelo::nitro::mmkv {
54
54
  public:
55
55
  // Methods
56
56
  std::string getBaseDirectory() override;
57
- std::string getAppGroupDirectory() override;
57
+ std::optional<std::string> getAppGroupDirectory() override;
58
58
 
59
59
  private:
60
60
  friend HybridBase;
@@ -46,7 +46,7 @@ abstract class HybridMMKVPlatformContextSpec: HybridObject() {
46
46
 
47
47
  @DoNotStrip
48
48
  @Keep
49
- abstract fun getAppGroupDirectory(): String
49
+ abstract fun getAppGroupDirectory(): String?
50
50
 
51
51
  private external fun initHybrid(): HybridData
52
52
 
@@ -20,6 +20,7 @@ namespace NitroMmkv { class HybridMMKVPlatformContextSpec_cxx; }
20
20
  #include <NitroModules/Result.hpp>
21
21
  #include <exception>
22
22
  #include <memory>
23
+ #include <optional>
23
24
  #include <string>
24
25
 
25
26
  /**
@@ -28,6 +29,21 @@ namespace NitroMmkv { class HybridMMKVPlatformContextSpec_cxx; }
28
29
  */
29
30
  namespace margelo::nitro::mmkv::bridge::swift {
30
31
 
32
+ // pragma MARK: std::optional<std::string>
33
+ /**
34
+ * Specialized version of `std::optional<std::string>`.
35
+ */
36
+ using std__optional_std__string_ = std::optional<std::string>;
37
+ inline std::optional<std::string> create_std__optional_std__string_(const std::string& value) noexcept {
38
+ return std::optional<std::string>(value);
39
+ }
40
+ inline bool has_value_std__optional_std__string_(const std::optional<std::string>& optional) noexcept {
41
+ return optional.has_value();
42
+ }
43
+ inline std::string get_std__optional_std__string_(const std::optional<std::string>& optional) noexcept {
44
+ return *optional;
45
+ }
46
+
31
47
  // pragma MARK: std::shared_ptr<HybridMMKVPlatformContextSpec>
32
48
  /**
33
49
  * Specialized version of `std::shared_ptr<HybridMMKVPlatformContextSpec>`.
@@ -48,5 +64,14 @@ namespace margelo::nitro::mmkv::bridge::swift {
48
64
  inline Result_std__string_ create_Result_std__string_(const std::exception_ptr& error) noexcept {
49
65
  return Result<std::string>::withError(error);
50
66
  }
67
+
68
+ // pragma MARK: Result<std::optional<std::string>>
69
+ using Result_std__optional_std__string__ = Result<std::optional<std::string>>;
70
+ inline Result_std__optional_std__string__ create_Result_std__optional_std__string__(const std::optional<std::string>& value) noexcept {
71
+ return Result<std::optional<std::string>>::withValue(value);
72
+ }
73
+ inline Result_std__optional_std__string__ create_Result_std__optional_std__string__(const std::exception_ptr& error) noexcept {
74
+ return Result<std::optional<std::string>>::withError(error);
75
+ }
51
76
 
52
77
  } // namespace margelo::nitro::mmkv::bridge::swift
@@ -16,6 +16,7 @@ namespace margelo::nitro::mmkv { class HybridMMKVPlatformContextSpec; }
16
16
  #include <NitroModules/Result.hpp>
17
17
  #include <exception>
18
18
  #include <memory>
19
+ #include <optional>
19
20
  #include <string>
20
21
 
21
22
  // C++ helpers for Swift
@@ -15,6 +15,7 @@ namespace NitroMmkv { class HybridMMKVPlatformContextSpec_cxx; }
15
15
 
16
16
 
17
17
  #include <string>
18
+ #include <optional>
18
19
 
19
20
  #include "NitroMmkv-Swift-Cxx-Umbrella.hpp"
20
21
 
@@ -65,7 +66,7 @@ namespace margelo::nitro::mmkv {
65
66
  auto __value = std::move(__result.value());
66
67
  return __value;
67
68
  }
68
- inline std::string getAppGroupDirectory() override {
69
+ inline std::optional<std::string> getAppGroupDirectory() override {
69
70
  auto __result = _swiftPart.getAppGroupDirectory();
70
71
  if (__result.hasError()) [[unlikely]] {
71
72
  std::rethrow_exception(__result.error());
@@ -15,7 +15,7 @@ public protocol HybridMMKVPlatformContextSpec_protocol: HybridObject {
15
15
 
16
16
  // Methods
17
17
  func getBaseDirectory() throws -> String
18
- func getAppGroupDirectory() throws -> String
18
+ func getAppGroupDirectory() throws -> String?
19
19
  }
20
20
 
21
21
  /// See ``HybridMMKVPlatformContextSpec``
@@ -122,14 +122,20 @@ open class HybridMMKVPlatformContextSpec_cxx {
122
122
  }
123
123
 
124
124
  @inline(__always)
125
- public final func getAppGroupDirectory() -> bridge.Result_std__string_ {
125
+ public final func getAppGroupDirectory() -> bridge.Result_std__optional_std__string__ {
126
126
  do {
127
127
  let __result = try self.__implementation.getAppGroupDirectory()
128
- let __resultCpp = std.string(__result)
129
- return bridge.create_Result_std__string_(__resultCpp)
128
+ let __resultCpp = { () -> bridge.std__optional_std__string_ in
129
+ if let __unwrappedValue = __result {
130
+ return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
131
+ } else {
132
+ return .init()
133
+ }
134
+ }()
135
+ return bridge.create_Result_std__optional_std__string__(__resultCpp)
130
136
  } catch (let __error) {
131
137
  let __exceptionPtr = __error.toCpp()
132
- return bridge.create_Result_std__string_(__exceptionPtr)
138
+ return bridge.create_Result_std__optional_std__string__(__exceptionPtr)
133
139
  }
134
140
  }
135
141
  }
@@ -16,6 +16,7 @@
16
16
 
17
17
 
18
18
  #include <string>
19
+ #include <optional>
19
20
 
20
21
  namespace margelo::nitro::mmkv {
21
22
 
@@ -49,7 +50,7 @@ namespace margelo::nitro::mmkv {
49
50
  public:
50
51
  // Methods
51
52
  virtual std::string getBaseDirectory() = 0;
52
- virtual std::string getAppGroupDirectory() = 0;
53
+ virtual std::optional<std::string> getAppGroupDirectory() = 0;
53
54
 
54
55
  protected:
55
56
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-mmkv",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.0-beta.8",
4
4
  "description": "react-native-mmkv",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -60,6 +60,7 @@
60
60
  "registry": "https://registry.npmjs.org/"
61
61
  },
62
62
  "devDependencies": {
63
+ "@expo/config-plugins": "^10.1.2",
63
64
  "@react-native/eslint-config": "0.81.0",
64
65
  "@testing-library/react-native": "^13.3.1",
65
66
  "@types/jest": "^29.5.12",
@@ -67,7 +68,6 @@
67
68
  "eslint": "^8.57.0",
68
69
  "eslint-config-prettier": "^9.1.0",
69
70
  "eslint-plugin-prettier": "^5.2.1",
70
- "jest": "^30.0.5",
71
71
  "nitro-codegen": "*",
72
72
  "prettier": "^3.3.3",
73
73
  "react": "19.1.0",
@@ -6,6 +6,7 @@ import {
6
6
  render,
7
7
  renderHook,
8
8
  screen,
9
+ cleanup,
9
10
  } from '@testing-library/react-native'
10
11
  import { createMMKV, useMMKVNumber, useMMKVString } from '..'
11
12
 
@@ -16,6 +17,10 @@ beforeEach(() => {
16
17
  mmkv.trim()
17
18
  })
18
19
 
20
+ afterEach(() => {
21
+ cleanup()
22
+ })
23
+
19
24
  test('hooks update when the value is changed directly through the instance', () => {
20
25
  const { result } = renderHook(() => useMMKVString('string-key', mmkv))
21
26
 
@@ -7,10 +7,31 @@ export function createMockMMKV(): MMKV {
7
7
  const storage = new Map<string, string | boolean | number | ArrayBuffer>()
8
8
  const listeners = new Set<(key: string) => void>()
9
9
 
10
+ const notifyListeners = (key: string) => {
11
+ listeners.forEach((listener) => {
12
+ listener(key)
13
+ })
14
+ }
15
+
10
16
  return {
11
- clearAll: () => storage.clear(),
12
- remove: (key) => storage.delete(key),
13
- set: (key, value) => storage.set(key, value),
17
+ clearAll: () => {
18
+ const keysBefore = storage.keys()
19
+ storage.clear()
20
+ // Notify all listeners for all keys that were cleared
21
+ for (const key of keysBefore) {
22
+ notifyListeners(key)
23
+ }
24
+ },
25
+ remove: (key) => {
26
+ const deleted = storage.delete(key)
27
+ if (deleted) {
28
+ notifyListeners(key)
29
+ }
30
+ },
31
+ set: (key, value) => {
32
+ storage.set(key, value)
33
+ notifyListeners(key)
34
+ },
14
35
  getString: (key) => {
15
36
  const result = storage.get(key)
16
37
  return typeof result === 'string' ? result : undefined
@@ -0,0 +1,23 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins'
2
+ import { createRunOncePlugin, withGradleProperties } from '@expo/config-plugins'
3
+
4
+ const pkg = require('../../package.json')
5
+
6
+ const withMMKV: ConfigPlugin<{}> = (config) => {
7
+ // remove 32-bit architectures from gradle.properties
8
+ return withGradleProperties(config, (cfg) => {
9
+ // Drop any existing entry…
10
+ cfg.modResults = cfg.modResults.filter(
11
+ (p) => !(p.type === 'property' && p.key === 'reactNativeArchitectures')
12
+ )
13
+ // …and force 64-bit only.
14
+ cfg.modResults.push({
15
+ type: 'property',
16
+ key: 'reactNativeArchitectures',
17
+ value: 'arm64-v8a,x86_64',
18
+ })
19
+ return cfg
20
+ })
21
+ }
22
+
23
+ export default createRunOncePlugin(withMMKV, pkg.name, pkg.version)
@@ -17,8 +17,8 @@ export interface Configuration {
17
17
  *
18
18
  * @example
19
19
  * ```ts
20
- * const userStorage = new MMKV({ id: `user-${userId}-storage` })
21
- * const globalStorage = new MMKV({ id: 'global-app-storage' })
20
+ * const userStorage = createMMKV({ id: `user-${userId}-storage` })
21
+ * const globalStorage = createMMKV({ id: 'global-app-storage' })
22
22
  * ```
23
23
  *
24
24
  * @default 'mmkv.default'
@@ -29,7 +29,7 @@ export interface Configuration {
29
29
 
30
30
  * @example
31
31
  * ```ts
32
- * const temporaryStorage = new MMKV({ path: '/tmp/' })
32
+ * const temporaryStorage = createMMKV({ path: '/tmp/' })
33
33
  * ```
34
34
  *
35
35
  * @note On iOS, if an `AppGroup` is set in `Info.plist` and `path` is `undefined`, MMKV will use the `AppGroup` directory.
@@ -46,7 +46,7 @@ export interface Configuration {
46
46
  *
47
47
  * @example
48
48
  * ```ts
49
- * const secureStorage = new MMKV({ encryptionKey: 'my-encryption-key!' })
49
+ * const secureStorage = createMMKV({ encryptionKey: 'my-encryption-key!' })
50
50
  * ```
51
51
  *
52
52
  * @default undefined
@@ -8,7 +8,10 @@ export interface MMKVPlatformContext
8
8
  getBaseDirectory(): string
9
9
  /**
10
10
  * Get the MMKV AppGroup's directory.
11
+ * The AppGroup can be set in your App's `Info.plist`, and will enable
12
+ * data sharing between main app, companions (e.g. watch app) and extensions.
11
13
  * @platform iOS
14
+ * @default undefined
12
15
  */
13
- getAppGroupDirectory(): string
16
+ getAppGroupDirectory(): string | undefined
14
17
  }