├── .github
└── img
│ ├── flipper.png
│ └── install.png
├── .gitignore
├── README.md
├── lerna.json
├── package.json
├── packages
├── flipper-plugin-react-native-mmkv
│ ├── .gitignore
│ ├── babel.config.js
│ ├── package.json
│ ├── src
│ │ └── index.tsx
│ └── tsconfig.json
└── react-native-mmkv-flipper-plugin
│ ├── .gitignore
│ ├── index.ts
│ ├── package.json
│ └── tsconfig.json
└── yarn.lock
/.github/img/flipper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muchobien/flipper-plugin-react-native-mmkv/c5d0cf586f6d1ce2233fe93fba169c219c8719d9/.github/img/flipper.png
--------------------------------------------------------------------------------
/.github/img/install.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/muchobien/flipper-plugin-react-native-mmkv/c5d0cf586f6d1ce2233fe93fba169c219c8719d9/.github/img/install.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Flipper plugin for [react-native-mmkv](https://github.com/mrousavy/react-native-mmkv)
2 |
3 | This plugin allows you to control react-native-mmkv from flipper
4 |
5 | ## Usage
6 |
7 | ### Install the flipper plugin.
8 |
9 | Simply search for 'react-native-mmkv' in the `Plugin Manager` in Flipper.
10 |
11 |
12 |
13 |
14 |
15 | ### In your react-native App
16 |
17 | ```sh
18 | yarn add -D react-native-flipper
19 | yarn add react-native-mmkv-flipper-plugin
20 | ```
21 |
22 | ```ts
23 | import { MMKV } from "react-native-mmkv";
24 | import { initializeMMKVFlipper } from "react-native-mmkv-flipper-plugin";
25 | const storage = new MMKV();
26 |
27 | // add this line inside your App.tsx
28 | if (__DEV__) {
29 | initializeMMKVFlipper({ default: storage });
30 | }
31 | ```
32 |
33 | ## Screenshot
34 |
35 | 
36 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*"
4 | ],
5 | "version": "1.0.0"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "flipper-plugin-react-native-mmkv",
3 | "version": "0.0.1",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "private": true,
7 | "workspaces": {
8 | "packages": [
9 | "packages/*"
10 | ]
11 | },
12 | "devDependencies": {
13 | "lerna": "^4.0.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/flipper-plugin-react-native-mmkv/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist/
3 |
--------------------------------------------------------------------------------
/packages/flipper-plugin-react-native-mmkv/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/preset-typescript',
4 | '@babel/preset-react',
5 | ['@babel/preset-env', {targets: {node: 'current'}}]
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/packages/flipper-plugin-react-native-mmkv/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
3 | "name": "flipper-plugin-react-native-mmkv",
4 | "id": "rn-mmkv",
5 | "version": "1.0.0",
6 | "pluginType": "client",
7 | "main": "dist/bundle.js",
8 | "flipperBundlerEntry": "src/index.tsx",
9 | "license": "MIT",
10 | "description": "Flipper plugin for React Native MMKV",
11 | "keywords": [
12 | "flipper-plugin"
13 | ],
14 | "icon": "apps",
15 | "title": "MMKV",
16 | "scripts": {
17 | "lint": "flipper-pkg lint",
18 | "prepack": "flipper-pkg lint && flipper-pkg bundle",
19 | "build": "flipper-pkg bundle",
20 | "watch": "flipper-pkg bundle --watch",
21 | "test": "jest --no-watchman"
22 | },
23 | "peerDependencies": {
24 | "@emotion/styled": "latest",
25 | "antd": "latest",
26 | "flipper-plugin": "^0.129.0",
27 | "react": "latest",
28 | "react-dom": "latest"
29 | },
30 | "devDependencies": {
31 | "@babel/preset-react": "latest",
32 | "@babel/preset-typescript": "latest",
33 | "@emotion/styled": "latest",
34 | "@testing-library/react": "latest",
35 | "@types/jest": "latest",
36 | "@types/react": "latest",
37 | "@types/react-dom": "latest",
38 | "antd": "latest",
39 | "flipper-pkg": "latest",
40 | "flipper-plugin": "latest",
41 | "jest": "latest",
42 | "jest-mock-console": "latest",
43 | "react": "latest",
44 | "react-dom": "latest",
45 | "typescript": "latest"
46 | },
47 | "jest": {
48 | "testEnvironment": "jsdom"
49 | },
50 | "dependencies": {
51 | "@ant-design/icons": "4.7.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/flipper-plugin-react-native-mmkv/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect, useMemo } from "react";
2 | import {
3 | createDataSource,
4 | createState,
5 | DataInspector,
6 | DataTable,
7 | DataTableColumn,
8 | DetailSidebar,
9 | Layout,
10 | Panel,
11 | PluginClient,
12 | usePlugin,
13 | useValue,
14 | } from "flipper-plugin";
15 | import { Button, Form, Input, Menu, Select } from "antd";
16 | import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
17 |
18 | type Data = Record;
19 |
20 | type Events = {
21 | "mmkv-remove-key": { key: string; instance: string };
22 | "mmkv-data": Record;
23 | "mmkv-key": { key: string; value: string | undefined; instance: string };
24 | };
25 |
26 | type SetParams = {
27 | key: string;
28 | value: string;
29 | };
30 |
31 | type Methods = {
32 | "mmkv-remove-key": (params: {
33 | key: string;
34 | instance: string;
35 | }) => Promise;
36 | "mmkv-remove-all": (instance: string) => Promise;
37 | "mmkv-set": (params: {
38 | key: string;
39 | value: string;
40 | instance: string;
41 | }) => Promise;
42 | };
43 |
44 | type InstanceDict = Record;
45 |
46 | type Row = {
47 | key: string;
48 | value: string;
49 | };
50 |
51 | // Read more: https://fbflipper.com/docs/tutorial/js-custom#creating-a-first-plugin
52 | // API: https://fbflipper.com/docs/extending/flipper-plugin#pluginclient
53 | export function plugin(client: PluginClient) {
54 | const rows = createDataSource([], {
55 | persist: "rows",
56 | key: "key",
57 | });
58 | const instances = createState({}, { persist: "instances" });
59 | const selectedInstance = createState(undefined, {
60 | persist: "selectedInstance",
61 | });
62 |
63 | const selectedRow = createState(undefined, {
64 | persist: "selection",
65 | });
66 |
67 | client.onMessage("mmkv-data", (newData) => {
68 | Object.entries(newData).forEach(([name, instance], index) => {
69 | if (index === 0) {
70 | selectedInstance.update(() => name);
71 | }
72 | instances.update((draft) => {
73 | draft[name] = instance;
74 | });
75 | });
76 | });
77 |
78 | client.onMessage("mmkv-key", ({ key, value, instance }) => {
79 | const selected = selectedInstance.get();
80 | if (value) {
81 | instances.update((draft) => {
82 | if (selected === instance) {
83 | rows.upsert({ key, value });
84 | }
85 | draft[instance][key] = value;
86 | });
87 | } else {
88 | instances.update((draft) => {
89 | if (selected === instance) {
90 | rows.deleteByKey(key);
91 | }
92 | delete draft[instance][key];
93 | });
94 | }
95 | });
96 |
97 | const removeKey = (key: string) => {
98 | const instance = selectedInstance.get();
99 | if (instance) {
100 | selectedRow.set(undefined);
101 | instances.update((draft) => {
102 | rows.deleteByKey(key);
103 | delete draft[instance][key];
104 | });
105 | client.send("mmkv-remove-key", { key, instance });
106 | }
107 | };
108 |
109 | const removeAll = () => {
110 | const instance = selectedInstance.get();
111 | if (instance) {
112 | client.send("mmkv-remove-all", instance);
113 | instances.update((draft) => {
114 | delete draft[instance];
115 | selectedRow.set(undefined);
116 | rows.clear();
117 | selectedInstance.update(() => undefined);
118 | });
119 | }
120 | };
121 |
122 | const set = (params: SetParams) => {
123 | const instance = selectedInstance.get();
124 | if (instance) {
125 | client.send("mmkv-set", { ...params, instance });
126 | instances.update((draft) => {
127 | draft[instance][params.key] = params.value;
128 | });
129 | selectedRow.set(params);
130 | rows.upsert(params);
131 | }
132 | };
133 |
134 | const updateRows = (update: Row[]) => {
135 | rows.clear();
136 | update.forEach((row) => {
137 | rows.upsert(row);
138 | });
139 | };
140 |
141 | console.log("instances", Object.keys(instances.get()));
142 |
143 | return {
144 | instances,
145 | removeAll,
146 | removeKey,
147 | rows,
148 | selectedInstance,
149 | selectedRow,
150 | set,
151 | updateRows,
152 | };
153 | }
154 |
155 | const columns: DataTableColumn[] = [
156 | {
157 | key: "key",
158 | title: "Key",
159 | width: "10%",
160 | },
161 | {
162 | key: "value",
163 | title: "Value",
164 | },
165 | ];
166 |
167 | const safeJSONParse = (value: string) => {
168 | try {
169 | return JSON.parse(value);
170 | } catch {
171 | return value;
172 | }
173 | };
174 |
175 | function Sidebar({
176 | instances,
177 | record,
178 | onFinish,
179 | onInstanceChange,
180 | selectedInstance,
181 | }: {
182 | record?: T;
183 | instances: InstanceDict;
184 | onFinish: (params: T) => void;
185 | onInstanceChange: (instance: string) => void;
186 | selectedInstance: string | undefined;
187 | }) {
188 | return (
189 |
190 |
191 |
208 |
209 | {record && (
210 |
211 |
218 |
219 | )}
220 |
221 |
239 |
240 |
241 |
251 |
252 |
253 |
254 |
257 |
258 |
259 |
260 |
261 | );
262 | }
263 |
264 | export function Component() {
265 | const instance = usePlugin(plugin);
266 | const selectedInstance = useValue(instance.selectedInstance);
267 | const selectedRecord = useValue(instance.selectedRow);
268 |
269 | const handleSelect = useCallback(
270 | (row: Row | undefined) => {
271 | instance.selectedRow.set(row);
272 | },
273 | [instance.selectedRow]
274 | );
275 |
276 | const handleInstanceChange = useCallback((ins: string) => {
277 | instance.selectedInstance.update(() => ins);
278 | }, []);
279 |
280 | useEffect(() => {
281 | const current = instance.instances.get();
282 | if (current && selectedInstance) {
283 | instance.updateRows(
284 | Object.entries(current[selectedInstance]).map(([key, value]) => ({
285 | key,
286 | value,
287 | }))
288 | );
289 | }
290 | }, [selectedInstance]);
291 |
292 | return (
293 |
294 |
295 |
301 |
307 |
310 | >
311 | }
312 | onContextMenu={(record) => (
313 | }
316 | onClick={() => {
317 | if (!record) return;
318 | instance.removeKey(record.key);
319 | }}
320 | >
321 | Remove
322 |
323 | )}
324 | />
325 |
332 |
333 |
334 | );
335 | }
336 |
--------------------------------------------------------------------------------
/packages/flipper-plugin-react-native-mmkv/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "module": "ES6",
5 | "jsx": "react",
6 | "sourceMap": true,
7 | "noEmit": true,
8 | "strict": true,
9 | "moduleResolution": "node",
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "files": ["src/index.tsx"]
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react-native-mmkv-flipper-plugin/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/packages/react-native-mmkv-flipper-plugin/index.ts:
--------------------------------------------------------------------------------
1 | import type { Flipper } from "react-native-flipper";
2 | import type { MMKV } from "react-native-mmkv";
3 | let FlipperModule: typeof import("react-native-flipper") | undefined;
4 |
5 | try {
6 | FlipperModule = require("react-native-flipper");
7 | } catch {
8 | // noop
9 | }
10 |
11 | type MMKVInstances = Record;
12 |
13 | type InstanceEntry = {
14 | key: string;
15 | value: string;
16 | instance: string;
17 | };
18 |
19 | let currentConnection: Flipper.FlipperConnection | null = null;
20 |
21 | export const initializeMMKVFlipper = (instances: MMKVInstances) => {
22 | if (FlipperModule == null) {
23 | throw new Error(
24 | "Please install the 'react-native-flipper' package in your project to use Flipper integration for 'react-native-mmkv'"
25 | );
26 | }
27 |
28 | const { addPlugin } = FlipperModule;
29 |
30 | if (currentConnection === null) {
31 | addPlugin({
32 | getId: () => "rn-mmkv",
33 | onConnect(connection) {
34 | currentConnection = connection;
35 | const data = Object.entries(instances).reduce<
36 | Record>
37 | >(
38 | (instancesDict, [name, instance]) => ({
39 | ...instancesDict,
40 | [name]: instance
41 | .getAllKeys()
42 | .reduce>(
43 | (instanceDict, key) => ({
44 | ...instanceDict,
45 | [key]: instance.getString(key) ?? null,
46 | }),
47 | {}
48 | ),
49 | }),
50 | {}
51 | );
52 |
53 | connection.send("mmkv-data", data);
54 |
55 | Object.entries(instances).forEach(([name, instance]) => {
56 | instance.addOnValueChangedListener((key) => {
57 | currentConnection?.send("mmkv-key", {
58 | key,
59 | value: instance.getString(key),
60 | instance: name,
61 | });
62 | });
63 | });
64 |
65 | connection.receive(
66 | "mmkv-remove-key",
67 | ({ key, instance }: Omit) => {
68 | instances[instance].delete(key);
69 | }
70 | );
71 |
72 | connection.receive(
73 | "mmkv-set",
74 | ({ key, value, instance }: InstanceEntry) => {
75 | instances[instance].set(key, value);
76 | }
77 | );
78 |
79 | connection.receive("mmkv-remove-all", (instance: string) => {
80 | instances[instance].clearAll();
81 | });
82 | },
83 | onDisconnect() {
84 | currentConnection = null;
85 | },
86 | runInBackground: () => false,
87 | });
88 | }
89 | };
90 |
--------------------------------------------------------------------------------
/packages/react-native-mmkv-flipper-plugin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-mmkv-flipper-plugin",
3 | "version": "1.0.0",
4 | "description": "React Native's MMKV debugger for Flipper",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "license": "MIT",
8 | "scripts": {
9 | "prepack": "yarn tsc"
10 | },
11 | "peerDependencies": {
12 | "react-native-flipper": ">=0.99.0",
13 | "react-native-mmkv": ">=1.4.0"
14 | },
15 | "devDependencies": {
16 | "@types/node": "17.0.10",
17 | "react-native-flipper": "0.129.0",
18 | "react-native-mmkv": "2.0.0",
19 | "typescript": "4.5.5"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/react-native-mmkv-flipper-plugin/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "lib": ["esnext", "dom"],
7 | "declaration": true,
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "strict": true,
11 | "skipLibCheck": true,
12 | "outDir": "dist",
13 | }
14 | }
15 |
--------------------------------------------------------------------------------