@buivietphi/skill-mobile-mt 2.0.0 → 2.1.0
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/AGENTS.md +56 -38
- package/README.md +83 -47
- package/SKILL.md +604 -50
- package/package.json +1 -1
- package/shared/bug-detection.md +411 -27
- package/shared/code-review.md +899 -37
- package/shared/debugging-intelligence.md +787 -0
- package/shared/i18n-localization.md +426 -0
- package/shared/prompt-engineering.md +272 -21
- package/shared/storage-patterns.md +312 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Prompt Engineering — Intelligent Prompt Generation
|
|
2
2
|
|
|
3
|
-
> Learned from Anthropic,
|
|
3
|
+
> Learned from Anthropic, Cursor, Lovable, Manus, Windsurf, Kiro, Claude Code, and top 50k+ star repos.
|
|
4
4
|
> How to generate prompts that AI agents execute correctly.
|
|
5
5
|
|
|
6
6
|
---
|
|
@@ -64,40 +64,138 @@
|
|
|
64
64
|
```xml
|
|
65
65
|
<think>
|
|
66
66
|
BUG: [description]
|
|
67
|
-
|
|
67
|
+
ERROR MESSAGE: [paste exact error]
|
|
68
|
+
|
|
69
|
+
⛔ STOP — CLASSIFY + SEARCH PROJECT FIRST (before ANY analysis):
|
|
70
|
+
|
|
71
|
+
<error_classification>
|
|
72
|
+
TYPE: [RUNTIME CRASH / BUILD ERROR / TYPE MISMATCH / NETWORK ERROR /
|
|
73
|
+
RENDER ERROR / NAVIGATION ERROR / PERFORMANCE / STATE ERROR /
|
|
74
|
+
NATIVE ERROR / MEMORY ERROR / INVESTIGATION]
|
|
75
|
+
|
|
76
|
+
SEARCH STRATEGY based on type:
|
|
77
|
+
→ RUNTIME/STATE/RENDER/NAVIGATION → Search src/ FIRST → then trace outward
|
|
78
|
+
→ BUILD/NATIVE → Search config files FIRST (tsconfig/gradle/Pod/pubspec)
|
|
79
|
+
→ NETWORK/API → Search API service files → then .env → then interceptors
|
|
80
|
+
→ INVESTIGATION → Search by feature name → read → report (don't fix yet)
|
|
81
|
+
|
|
82
|
+
If complex bug → Read shared/debugging-intelligence.md for pattern match
|
|
83
|
+
</error_classification>
|
|
84
|
+
|
|
85
|
+
<project_search>
|
|
86
|
+
STEP 1: Extract keywords from error:
|
|
87
|
+
→ File/path in stack trace: [extract]
|
|
88
|
+
→ Function/class/component name: [extract]
|
|
89
|
+
→ Module/package name: [extract]
|
|
90
|
+
→ Line number: [extract if available]
|
|
91
|
+
→ Error code / HTTP status: [extract if available]
|
|
92
|
+
|
|
93
|
+
STEP 2: Filter noise from log (if user pasted log):
|
|
94
|
+
→ SKIP: node_modules/*, React internals, engine frames
|
|
95
|
+
→ FOCUS: lines with src/ paths, "Error:", "Caused by:", YOUR component names
|
|
96
|
+
|
|
97
|
+
STEP 3: Search project source code (MANDATORY):
|
|
98
|
+
→ Grep "[keyword]" src/ ← ALWAYS start here (unless BUILD error)
|
|
99
|
+
→ Grep "[function_name]" src/ ← find the actual function
|
|
100
|
+
→ Glob "**/*[ComponentName]*" ← find the actual file
|
|
101
|
+
→ Results: [list files found]
|
|
102
|
+
|
|
103
|
+
STEP 4: Read matched files (TOP 3-5):
|
|
104
|
+
→ Read [file1] — [what I found: actual code, types, imports]
|
|
105
|
+
→ Read [file2] — [what I found: related logic, callers]
|
|
106
|
+
→ Read [file3] — [what I found: state/store connected to this]
|
|
107
|
+
|
|
108
|
+
⛔ If I skipped Step 1-4 → GO BACK AND DO THEM NOW
|
|
109
|
+
⛔ If I found 0 results in src/ → widen: lib/ → app/ → project root
|
|
110
|
+
</project_search>
|
|
68
111
|
|
|
69
112
|
<source_verification>
|
|
70
|
-
⚠️
|
|
71
|
-
- [ ]
|
|
72
|
-
- [ ]
|
|
113
|
+
⚠️ Verify I have REAL project data (not assumptions):
|
|
114
|
+
- [ ] Classified error type and picked correct search strategy
|
|
115
|
+
- [ ] Filtered noise from log (if applicable)
|
|
116
|
+
- [ ] Searched src/ with Grep for error keywords → found files
|
|
117
|
+
- [ ] Read the actual file(s) where bug occurs
|
|
118
|
+
- [ ] Verified function/class names exist in project (grep result)
|
|
73
119
|
- [ ] Checked package versions in package.json/pubspec.yaml
|
|
74
120
|
- [ ] Identified data types from actual code (not assumed)
|
|
121
|
+
- [ ] Traced the call chain: who calls this → what it returns
|
|
75
122
|
</source_verification>
|
|
76
123
|
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
124
|
+
<root_cause_tracing>
|
|
125
|
+
⛔ NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
|
|
126
|
+
|
|
127
|
+
STEP 1 — IMMEDIATE CAUSE (what throws):
|
|
128
|
+
- Error type: [from classification above]
|
|
129
|
+
- Crash/error at: [file:line from project search]
|
|
130
|
+
- What code does at that line: [describe from reading]
|
|
131
|
+
- What value is wrong: [actual vs expected]
|
|
132
|
+
|
|
133
|
+
STEP 2 — TRACE BACKWARD (what called this):
|
|
134
|
+
- Who calls this function? → [grep callers in src/]
|
|
135
|
+
- What data does caller pass? → [trace data origin]
|
|
136
|
+
- Go up one level: who calls the caller? → [trace further]
|
|
137
|
+
|
|
138
|
+
STEP 3 — ROOT CAUSE (where chain breaks):
|
|
139
|
+
- Root cause at: [file:line — where correct data becomes incorrect]
|
|
140
|
+
- WHY it fails: [based on actual code read, NOT guess]
|
|
141
|
+
- Does this match a known pattern? → [check debugging-intelligence.md if loaded]
|
|
142
|
+
- Does root cause explain ALL symptoms? YES/NO
|
|
143
|
+
→ If NO → theory is wrong → re-trace from Step 2
|
|
144
|
+
</root_cause_tracing>
|
|
145
|
+
|
|
146
|
+
<working_example>
|
|
147
|
+
Search for similar working code in SAME project:
|
|
148
|
+
→ Grep for similar pattern that works: [search term]
|
|
149
|
+
→ Found working example at: [file:line] (or "none found")
|
|
150
|
+
→ Differences between broken vs working:
|
|
151
|
+
1. [difference]
|
|
152
|
+
2. [difference]
|
|
153
|
+
→ The fix should align broken code with working pattern
|
|
154
|
+
</working_example>
|
|
155
|
+
|
|
156
|
+
<hypothesis>
|
|
157
|
+
⛔ 1 HYPOTHESIS → 1 MINIMAL CHANGE → VERIFY
|
|
158
|
+
|
|
159
|
+
HYPOTHESIS: [specific theory based on root cause]
|
|
160
|
+
CHANGE: [smallest possible change to test this — 1 change only]
|
|
161
|
+
EXPECTED RESULT: [what should happen if hypothesis is correct]
|
|
162
|
+
|
|
163
|
+
⛔ If this fails → REVERT → form NEW hypothesis (never stack fixes)
|
|
164
|
+
⛔ If 3 hypotheses fail → STOP → question architecture
|
|
165
|
+
</hypothesis>
|
|
90
166
|
|
|
91
167
|
<fix>
|
|
92
|
-
[Specific change with code snippet]
|
|
168
|
+
[Specific change with code snippet — before → after]
|
|
93
169
|
|
|
94
170
|
WHY IT WORKS:
|
|
95
|
-
[Explain
|
|
171
|
+
[Explain based on root cause tracing — not guess]
|
|
96
172
|
SOURCE: [where this fix pattern comes from — project code / skill file / official docs]
|
|
173
|
+
|
|
174
|
+
DEFENSE IN DEPTH (make bug structurally impossible):
|
|
175
|
+
- Layer 1 (input): [validation added]
|
|
176
|
+
- Layer 2 (state): [guard added]
|
|
177
|
+
- Layer 3 (render): [null check / fallback added]
|
|
97
178
|
</fix>
|
|
98
179
|
|
|
180
|
+
<verification>
|
|
181
|
+
⛔ NO COMPLETION CLAIMS WITHOUT EVIDENCE
|
|
182
|
+
|
|
183
|
+
🚩 Anti-rationalization check:
|
|
184
|
+
- Am I saying "should work now" without running it? → RUN IT
|
|
185
|
+
- Am I "confident" without evidence? → PROVE IT
|
|
186
|
+
- Am I stacking 3+ changes? → REVERT. 1 change only.
|
|
187
|
+
|
|
188
|
+
Evidence:
|
|
189
|
+
- [ ] Fix addresses root cause (not just symptoms)
|
|
190
|
+
- [ ] All symptoms explained by this root cause
|
|
191
|
+
- [ ] Working example pattern followed (if found)
|
|
192
|
+
- [ ] Defense-in-depth added at [N] layers
|
|
193
|
+
- [ ] Side effects checked (grep for other callers)
|
|
194
|
+
- [ ] Both platforms considered (iOS + Android)
|
|
195
|
+
</verification>
|
|
196
|
+
|
|
99
197
|
<side_effects>
|
|
100
|
-
- Files that import this: [list
|
|
198
|
+
- Files that import this: [list from grep results]
|
|
101
199
|
- Tests affected: [list]
|
|
102
200
|
- Platform-specific: iOS [impact] / Android [impact]
|
|
103
201
|
</side_effects>
|
|
@@ -116,6 +214,64 @@ FILE: [path]
|
|
|
116
214
|
</think>
|
|
117
215
|
```
|
|
118
216
|
|
|
217
|
+
### Diagnostic Scan (user unsure / vague / "check this for me")
|
|
218
|
+
|
|
219
|
+
```xml
|
|
220
|
+
<think>
|
|
221
|
+
USER SAID: [what user described or asked — could be vague]
|
|
222
|
+
AREA: [extract: screen name / feature name / module name / file name]
|
|
223
|
+
|
|
224
|
+
<area_identification>
|
|
225
|
+
What did user mention or show?
|
|
226
|
+
→ Screen/feature name: [extract from user's words]
|
|
227
|
+
→ File pasted/referenced: [if any]
|
|
228
|
+
→ Behavior described: [if any]
|
|
229
|
+
→ If unclear → I should ask: "Which screen or feature should I check?"
|
|
230
|
+
</area_identification>
|
|
231
|
+
|
|
232
|
+
<project_search>
|
|
233
|
+
Search broadly for this area:
|
|
234
|
+
→ Grep "[feature]" src/ → found: [list files]
|
|
235
|
+
→ Glob "**/*[ScreenName]*" → found: [list files]
|
|
236
|
+
→ Also search: related hooks, services, stores, utils
|
|
237
|
+
→ Total files to scan: [N files]
|
|
238
|
+
</project_search>
|
|
239
|
+
|
|
240
|
+
<diagnostic_scan>
|
|
241
|
+
For EACH file, run the checklist:
|
|
242
|
+
|
|
243
|
+
FILE: [file1:path]
|
|
244
|
+
□ Crash risks: [findings or "clean"]
|
|
245
|
+
□ Memory leaks: [findings or "clean"]
|
|
246
|
+
□ Race conditions: [findings or "clean"]
|
|
247
|
+
□ Security: [findings or "clean"]
|
|
248
|
+
□ Performance: [findings or "clean"]
|
|
249
|
+
□ UX/states: [findings or "clean"]
|
|
250
|
+
□ Data flow: [trace API → state → render — any break?]
|
|
251
|
+
□ Edge cases: [empty data? error response? offline? slow?]
|
|
252
|
+
|
|
253
|
+
FILE: [file2:path]
|
|
254
|
+
□ ... (repeat for each file)
|
|
255
|
+
</diagnostic_scan>
|
|
256
|
+
|
|
257
|
+
<report>
|
|
258
|
+
Scanned: [N files] in [area name]
|
|
259
|
+
|
|
260
|
+
🔴 Issues found:
|
|
261
|
+
1. [SEVERITY] [file:line] — [description]
|
|
262
|
+
2. [SEVERITY] [file:line] — [description]
|
|
263
|
+
|
|
264
|
+
🟡 Suspicious (might be intentional):
|
|
265
|
+
1. [file:line] — [what looks off and why]
|
|
266
|
+
|
|
267
|
+
✅ Looks good:
|
|
268
|
+
- [aspect that's well-implemented]
|
|
269
|
+
|
|
270
|
+
Recommendation: [what to fix first / what to investigate deeper]
|
|
271
|
+
</report>
|
|
272
|
+
</think>
|
|
273
|
+
```
|
|
274
|
+
|
|
119
275
|
**Example:**
|
|
120
276
|
```xml
|
|
121
277
|
<think>
|
|
@@ -652,6 +808,101 @@ Faster than reading documentation.
|
|
|
652
808
|
|
|
653
809
|
---
|
|
654
810
|
|
|
811
|
+
## Advanced Patterns (from top AI tools)
|
|
812
|
+
|
|
813
|
+
### Verification-First Pattern (Anthropic #1 recommendation)
|
|
814
|
+
|
|
815
|
+
```
|
|
816
|
+
ALWAYS provide verification criteria BEFORE implementation:
|
|
817
|
+
|
|
818
|
+
<good-example>
|
|
819
|
+
"Add pagination to ProductList. Verify: loads 20 items, next page
|
|
820
|
+
on scroll to bottom, shows loading spinner during fetch, handles
|
|
821
|
+
empty last page. Run: jest --testPathPattern=ProductList"
|
|
822
|
+
</good-example>
|
|
823
|
+
|
|
824
|
+
<bad-example>
|
|
825
|
+
"Add pagination to ProductList"
|
|
826
|
+
(no way to verify success)
|
|
827
|
+
</bad-example>
|
|
828
|
+
|
|
829
|
+
The single highest-leverage thing: include tests or expected outputs
|
|
830
|
+
so the agent can check itself.
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### Investigate-Before-Answer Pattern (used by Cursor, Lovable)
|
|
834
|
+
|
|
835
|
+
```
|
|
836
|
+
NEVER speculate about code you have not opened:
|
|
837
|
+
|
|
838
|
+
STEP 1: User mentions file → Read it
|
|
839
|
+
STEP 2: Understand the actual code → then answer
|
|
840
|
+
STEP 3: If referencing a function → Grep to verify it exists
|
|
841
|
+
|
|
842
|
+
⛔ "The error is probably because..." (guessing)
|
|
843
|
+
✅ Read file → find line → "Line 42 accesses product.images[0]
|
|
844
|
+
without null check. The API sometimes returns empty array."
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
### Assumption-Driven Progress (from Cursor — for non-blocking work)
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
When NOT blocked but details are unclear:
|
|
851
|
+
|
|
852
|
+
✅ State assumption clearly → proceed → let user correct
|
|
853
|
+
"Assuming REST API (matching product feature pattern). Creating
|
|
854
|
+
authService with axios. If this should use Firebase, let me know."
|
|
855
|
+
|
|
856
|
+
⛔ Ask permission for every small decision
|
|
857
|
+
"Should I use axios or fetch? Should the file be named authService
|
|
858
|
+
or AuthService? Should I put it in services/ or api/?"
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### Negative Space Pattern (used by ALL top tools)
|
|
862
|
+
|
|
863
|
+
```
|
|
864
|
+
Explicitly state what NOT to do — prevents drift:
|
|
865
|
+
|
|
866
|
+
⛔ NEVER add new packages without checking existing deps first
|
|
867
|
+
⛔ NEVER create utils/ or helpers/ for one-time operations
|
|
868
|
+
⛔ NEVER add error handling for scenarios that can't happen
|
|
869
|
+
⛔ NEVER refactor surrounding code when fixing a bug
|
|
870
|
+
⛔ NEVER add comments to code that is self-explanatory
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### Batched Operations (from Lovable — reduces tool call waste)
|
|
874
|
+
|
|
875
|
+
```
|
|
876
|
+
COMBINE operations that can run together:
|
|
877
|
+
|
|
878
|
+
<good-example>
|
|
879
|
+
Read 3 files in ONE message (parallel):
|
|
880
|
+
Read src/features/product/ProductScreen.tsx
|
|
881
|
+
Read src/features/product/productService.ts
|
|
882
|
+
Read src/features/product/product.types.ts
|
|
883
|
+
</good-example>
|
|
884
|
+
|
|
885
|
+
<bad-example>
|
|
886
|
+
Read file 1 → wait → Read file 2 → wait → Read file 3
|
|
887
|
+
(3 round-trips instead of 1)
|
|
888
|
+
</bad-example>
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
### Error Recovery with Escalation (from Cursor, Claude Code)
|
|
892
|
+
|
|
893
|
+
```
|
|
894
|
+
ATTEMPT 1: Auto-fix (missing imports, type errors, lint)
|
|
895
|
+
ATTEMPT 2: Read related files, check dependencies
|
|
896
|
+
ATTEMPT 3: Try alternative approach
|
|
897
|
+
ATTEMPT 4: STOP → present options to user
|
|
898
|
+
|
|
899
|
+
⛔ NEVER: loop on same error 5+ times
|
|
900
|
+
⛔ NEVER: suppress errors to make tests pass
|
|
901
|
+
✅ If corrected twice on same issue → /clear and restart with better prompt
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
---
|
|
905
|
+
|
|
655
906
|
## Quick Reference
|
|
656
907
|
|
|
657
908
|
### Good Prompt Checklist
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# Mobile Storage Patterns
|
|
2
|
+
|
|
3
|
+
> On-device storage — when to use what, how to implement correctly.
|
|
4
|
+
> Covers: AsyncStorage, MMKV, SecureStore/Keychain, SQLite, WatermelonDB, Realm, SharedPreferences.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Decision Matrix — Pick Storage Type First
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
WHAT ARE YOU STORING? → STORAGE TO USE
|
|
12
|
+
────────────────────────────────────────────────────────────────────
|
|
13
|
+
Auth tokens / secrets → SecureStore (RN) / Keychain (iOS)
|
|
14
|
+
EncryptedSharedPreferences (Android)
|
|
15
|
+
flutter_secure_storage (Flutter)
|
|
16
|
+
|
|
17
|
+
App preferences / settings → MMKV (RN, fast KV)
|
|
18
|
+
(theme, language, onboarding) SharedPreferences (Android native)
|
|
19
|
+
UserDefaults (iOS native)
|
|
20
|
+
shared_preferences (Flutter)
|
|
21
|
+
|
|
22
|
+
Simple key-value cache → MMKV (RN) / MMKV (Flutter)
|
|
23
|
+
(session data, small objects)
|
|
24
|
+
|
|
25
|
+
Structured relational data → SQLite (via expo-sqlite / sqflite)
|
|
26
|
+
(offline CRUD, complex queries) WatermelonDB (RN, reactive queries)
|
|
27
|
+
drift (Flutter, type-safe)
|
|
28
|
+
|
|
29
|
+
Large offline datasets → WatermelonDB (RN)
|
|
30
|
+
(sync with server, observables) drift (Flutter)
|
|
31
|
+
Room (Android native)
|
|
32
|
+
CoreData / SwiftData (iOS native)
|
|
33
|
+
Realm (cross-platform)
|
|
34
|
+
|
|
35
|
+
Files / images / documents → FileSystem (expo-file-system / path_provider)
|
|
36
|
+
AsyncStorage ❌ (NOT for binary data)
|
|
37
|
+
|
|
38
|
+
⛔ RULE: AsyncStorage is deprecated for RN. Use MMKV instead.
|
|
39
|
+
⛔ RULE: NEVER store tokens in AsyncStorage / SharedPreferences / UserDefaults.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## React Native
|
|
45
|
+
|
|
46
|
+
### 1. Secure Storage (Tokens, Credentials)
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// expo-secure-store (Expo) / react-native-keychain (bare RN)
|
|
50
|
+
import * as SecureStore from 'expo-secure-store';
|
|
51
|
+
|
|
52
|
+
// Store
|
|
53
|
+
await SecureStore.setItemAsync('accessToken', token);
|
|
54
|
+
|
|
55
|
+
// Read
|
|
56
|
+
const token = await SecureStore.getItemAsync('accessToken');
|
|
57
|
+
|
|
58
|
+
// Delete (on logout — ALWAYS do this)
|
|
59
|
+
await SecureStore.deleteItemAsync('accessToken');
|
|
60
|
+
await SecureStore.deleteItemAsync('refreshToken');
|
|
61
|
+
|
|
62
|
+
// RULE: On logout, delete ALL secure store keys
|
|
63
|
+
async function logout() {
|
|
64
|
+
await Promise.all([
|
|
65
|
+
SecureStore.deleteItemAsync('accessToken'),
|
|
66
|
+
SecureStore.deleteItemAsync('refreshToken'),
|
|
67
|
+
SecureStore.deleteItemAsync('userId'),
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. MMKV (Preferences + KV Cache) — 60x faster than AsyncStorage
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// react-native-mmkv
|
|
76
|
+
import { MMKV } from 'react-native-mmkv';
|
|
77
|
+
|
|
78
|
+
// Create instance (one per app, or per domain)
|
|
79
|
+
export const storage = new MMKV();
|
|
80
|
+
|
|
81
|
+
// Typed wrapper (recommended)
|
|
82
|
+
export const Storage = {
|
|
83
|
+
getString: (key: string) => storage.getString(key),
|
|
84
|
+
setString: (key: string, value: string) => storage.set(key, value),
|
|
85
|
+
getBoolean: (key: string) => storage.getBoolean(key) ?? false,
|
|
86
|
+
setBoolean: (key: string, value: boolean) => storage.set(key, value),
|
|
87
|
+
getObject: <T>(key: string): T | null => {
|
|
88
|
+
const raw = storage.getString(key);
|
|
89
|
+
return raw ? JSON.parse(raw) : null;
|
|
90
|
+
},
|
|
91
|
+
setObject: <T>(key: string, value: T) => storage.set(key, JSON.stringify(value)),
|
|
92
|
+
delete: (key: string) => storage.delete(key),
|
|
93
|
+
clear: () => storage.clearAll(),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// With Zustand persist (recommended combo)
|
|
97
|
+
import { create } from 'zustand';
|
|
98
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
99
|
+
|
|
100
|
+
const mmkvStorage = {
|
|
101
|
+
getItem: (name: string) => storage.getString(name) ?? null,
|
|
102
|
+
setItem: (name: string, value: string) => storage.set(name, value),
|
|
103
|
+
removeItem: (name: string) => storage.delete(name),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const useSettingsStore = create(
|
|
107
|
+
persist(
|
|
108
|
+
(set) => ({
|
|
109
|
+
theme: 'light',
|
|
110
|
+
language: 'en',
|
|
111
|
+
setTheme: (theme: string) => set({ theme }),
|
|
112
|
+
setLanguage: (lang: string) => set({ language: lang }),
|
|
113
|
+
}),
|
|
114
|
+
{ name: 'settings', storage: createJSONStorage(() => mmkvStorage) }
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 3. SQLite / WatermelonDB (Structured Offline Data)
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// expo-sqlite (simple queries)
|
|
123
|
+
import * as SQLite from 'expo-sqlite';
|
|
124
|
+
|
|
125
|
+
const db = SQLite.openDatabaseSync('app.db');
|
|
126
|
+
|
|
127
|
+
// Init schema
|
|
128
|
+
db.execSync(`
|
|
129
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
title TEXT NOT NULL,
|
|
132
|
+
completed INTEGER NOT NULL DEFAULT 0,
|
|
133
|
+
created_at INTEGER NOT NULL
|
|
134
|
+
)
|
|
135
|
+
`);
|
|
136
|
+
|
|
137
|
+
// Query
|
|
138
|
+
const tasks = db.getAllSync<Task>('SELECT * FROM tasks WHERE completed = ?', [0]);
|
|
139
|
+
|
|
140
|
+
// Insert
|
|
141
|
+
db.runSync('INSERT INTO tasks (id, title, completed, created_at) VALUES (?, ?, ?, ?)',
|
|
142
|
+
[uuid(), 'Buy milk', 0, Date.now()]);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
// WatermelonDB (reactive queries, sync-ready)
|
|
147
|
+
// Best for: large datasets, reactive UI, server sync
|
|
148
|
+
import { Database } from '@nozbe/watermelondb';
|
|
149
|
+
import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite';
|
|
150
|
+
|
|
151
|
+
const adapter = new SQLiteAdapter({ schema, migrations });
|
|
152
|
+
const database = new Database({ adapter, modelClasses: [Post, Comment] });
|
|
153
|
+
|
|
154
|
+
// Observe (reactive — auto re-renders on change)
|
|
155
|
+
const posts = database.get('posts').query().observe();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 4. Avoid AsyncStorage (Legacy)
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// ❌ DEPRECATED — avoid in new projects
|
|
162
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
163
|
+
|
|
164
|
+
// ✅ Migrate to MMKV:
|
|
165
|
+
// Before: await AsyncStorage.setItem('theme', 'dark');
|
|
166
|
+
// After: storage.set('theme', 'dark');
|
|
167
|
+
|
|
168
|
+
// ✅ Migrate to expo-secure-store for tokens:
|
|
169
|
+
// Before: await AsyncStorage.setItem('token', jwt);
|
|
170
|
+
// After: await SecureStore.setItemAsync('token', jwt);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Flutter
|
|
176
|
+
|
|
177
|
+
### 1. Secure Storage (Tokens)
|
|
178
|
+
|
|
179
|
+
```dart
|
|
180
|
+
// flutter_secure_storage
|
|
181
|
+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
182
|
+
|
|
183
|
+
final _secureStorage = FlutterSecureStorage(
|
|
184
|
+
aOptions: AndroidOptions(encryptedSharedPreferences: true),
|
|
185
|
+
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Store
|
|
189
|
+
await _secureStorage.write(key: 'accessToken', value: token);
|
|
190
|
+
|
|
191
|
+
// Read
|
|
192
|
+
final token = await _secureStorage.read(key: 'accessToken');
|
|
193
|
+
|
|
194
|
+
// Delete on logout
|
|
195
|
+
await _secureStorage.deleteAll();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. SharedPreferences / Hive (KV Storage)
|
|
199
|
+
|
|
200
|
+
```dart
|
|
201
|
+
// shared_preferences (simple, built-in)
|
|
202
|
+
final prefs = await SharedPreferences.getInstance();
|
|
203
|
+
await prefs.setString('language', 'en');
|
|
204
|
+
final lang = prefs.getString('language') ?? 'en';
|
|
205
|
+
|
|
206
|
+
// Hive (faster, type-safe, no codegen)
|
|
207
|
+
import 'package:hive_flutter/hive_flutter.dart';
|
|
208
|
+
|
|
209
|
+
await Hive.initFlutter();
|
|
210
|
+
final box = await Hive.openBox('settings');
|
|
211
|
+
box.put('theme', 'dark');
|
|
212
|
+
final theme = box.get('theme', defaultValue: 'light');
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 3. Drift (SQLite, type-safe)
|
|
216
|
+
|
|
217
|
+
```dart
|
|
218
|
+
// drift — type-safe SQLite with code generation
|
|
219
|
+
@DriftDatabase(tables: [Tasks])
|
|
220
|
+
class AppDatabase extends _$AppDatabase {
|
|
221
|
+
AppDatabase() : super(_openConnection());
|
|
222
|
+
|
|
223
|
+
Stream<List<Task>> watchIncompleteTasks() =>
|
|
224
|
+
(select(tasks)..where((t) => t.completed.not())).watch();
|
|
225
|
+
|
|
226
|
+
Future insertTask(TasksCompanion task) => into(tasks).insert(task);
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## iOS Native (Swift)
|
|
233
|
+
|
|
234
|
+
```swift
|
|
235
|
+
// UserDefaults — preferences ONLY (not tokens)
|
|
236
|
+
UserDefaults.standard.set("en", forKey: "language")
|
|
237
|
+
let lang = UserDefaults.standard.string(forKey: "language") ?? "en"
|
|
238
|
+
|
|
239
|
+
// Keychain — tokens and secrets
|
|
240
|
+
import Security
|
|
241
|
+
|
|
242
|
+
func saveToKeychain(key: String, value: String) {
|
|
243
|
+
let data = value.data(using: .utf8)!
|
|
244
|
+
let query: [String: Any] = [
|
|
245
|
+
kSecClass as String: kSecClassGenericPassword,
|
|
246
|
+
kSecAttrAccount as String: key,
|
|
247
|
+
kSecValueData as String: data,
|
|
248
|
+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
249
|
+
]
|
|
250
|
+
SecItemDelete(query as CFDictionary)
|
|
251
|
+
SecItemAdd(query as CFDictionary, nil)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// SwiftData / CoreData — structured offline data
|
|
255
|
+
@Model class Task {
|
|
256
|
+
var id: UUID
|
|
257
|
+
var title: String
|
|
258
|
+
var completed: Bool
|
|
259
|
+
init(title: String) { self.id = UUID(); self.title = title; self.completed = false }
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Android Native (Kotlin)
|
|
266
|
+
|
|
267
|
+
```kotlin
|
|
268
|
+
// EncryptedSharedPreferences — tokens and secrets
|
|
269
|
+
val masterKey = MasterKey.Builder(context)
|
|
270
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()
|
|
271
|
+
val encryptedPrefs = EncryptedSharedPreferences.create(
|
|
272
|
+
context, "secure_prefs", masterKey,
|
|
273
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
274
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
275
|
+
)
|
|
276
|
+
encryptedPrefs.edit().putString("accessToken", token).apply()
|
|
277
|
+
|
|
278
|
+
// DataStore — preferences (replaces SharedPreferences)
|
|
279
|
+
val Context.dataStore by preferencesDataStore(name = "settings")
|
|
280
|
+
val LANGUAGE_KEY = stringPreferencesKey("language")
|
|
281
|
+
|
|
282
|
+
suspend fun saveLanguage(context: Context, lang: String) {
|
|
283
|
+
context.dataStore.edit { it[LANGUAGE_KEY] = lang }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
val languageFlow = context.dataStore.data.map { it[LANGUAGE_KEY] ?: "en" }
|
|
287
|
+
|
|
288
|
+
// Room — structured offline data
|
|
289
|
+
@Entity data class Task(@PrimaryKey val id: String, val title: String, val completed: Boolean)
|
|
290
|
+
@Dao interface TaskDao {
|
|
291
|
+
@Query("SELECT * FROM task WHERE completed = 0") fun getActive(): Flow<List<Task>>
|
|
292
|
+
@Insert suspend fun insert(task: Task)
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Security Checklist
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
✅ Tokens → SecureStore / Keychain / EncryptedSharedPreferences ONLY
|
|
302
|
+
✅ On logout → delete ALL secure storage keys
|
|
303
|
+
✅ Encrypt sensitive data before storing in SQLite/MMKV
|
|
304
|
+
✅ Don't log stored values (console.log, print)
|
|
305
|
+
✅ Use device-only accessibility (not iCloud sync for tokens)
|
|
306
|
+
|
|
307
|
+
⛔ NEVER: AsyncStorage for tokens
|
|
308
|
+
⛔ NEVER: UserDefaults for tokens
|
|
309
|
+
⛔ NEVER: SharedPreferences (unencrypted) for tokens
|
|
310
|
+
⛔ NEVER: Log token values in debug output
|
|
311
|
+
⛔ NEVER: Store plain-text passwords
|
|
312
|
+
```
|