skillstore-cli 1.0.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/README.md +95 -0
- package/data/bundles/devflow-complete.json +19 -0
- package/data/free-skills/devflow-agile/manifest.json +19 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/retro.md +23 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/review.md +21 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/sprint.md +30 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/standup.md +20 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile.md +35 -0
- package/data/free-skills/devflow-agile/plugin/commands/devflow.md +42 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/SKILL.md +93 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/assets/sample-output.md +182 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/clean-architecture.md +361 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/clean-code-guide.md +207 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/debugging-methodology.md +191 -0
- package/data/free-skills/devflow-agile/template/agents/agile-coach.md +76 -0
- package/data/free-skills/devflow-agile/template/workflows/agile-sprint-workflow.md +81 -0
- package/data/free-skills/devflow-bootstrap/manifest.json +8 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/bootstrap/auto.md +31 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/bootstrap.md +38 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/devflow.md +20 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/SKILL.md +56 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/assets/sample-output.md +216 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/references/architecture-decisions.md +254 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/references/stack-templates.md +400 -0
- package/data/free-skills/devflow-bootstrap/template/agents/bootstrap-specialist.md +56 -0
- package/data/free-skills/devflow-bootstrap/template/workflows/bootstrap-workflow.md +70 -0
- package/data/free-skills/devflow-docs/manifest.json +8 -0
- package/data/free-skills/devflow-docs/plugin/commands/devflow.md +20 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs/generate.md +17 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs/parse.md +19 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs.md +26 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/SKILL.md +59 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/assets/sample-output.md +114 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/references/extraction-techniques.md +115 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/references/ocr-strategies.md +167 -0
- package/data/free-skills/devflow-docs/template/agents/docs-specialist.md +35 -0
- package/data/free-skills/devflow-docs/template/workflows/docs-workflow.md +70 -0
- package/data/free-skills/devflow-postproject/manifest.json +13 -0
- package/data/free-skills/devflow-postproject/plugin/commands/devflow.md +34 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/handover.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/retro.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/support.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject.md +32 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/SKILL.md +70 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/assets/sample-output.md +79 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/facilitation-techniques.md +178 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/lessons-learned-template.md +118 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/retro-techniques.md +100 -0
- package/data/free-skills/devflow-postproject/template/agents/transition-manager.md +71 -0
- package/data/free-skills/devflow-postproject/template/workflows/transition-workflow.md +72 -0
- package/data/free-skills/devflow-presale/manifest.json +15 -0
- package/data/free-skills/devflow-presale/plugin/commands/devflow.md +47 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/analyze.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/estimate.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/price.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/propose.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale.md +42 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/SKILL.md +63 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/assets/sample-output.md +129 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/references/extraction-framework.md +140 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/references/output-template.md +132 -0
- package/data/free-skills/devflow-presale/template/agents/presale-lead.md +83 -0
- package/data/free-skills/devflow-presale/template/agents/proposal-reviewer.md +63 -0
- package/data/free-skills/devflow-presale/template/workflows/presale-workflow.md +70 -0
- package/data/registry/categories.json +7 -0
- package/data/registry/packages.json +184 -0
- package/data/shared/framework/agents/brainstormer.md +74 -0
- package/data/shared/framework/agents/code-reviewer.md +87 -0
- package/data/shared/framework/agents/debugger.md +84 -0
- package/data/shared/framework/agents/docs-manager.md +55 -0
- package/data/shared/framework/agents/git-manager.md +59 -0
- package/data/shared/framework/agents/planner.md +68 -0
- package/data/shared/framework/agents/researcher.md +66 -0
- package/data/shared/framework/agents/tester.md +65 -0
- package/data/shared/framework/commands/cook/auto.md +27 -0
- package/data/shared/framework/commands/cook.md +45 -0
- package/data/shared/framework/commands/fix/ci.md +21 -0
- package/data/shared/framework/commands/fix/test.md +26 -0
- package/data/shared/framework/commands/fix/types.md +29 -0
- package/data/shared/framework/commands/fix.md +26 -0
- package/data/shared/framework/commands/git/cm.md +37 -0
- package/data/shared/framework/commands/git/pr.md +40 -0
- package/data/shared/framework/config/CLAUDE.md.template +26 -0
- package/data/shared/framework/config/settings.json +41 -0
- package/data/shared/framework/config/skillstore.config.json +29 -0
- package/data/shared/framework/hooks/discord-notify.sh +85 -0
- package/data/shared/framework/hooks/docs-sync.sh +53 -0
- package/data/shared/framework/hooks/modularization-hook.js +103 -0
- package/data/shared/framework/hooks/notification.js +94 -0
- package/data/shared/framework/hooks/quality-gate.js +109 -0
- package/data/shared/framework/hooks/scout-block.js +77 -0
- package/data/shared/framework/hooks/telegram-notify.sh +77 -0
- package/data/shared/framework/protocols/error-recovery.md +80 -0
- package/data/shared/framework/protocols/orchestration-protocol.md +112 -0
- package/data/shared/framework/quality/review-protocol.md +76 -0
- package/data/shared/framework/quality/verification-protocol.md +66 -0
- package/data/shared/framework/rules/development-rules.md +75 -0
- package/data/shared/framework/skills/backend-development/SKILL.md +77 -0
- package/data/shared/framework/skills/backend-development/assets/sample-output.md +175 -0
- package/data/shared/framework/skills/backend-development/references/advanced-patterns.md +180 -0
- package/data/shared/framework/skills/backend-development/references/api-design-guide.md +160 -0
- package/data/shared/framework/skills/backend-development/references/architecture-patterns.md +183 -0
- package/data/shared/framework/skills/backend-development/references/observability-resilience.md +155 -0
- package/data/shared/framework/skills/backend-development/references/troubleshooting.md +199 -0
- package/data/shared/framework/skills/codebase-analysis/SKILL.md +72 -0
- package/data/shared/framework/skills/codebase-analysis/assets/sample-output.md +263 -0
- package/data/shared/framework/skills/codebase-analysis/references/analysis-techniques.md +241 -0
- package/data/shared/framework/skills/codebase-analysis/references/dependency-mapping.md +280 -0
- package/data/shared/framework/skills/codebase-analysis/references/tech-debt-assessment.md +208 -0
- package/data/shared/framework/skills/databases/SKILL.md +72 -0
- package/data/shared/framework/skills/databases/assets/sample-output.md +212 -0
- package/data/shared/framework/skills/databases/references/advanced-data-patterns.md +259 -0
- package/data/shared/framework/skills/databases/references/query-optimization.md +214 -0
- package/data/shared/framework/skills/databases/references/schema-design.md +159 -0
- package/data/shared/framework/skills/databases/references/troubleshooting.md +214 -0
- package/data/shared/framework/skills/debugging-investigation/SKILL.md +84 -0
- package/data/shared/framework/skills/debugging-investigation/assets/sample-output.md +314 -0
- package/data/shared/framework/skills/debugging-investigation/references/systematic-debugging.md +197 -0
- package/data/shared/framework/skills/debugging-investigation/references/tool-specific-guides.md +202 -0
- package/data/shared/framework/skills/debugging-investigation/references/troubleshooting-patterns.md +196 -0
- package/data/shared/framework/skills/frontend-development/SKILL.md +67 -0
- package/data/shared/framework/skills/frontend-development/assets/sample-output.md +110 -0
- package/data/shared/framework/skills/frontend-development/references/component-patterns.md +112 -0
- package/data/shared/framework/skills/frontend-development/references/performance-guide.md +169 -0
- package/data/shared/framework/skills/frontend-development/references/routing-forms-realtime.md +374 -0
- package/data/shared/framework/skills/frontend-development/references/ssr-rsc-patterns.md +284 -0
- package/data/shared/framework/skills/frontend-development/references/troubleshooting.md +154 -0
- package/data/shared/framework/skills/mobile-development/SKILL.md +67 -0
- package/data/shared/framework/skills/mobile-development/assets/sample-output.md +382 -0
- package/data/shared/framework/skills/mobile-development/references/mobile-patterns.md +681 -0
- package/data/shared/framework/skills/mobile-development/references/mobile-performance.md +524 -0
- package/data/shared/framework/skills/mobile-development/references/troubleshooting.md +158 -0
- package/data/shared/framework/skills/security-audit/SKILL.md +83 -0
- package/data/shared/framework/skills/security-audit/assets/sample-output.md +451 -0
- package/data/shared/framework/skills/security-audit/references/owasp-checklist.md +580 -0
- package/data/shared/framework/skills/security-audit/references/secure-coding-patterns.md +433 -0
- package/data/shared/framework/skills/security-audit/references/vulnerability-remediation.md +331 -0
- package/data/shared/framework/skills/ui-generation/SKILL.md +70 -0
- package/data/shared/framework/skills/ui-generation/assets/sample-output.md +139 -0
- package/data/shared/framework/skills/ui-generation/references/accessibility-responsive.md +127 -0
- package/data/shared/framework/skills/ui-generation/references/compound-components.md +252 -0
- package/data/shared/framework/skills/ui-generation/references/generation-patterns.md +110 -0
- package/data/shared/framework/skills/ui-generation/references/storybook-design-system.md +278 -0
- package/data/shared/framework/skills/ui-generation/references/troubleshooting.md +198 -0
- package/data/shared/framework/workflows/documentation-management.md +58 -0
- package/data/shared/framework/workflows/primary-workflow.md +88 -0
- package/dist/commands/activate.d.ts +3 -0
- package/dist/commands/activate.d.ts.map +1 -0
- package/dist/commands/activate.js +34 -0
- package/dist/commands/activate.js.map +1 -0
- package/dist/commands/bundle.d.ts +3 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +64 -0
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +99 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +37 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +30 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +68 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/download/cache.d.ts +3 -0
- package/dist/download/cache.d.ts.map +1 -0
- package/dist/download/cache.js +18 -0
- package/dist/download/cache.js.map +1 -0
- package/dist/download/client.d.ts +2 -0
- package/dist/download/client.d.ts.map +1 -0
- package/dist/download/client.js +58 -0
- package/dist/download/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/file-copier.d.ts +6 -0
- package/dist/installer/file-copier.d.ts.map +1 -0
- package/dist/installer/file-copier.js +32 -0
- package/dist/installer/file-copier.js.map +1 -0
- package/dist/installer/plugin-installer.d.ts +12 -0
- package/dist/installer/plugin-installer.d.ts.map +1 -0
- package/dist/installer/plugin-installer.js +33 -0
- package/dist/installer/plugin-installer.js.map +1 -0
- package/dist/installer/template-installer.d.ts +12 -0
- package/dist/installer/template-installer.d.ts.map +1 -0
- package/dist/installer/template-installer.js +45 -0
- package/dist/installer/template-installer.js.map +1 -0
- package/dist/license/crypto.d.ts +16 -0
- package/dist/license/crypto.d.ts.map +1 -0
- package/dist/license/crypto.js +50 -0
- package/dist/license/crypto.js.map +1 -0
- package/dist/license/license-store.d.ts +19 -0
- package/dist/license/license-store.d.ts.map +1 -0
- package/dist/license/license-store.js +99 -0
- package/dist/license/license-store.js.map +1 -0
- package/dist/license/validator.d.ts +32 -0
- package/dist/license/validator.d.ts.map +1 -0
- package/dist/license/validator.js +81 -0
- package/dist/license/validator.js.map +1 -0
- package/dist/registry/loader.d.ts +30 -0
- package/dist/registry/loader.d.ts.map +1 -0
- package/dist/registry/loader.js +22 -0
- package/dist/registry/loader.js.map +1 -0
- package/dist/registry/search-engine.d.ts +9 -0
- package/dist/registry/search-engine.d.ts.map +1 -0
- package/dist/registry/search-engine.js +30 -0
- package/dist/registry/search-engine.js.map +1 -0
- package/dist/utils/config.d.ts +14 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +28 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +20 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +79 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# Mobile Development Patterns
|
|
2
|
+
|
|
3
|
+
## React Native Patterns
|
|
4
|
+
|
|
5
|
+
### Functional Components with Hooks
|
|
6
|
+
All components should be functional. Use hooks for lifecycle and side effects.
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
// Screen component with data fetching
|
|
10
|
+
function OrderListScreen() {
|
|
11
|
+
const navigation = useNavigation<OrderStackNav>();
|
|
12
|
+
const { data, isLoading, refetch } = useOrders();
|
|
13
|
+
const insets = useSafeAreaInsets();
|
|
14
|
+
|
|
15
|
+
if (isLoading) return <OrderListSkeleton />;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<View style={{ paddingTop: insets.top }}>
|
|
19
|
+
<FlatList
|
|
20
|
+
data={data}
|
|
21
|
+
renderItem={({ item }) => (
|
|
22
|
+
<OrderCard
|
|
23
|
+
order={item}
|
|
24
|
+
onPress={() => navigation.navigate('OrderDetail', { id: item.id })}
|
|
25
|
+
/>
|
|
26
|
+
)}
|
|
27
|
+
keyExtractor={(item) => item.id}
|
|
28
|
+
onRefresh={refetch}
|
|
29
|
+
refreshing={isLoading}
|
|
30
|
+
/>
|
|
31
|
+
</View>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Custom Hooks for Business Logic
|
|
37
|
+
Separate data logic from UI. Every hook has a single responsibility.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// useOrders.ts — encapsulates order fetching, caching, and mutation
|
|
41
|
+
export function useOrders(filters?: OrderFilters) {
|
|
42
|
+
return useQuery({
|
|
43
|
+
queryKey: ['orders', filters],
|
|
44
|
+
queryFn: () => orderApi.list(filters),
|
|
45
|
+
staleTime: 30_000,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useCreateOrder() {
|
|
50
|
+
const queryClient = useQueryClient();
|
|
51
|
+
return useMutation({
|
|
52
|
+
mutationFn: orderApi.create,
|
|
53
|
+
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['orders'] }),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Navigation (React Navigation)
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Type-safe navigation setup
|
|
62
|
+
type RootStackParamList = {
|
|
63
|
+
Home: undefined;
|
|
64
|
+
OrderDetail: { id: string };
|
|
65
|
+
Checkout: { cartId: string };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const Stack = createNativeStackNavigator<RootStackParamList>();
|
|
69
|
+
|
|
70
|
+
function AppNavigator() {
|
|
71
|
+
const { isAuthenticated } = useAuth();
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<Stack.Navigator>
|
|
75
|
+
{isAuthenticated ? (
|
|
76
|
+
<>
|
|
77
|
+
<Stack.Screen name="Home" component={HomeScreen} />
|
|
78
|
+
<Stack.Screen name="OrderDetail" component={OrderDetailScreen} />
|
|
79
|
+
<Stack.Screen
|
|
80
|
+
name="Checkout"
|
|
81
|
+
component={CheckoutScreen}
|
|
82
|
+
options={{ presentation: 'modal' }}
|
|
83
|
+
/>
|
|
84
|
+
</>
|
|
85
|
+
) : (
|
|
86
|
+
<Stack.Screen name="Login" component={LoginScreen} />
|
|
87
|
+
)}
|
|
88
|
+
</Stack.Navigator>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### State Management (Zustand)
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Lightweight global store — no boilerplate
|
|
97
|
+
interface CartStore {
|
|
98
|
+
items: CartItem[];
|
|
99
|
+
addItem: (item: CartItem) => void;
|
|
100
|
+
removeItem: (id: string) => void;
|
|
101
|
+
total: () => number;
|
|
102
|
+
clear: () => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const useCartStore = create<CartStore>((set, get) => ({
|
|
106
|
+
items: [],
|
|
107
|
+
addItem: (item) => set((s) => ({ items: [...s.items, item] })),
|
|
108
|
+
removeItem: (id) => set((s) => ({ items: s.items.filter((i) => i.id !== id) })),
|
|
109
|
+
total: () => get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
|
|
110
|
+
clear: () => set({ items: [] }),
|
|
111
|
+
}));
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Native Modules
|
|
115
|
+
When you need platform APIs not exposed by React Native:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Turbo Module (New Architecture)
|
|
119
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
120
|
+
|
|
121
|
+
interface HapticSpec extends TurboModule {
|
|
122
|
+
impact(style: 'light' | 'medium' | 'heavy'): void;
|
|
123
|
+
notification(type: 'success' | 'warning' | 'error'): void;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const HapticModule = TurboModuleRegistry.getEnforcing<HapticSpec>('Haptic');
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Rule**: Prefer community libraries (`react-native-haptic-feedback`) over writing native modules. Only write custom modules for proprietary SDK integrations.
|
|
130
|
+
|
|
131
|
+
## Flutter Patterns
|
|
132
|
+
|
|
133
|
+
### Widget Composition
|
|
134
|
+
Build complex UIs by composing small, focused widgets.
|
|
135
|
+
|
|
136
|
+
```dart
|
|
137
|
+
// Screen widget — orchestrates layout
|
|
138
|
+
class OrderListScreen extends StatelessWidget {
|
|
139
|
+
@override
|
|
140
|
+
Widget build(BuildContext context) {
|
|
141
|
+
return Scaffold(
|
|
142
|
+
appBar: AppBar(title: const Text('Orders')),
|
|
143
|
+
body: const OrderListBody(),
|
|
144
|
+
floatingActionButton: const NewOrderFab(),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Extracted widget — testable, reusable
|
|
150
|
+
class OrderCard extends StatelessWidget {
|
|
151
|
+
final Order order;
|
|
152
|
+
final VoidCallback onTap;
|
|
153
|
+
|
|
154
|
+
const OrderCard({required this.order, required this.onTap});
|
|
155
|
+
|
|
156
|
+
@override
|
|
157
|
+
Widget build(BuildContext context) {
|
|
158
|
+
return Card(
|
|
159
|
+
child: ListTile(
|
|
160
|
+
title: Text(order.title),
|
|
161
|
+
subtitle: Text(order.status.label),
|
|
162
|
+
trailing: Text('\$${order.total.toStringAsFixed(2)}'),
|
|
163
|
+
onTap: onTap,
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### State Management (Riverpod)
|
|
171
|
+
|
|
172
|
+
```dart
|
|
173
|
+
// Provider for order list
|
|
174
|
+
final ordersProvider = FutureProvider.autoDispose<List<Order>>((ref) {
|
|
175
|
+
return ref.read(orderRepositoryProvider).fetchOrders();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// In widget
|
|
179
|
+
class OrderListBody extends ConsumerWidget {
|
|
180
|
+
@override
|
|
181
|
+
Widget build(BuildContext context, WidgetRef ref) {
|
|
182
|
+
final ordersAsync = ref.watch(ordersProvider);
|
|
183
|
+
|
|
184
|
+
return ordersAsync.when(
|
|
185
|
+
data: (orders) => ListView.builder(
|
|
186
|
+
itemCount: orders.length,
|
|
187
|
+
itemBuilder: (_, i) => OrderCard(
|
|
188
|
+
order: orders[i],
|
|
189
|
+
onTap: () => context.push('/orders/${orders[i].id}'),
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
loading: () => const OrderListSkeleton(),
|
|
193
|
+
error: (e, _) => ErrorRetry(onRetry: () => ref.invalidate(ordersProvider)),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### BLoC Pattern (for complex event-driven flows)
|
|
200
|
+
|
|
201
|
+
```dart
|
|
202
|
+
// Events
|
|
203
|
+
sealed class CartEvent {}
|
|
204
|
+
class AddToCart extends CartEvent { final Product product; AddToCart(this.product); }
|
|
205
|
+
class RemoveFromCart extends CartEvent { final String productId; RemoveFromCart(this.productId); }
|
|
206
|
+
|
|
207
|
+
// State
|
|
208
|
+
class CartState {
|
|
209
|
+
final List<CartItem> items;
|
|
210
|
+
double get total => items.fold(0, (sum, i) => sum + i.price * i.quantity);
|
|
211
|
+
const CartState({this.items = const []});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// BLoC
|
|
215
|
+
class CartBloc extends Bloc<CartEvent, CartState> {
|
|
216
|
+
CartBloc() : super(const CartState()) {
|
|
217
|
+
on<AddToCart>((event, emit) {
|
|
218
|
+
emit(CartState(items: [...state.items, CartItem.fromProduct(event.product)]));
|
|
219
|
+
});
|
|
220
|
+
on<RemoveFromCart>((event, emit) {
|
|
221
|
+
emit(CartState(items: state.items.where((i) => i.productId != event.productId).toList()));
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Navigation (GoRouter)
|
|
228
|
+
|
|
229
|
+
```dart
|
|
230
|
+
final router = GoRouter(
|
|
231
|
+
initialLocation: '/',
|
|
232
|
+
redirect: (context, state) {
|
|
233
|
+
final isAuth = ref.read(authProvider).isAuthenticated;
|
|
234
|
+
if (!isAuth && !state.matchedLocation.startsWith('/login')) return '/login';
|
|
235
|
+
return null;
|
|
236
|
+
},
|
|
237
|
+
routes: [
|
|
238
|
+
GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
|
|
239
|
+
GoRoute(
|
|
240
|
+
path: '/orders/:id',
|
|
241
|
+
builder: (_, state) => OrderDetailScreen(id: state.pathParameters['id']!),
|
|
242
|
+
),
|
|
243
|
+
ShellRoute(
|
|
244
|
+
builder: (_, __, child) => MainShell(child: child),
|
|
245
|
+
routes: [
|
|
246
|
+
GoRoute(path: '/home', builder: (_, __) => const HomeTab()),
|
|
247
|
+
GoRoute(path: '/search', builder: (_, __) => const SearchTab()),
|
|
248
|
+
GoRoute(path: '/profile', builder: (_, __) => const ProfileTab()),
|
|
249
|
+
],
|
|
250
|
+
),
|
|
251
|
+
],
|
|
252
|
+
);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Platform Channels (for native interop)
|
|
256
|
+
|
|
257
|
+
```dart
|
|
258
|
+
// Dart side
|
|
259
|
+
class NativePayment {
|
|
260
|
+
static const _channel = MethodChannel('com.app/payment');
|
|
261
|
+
|
|
262
|
+
static Future<PaymentResult> processPayment(PaymentRequest req) async {
|
|
263
|
+
final result = await _channel.invokeMethod('processPayment', req.toJson());
|
|
264
|
+
return PaymentResult.fromJson(result);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Expo vs Bare Workflow
|
|
270
|
+
|
|
271
|
+
### Decision Matrix
|
|
272
|
+
|
|
273
|
+
| Factor | Expo Managed | Bare Workflow |
|
|
274
|
+
|--------|-------------|---------------|
|
|
275
|
+
| **Setup time** | Minutes | Hours (Xcode + Android Studio) |
|
|
276
|
+
| **OTA updates** | EAS Update built-in | Manual CodePush setup |
|
|
277
|
+
| **Native modules** | Expo Modules API | Direct Turbo Modules |
|
|
278
|
+
| **CI/CD** | EAS Build (cloud) | Self-hosted or Fastlane |
|
|
279
|
+
| **Custom native code** | Via config plugins or dev client | Full native access |
|
|
280
|
+
| **App size** | Larger (includes Expo runtime) | Smaller (only what you use) |
|
|
281
|
+
| **Upgrade path** | `npx expo install` | Manual RN upgrade (can be painful) |
|
|
282
|
+
|
|
283
|
+
**Start with Expo Managed** unless you need:
|
|
284
|
+
- A specific native SDK with no Expo wrapper (rare — check expo packages first)
|
|
285
|
+
- Custom build configurations not supported by config plugins
|
|
286
|
+
- App size under 15MB (games, utilities)
|
|
287
|
+
|
|
288
|
+
### Expo Router (File-Based Routing)
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
app/
|
|
292
|
+
├── _layout.tsx ← Root layout (providers, fonts)
|
|
293
|
+
├── (tabs)/
|
|
294
|
+
│ ├── _layout.tsx ← Tab navigator
|
|
295
|
+
│ ├── index.tsx ← Home tab
|
|
296
|
+
│ ├── search.tsx ← Search tab
|
|
297
|
+
│ └── profile.tsx ← Profile tab
|
|
298
|
+
├── restaurant/
|
|
299
|
+
│ └── [id].tsx ← Dynamic route /restaurant/123
|
|
300
|
+
├── checkout/
|
|
301
|
+
│ ├── _layout.tsx ← Checkout stack
|
|
302
|
+
│ ├── cart.tsx
|
|
303
|
+
│ └── payment.tsx
|
|
304
|
+
└── +not-found.tsx ← 404 screen
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// app/restaurant/[id].tsx
|
|
309
|
+
import { useLocalSearchParams } from 'expo-router';
|
|
310
|
+
|
|
311
|
+
export default function RestaurantScreen() {
|
|
312
|
+
const { id } = useLocalSearchParams<{ id: string }>();
|
|
313
|
+
// fetch restaurant by id...
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### EAS Build + EAS Update
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# Build for internal testing
|
|
321
|
+
eas build --platform all --profile preview
|
|
322
|
+
|
|
323
|
+
# OTA update (no app store review needed)
|
|
324
|
+
eas update --branch production --message "Fix cart total calculation"
|
|
325
|
+
|
|
326
|
+
# Build for store submission
|
|
327
|
+
eas build --platform all --profile production
|
|
328
|
+
eas submit --platform all
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**OTA Update Rules**:
|
|
332
|
+
- JS-only changes: OTA safe (bug fixes, UI tweaks, copy changes)
|
|
333
|
+
- Native dependency changes: requires new build (new SDK, permission changes)
|
|
334
|
+
- Always test OTA updates on preview branch before production
|
|
335
|
+
|
|
336
|
+
### Crash Reporting Setup
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// Sentry integration (works with Expo)
|
|
340
|
+
import * as Sentry from '@sentry/react-native';
|
|
341
|
+
|
|
342
|
+
Sentry.init({
|
|
343
|
+
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
|
|
344
|
+
tracesSampleRate: 0.2, // 20% of transactions
|
|
345
|
+
profilesSampleRate: 0.1,
|
|
346
|
+
enableAutoSessionTracking: true,
|
|
347
|
+
attachStacktrace: true,
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Wrap root component
|
|
351
|
+
export default Sentry.wrap(App);
|
|
352
|
+
|
|
353
|
+
// Manual breadcrumbs for important actions
|
|
354
|
+
Sentry.addBreadcrumb({
|
|
355
|
+
category: 'order',
|
|
356
|
+
message: `Order placed: ${orderId}`,
|
|
357
|
+
level: 'info',
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Source Maps**: EAS Build automatically uploads source maps to Sentry when configured:
|
|
362
|
+
```json
|
|
363
|
+
// app.json
|
|
364
|
+
{
|
|
365
|
+
"expo": {
|
|
366
|
+
"plugins": ["@sentry/react-native/expo"]
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Native Module Decision Tree
|
|
372
|
+
|
|
373
|
+
```
|
|
374
|
+
Need native functionality?
|
|
375
|
+
│
|
|
376
|
+
├─ Check expo packages first (expo-camera, expo-location, etc.)
|
|
377
|
+
│ → Found? Use it. Done.
|
|
378
|
+
│
|
|
379
|
+
├─ Check community libraries (react-native-*)
|
|
380
|
+
│ → Found + maintained? Use it. Done.
|
|
381
|
+
│
|
|
382
|
+
├─ Expo Modules API (write once, both platforms)
|
|
383
|
+
│ → Preferred for new modules. Swift + Kotlin.
|
|
384
|
+
│
|
|
385
|
+
└─ Turbo Modules (React Native core)
|
|
386
|
+
→ For performance-critical or complex native code.
|
|
387
|
+
→ Requires C++ for shared implementation.
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Cross-Platform Considerations
|
|
391
|
+
|
|
392
|
+
### Shared Logic vs Platform-Specific UI
|
|
393
|
+
|
|
394
|
+
```
|
|
395
|
+
Shared (80%+): Platform-specific (20%):
|
|
396
|
+
├── Business logic ├── Navigation transitions
|
|
397
|
+
├── API clients ├── Date/time pickers
|
|
398
|
+
├── State management ├── Action sheets vs bottom sheets
|
|
399
|
+
├── Data models ├── Pull-to-refresh behavior
|
|
400
|
+
├── Validation rules ├── Haptic feedback patterns
|
|
401
|
+
└── Utility functions └── System settings integration
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Decision Matrix
|
|
405
|
+
|
|
406
|
+
| Concern | React Native Approach | Flutter Approach |
|
|
407
|
+
|---------|----------------------|------------------|
|
|
408
|
+
| Navigation | React Navigation | GoRouter |
|
|
409
|
+
| State (simple) | Zustand / Jotai | Riverpod |
|
|
410
|
+
| State (complex) | Redux Toolkit | BLoC |
|
|
411
|
+
| Server state | TanStack Query | Riverpod + Repository |
|
|
412
|
+
| Local DB | WatermelonDB / MMKV | Drift / Hive |
|
|
413
|
+
| Styling | StyleSheet + Nativewind | Theme + custom widgets |
|
|
414
|
+
| Native access | Turbo Modules | Platform channels |
|
|
415
|
+
| Testing | Jest + Detox | flutter_test + integration_test |
|
|
416
|
+
|
|
417
|
+
## Navigation Patterns
|
|
418
|
+
|
|
419
|
+
### Stack Navigation
|
|
420
|
+
Primary screens push onto a stack. Use for drill-down flows.
|
|
421
|
+
```
|
|
422
|
+
Home → Product List → Product Detail → Add to Cart
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Tab Navigation
|
|
426
|
+
Top-level sections. 3-5 tabs maximum. Each tab maintains its own stack.
|
|
427
|
+
```
|
|
428
|
+
┌─────────┬──────────┬─────────┬──────────┐
|
|
429
|
+
│ Home │ Search │ Orders │ Profile │
|
|
430
|
+
└─────────┴──────────┴─────────┴──────────┘
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Drawer Navigation
|
|
434
|
+
Side menu for apps with many sections. Less common in modern apps — prefer tabs.
|
|
435
|
+
|
|
436
|
+
### Deep Linking
|
|
437
|
+
Essential for push notification taps, email links, and sharing.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
// React Native — linking config
|
|
441
|
+
const linking = {
|
|
442
|
+
prefixes: ['myapp://', 'https://myapp.com'],
|
|
443
|
+
config: {
|
|
444
|
+
screens: {
|
|
445
|
+
Home: '',
|
|
446
|
+
OrderDetail: 'orders/:id',
|
|
447
|
+
Checkout: 'checkout/:cartId',
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Test deep links**: `npx uri-scheme open myapp://orders/123 --ios` or `adb shell am start -d "myapp://orders/123"`.
|
|
454
|
+
|
|
455
|
+
## State Management Comparison
|
|
456
|
+
|
|
457
|
+
Pick the simplest option that satisfies your needs:
|
|
458
|
+
|
|
459
|
+
| Complexity | RN Solution | Flutter Solution | When |
|
|
460
|
+
|-----------|-------------|------------------|------|
|
|
461
|
+
| Single component | `useState` | `StatefulWidget` / `ValueNotifier` | Toggle, input, animation |
|
|
462
|
+
| Parent-child | Props + callbacks | Constructor params + callbacks | Shared between 2-3 widgets |
|
|
463
|
+
| Feature scope | Context + `useReducer` | `Riverpod` scoped provider | Auth, theme, feature settings |
|
|
464
|
+
| Global | Zustand / Redux Toolkit | BLoC / Riverpod global | Cart, notifications, complex flows |
|
|
465
|
+
| Server data | TanStack Query | Riverpod `FutureProvider` | API data with cache + revalidation |
|
|
466
|
+
|
|
467
|
+
**Rule**: If data comes from an API, use a server-state library first. Only copy to global state if multiple features need to mutate it independently.
|
|
468
|
+
|
|
469
|
+
## Offline-First Patterns
|
|
470
|
+
|
|
471
|
+
### Architecture
|
|
472
|
+
|
|
473
|
+
```
|
|
474
|
+
┌─────────────────────────────────────────┐
|
|
475
|
+
│ UI Layer │
|
|
476
|
+
├─────────────────────────────────────────┤
|
|
477
|
+
│ Repository Layer │
|
|
478
|
+
│ ┌──────────┐ ┌──────────────────────┐ │
|
|
479
|
+
│ │ Local DB │←→│ Sync Engine │ │
|
|
480
|
+
│ │ (source │ │ ┌──────────────────┐ │ │
|
|
481
|
+
│ │ of truth)│ │ │ Pending Queue │ │ │
|
|
482
|
+
│ └──────────┘ │ │ Conflict Resolver│ │ │
|
|
483
|
+
│ │ │ Retry Manager │ │ │
|
|
484
|
+
│ │ └──────────────────┘ │ │
|
|
485
|
+
│ └───────────┬──────────┘ │
|
|
486
|
+
├────────────────────────────┼────────────┤
|
|
487
|
+
│ Remote API │
|
|
488
|
+
└─────────────────────────────────────────┘
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Sync Queue
|
|
492
|
+
Queue mutations when offline. Process when connection returns.
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
interface PendingAction {
|
|
496
|
+
id: string;
|
|
497
|
+
type: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
498
|
+
entity: string;
|
|
499
|
+
payload: any;
|
|
500
|
+
createdAt: number;
|
|
501
|
+
retryCount: number;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// On reconnect: process queue in order, handle conflicts
|
|
505
|
+
async function processSyncQueue(queue: PendingAction[]) {
|
|
506
|
+
for (const action of queue.sort((a, b) => a.createdAt - b.createdAt)) {
|
|
507
|
+
try {
|
|
508
|
+
await syncAction(action);
|
|
509
|
+
await removeFromQueue(action.id);
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (isConflict(error)) {
|
|
512
|
+
await resolveConflict(action, error);
|
|
513
|
+
} else if (action.retryCount < MAX_RETRIES) {
|
|
514
|
+
await incrementRetry(action.id);
|
|
515
|
+
} else {
|
|
516
|
+
await moveToDeadLetter(action);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Conflict Resolution Strategies
|
|
524
|
+
|
|
525
|
+
| Strategy | Description | Best For |
|
|
526
|
+
|----------|-------------|----------|
|
|
527
|
+
| Last write wins | Timestamp-based, latest change wins | Simple fields (name, status) |
|
|
528
|
+
| Server wins | Server version always takes precedence | Financial data, inventory |
|
|
529
|
+
| Client wins | Client version takes precedence | Draft content, user preferences |
|
|
530
|
+
| Manual merge | Show both versions, let user choose | Collaborative editing |
|
|
531
|
+
|
|
532
|
+
## Push Notifications
|
|
533
|
+
|
|
534
|
+
### Setup Checklist
|
|
535
|
+
|
|
536
|
+
1. **FCM/APNs configuration**: Register app with Firebase/Apple Developer portal
|
|
537
|
+
2. **Token management**: Store device token on backend, refresh on app launch
|
|
538
|
+
3. **Permission request**: Ask at a contextual moment, not on first launch
|
|
539
|
+
4. **Payload handling**: Handle notification when app is in foreground, background, and terminated
|
|
540
|
+
5. **Channel management** (Android): Create channels for different notification types
|
|
541
|
+
|
|
542
|
+
### Token Lifecycle
|
|
543
|
+
|
|
544
|
+
```typescript
|
|
545
|
+
// On app launch — always refresh token
|
|
546
|
+
async function initPushNotifications() {
|
|
547
|
+
const permission = await requestPermission();
|
|
548
|
+
if (permission !== 'granted') return;
|
|
549
|
+
|
|
550
|
+
const token = await getDeviceToken();
|
|
551
|
+
await api.registerDevice({ token, platform: Platform.OS });
|
|
552
|
+
|
|
553
|
+
// Listen for token refresh
|
|
554
|
+
onTokenRefresh((newToken) => {
|
|
555
|
+
api.registerDevice({ token: newToken, platform: Platform.OS });
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### Notification Handling
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
// Foreground — show in-app banner (not system notification)
|
|
564
|
+
onForegroundMessage((message) => {
|
|
565
|
+
showInAppNotification({
|
|
566
|
+
title: message.title,
|
|
567
|
+
body: message.body,
|
|
568
|
+
onPress: () => navigateToScreen(message.data),
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Background/terminated — user tapped notification
|
|
573
|
+
onNotificationOpened((message) => {
|
|
574
|
+
navigateToScreen(message.data);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// Data-only notifications — silent processing
|
|
578
|
+
onBackgroundMessage(async (message) => {
|
|
579
|
+
await syncDataInBackground(message.data);
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Permissions Handling
|
|
584
|
+
|
|
585
|
+
### Request Pattern
|
|
586
|
+
|
|
587
|
+
Never request permissions at app launch. Request at the moment of need with context.
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
async function requestCameraPermission(): Promise<boolean> {
|
|
591
|
+
const status = await check(PERMISSIONS.IOS.CAMERA);
|
|
592
|
+
|
|
593
|
+
switch (status) {
|
|
594
|
+
case 'granted':
|
|
595
|
+
return true;
|
|
596
|
+
case 'denied':
|
|
597
|
+
// First time — show system dialog
|
|
598
|
+
const result = await request(PERMISSIONS.IOS.CAMERA);
|
|
599
|
+
return result === 'granted';
|
|
600
|
+
case 'blocked':
|
|
601
|
+
// Previously denied — show explanation and link to settings
|
|
602
|
+
showPermissionDialog({
|
|
603
|
+
title: 'Camera Access Needed',
|
|
604
|
+
message: 'To scan barcodes, allow camera access in Settings.',
|
|
605
|
+
onOpenSettings: () => openSettings(),
|
|
606
|
+
});
|
|
607
|
+
return false;
|
|
608
|
+
default:
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Permissions Checklist
|
|
615
|
+
|
|
616
|
+
| Permission | When to Request | Fallback |
|
|
617
|
+
|-----------|----------------|----------|
|
|
618
|
+
| Camera | User taps "Scan" or "Take Photo" | Manual text input |
|
|
619
|
+
| Location | User opens map or delivery tracking | Manual address entry |
|
|
620
|
+
| Notifications | After first meaningful action (first order) | In-app notification center |
|
|
621
|
+
| Photo Library | User taps "Upload Photo" | Camera capture instead |
|
|
622
|
+
| Contacts | User taps "Invite Friends" | Share link manually |
|
|
623
|
+
|
|
624
|
+
## App Lifecycle Management
|
|
625
|
+
|
|
626
|
+
### States and Transitions
|
|
627
|
+
|
|
628
|
+
```
|
|
629
|
+
┌──────────┐ ┌────────────┐ ┌────────────┐
|
|
630
|
+
│ Launched │────→│ Foreground │←───→│ Background │
|
|
631
|
+
└──────────┘ └────────────┘ └─────┬──────┘
|
|
632
|
+
│
|
|
633
|
+
┌─────▼──────┐
|
|
634
|
+
│ Terminated │
|
|
635
|
+
└─────────────┘
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
### What to Do in Each State
|
|
639
|
+
|
|
640
|
+
| Transition | Action |
|
|
641
|
+
|-----------|--------|
|
|
642
|
+
| Launch → Foreground | Init services, check auth, fetch fresh data |
|
|
643
|
+
| Foreground → Background | Save draft state, pause timers, disconnect sockets |
|
|
644
|
+
| Background → Foreground | Refresh stale data, reconnect sockets, check auth |
|
|
645
|
+
| Background → Terminated | Persist critical state to local DB |
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
// React Native
|
|
649
|
+
useEffect(() => {
|
|
650
|
+
const sub = AppState.addEventListener('change', (state) => {
|
|
651
|
+
if (state === 'active') {
|
|
652
|
+
queryClient.refetchQueries({ type: 'active', stale: true });
|
|
653
|
+
reconnectWebSocket();
|
|
654
|
+
}
|
|
655
|
+
if (state === 'background') {
|
|
656
|
+
saveDraftState();
|
|
657
|
+
disconnectWebSocket();
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
return () => sub.remove();
|
|
661
|
+
}, []);
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
```dart
|
|
665
|
+
// Flutter
|
|
666
|
+
class _AppLifecycleState extends State<App> with WidgetsBindingObserver {
|
|
667
|
+
@override
|
|
668
|
+
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
669
|
+
switch (state) {
|
|
670
|
+
case AppLifecycleState.resumed:
|
|
671
|
+
ref.invalidate(ordersProvider); // Refresh data
|
|
672
|
+
break;
|
|
673
|
+
case AppLifecycleState.paused:
|
|
674
|
+
ref.read(draftProvider.notifier).saveToDisk();
|
|
675
|
+
break;
|
|
676
|
+
default:
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|