@buivietphi/skill-mobile-mt 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.
@@ -0,0 +1,208 @@
1
+ # Android Native — Production Patterns
2
+
3
+ > Battle-tested patterns for Android Kotlin development.
4
+ > Multi-module Gradle, Hilt DI, Compose UI, offline-first.
5
+ > Also reference for RN/Flutter Android-side issues.
6
+
7
+ ---
8
+
9
+ ## Clean Architecture (Multi-Module)
10
+
11
+ ```
12
+ project/
13
+ ├── app/ # Main application module
14
+ │ ├── src/main/
15
+ │ │ ├── java/com/company/app/
16
+ │ │ │ ├── di/ # Hilt modules
17
+ │ │ │ ├── presentation/
18
+ │ │ │ │ ├── features/
19
+ │ │ │ │ │ ├── auth/
20
+ │ │ │ │ │ │ ├── ui/ # Composables
21
+ │ │ │ │ │ │ └── viewmodel/
22
+ │ │ │ │ │ └── home/
23
+ │ │ │ │ ├── navigation/
24
+ │ │ │ │ └── theme/
25
+ │ │ │ └── domain/
26
+ │ │ │ ├── model/ # Domain entities
27
+ │ │ │ ├── usecase/ # Business rules
28
+ │ │ │ └── repository/ # Repository interfaces
29
+ │ │ ├── res/
30
+ │ │ └── AndroidManifest.xml
31
+ │ └── build.gradle.kts
32
+ ├── data/ # Data layer module
33
+ │ ├── src/main/java/
34
+ │ │ ├── repository/ # Repository implementations
35
+ │ │ ├── remote/ # API service, DTOs
36
+ │ │ ├── local/ # Room DAOs, entities
37
+ │ │ └── mapper/ # DTO ↔ Entity ↔ Domain mappers
38
+ │ └── build.gradle.kts
39
+ ├── common/ # Shared utilities module
40
+ │ └── src/main/java/
41
+ ├── build.gradle.kts # Root build file
42
+ ├── settings.gradle.kts # Module declarations
43
+ └── gradle/
44
+ └── libs.versions.toml # Version catalog
45
+ ```
46
+
47
+ ### Dependency Rule
48
+ ```
49
+ app (presentation) → domain/ ← data/
50
+
51
+ Presentation depends on Domain. Data depends on Domain.
52
+ Domain depends on NOTHING.
53
+ app module has access to all modules.
54
+ data module implements domain interfaces.
55
+ ```
56
+
57
+ ## Compose UI Pattern
58
+
59
+ ```kotlin
60
+ @Composable
61
+ fun ProductListScreen(
62
+ viewModel: ProductListViewModel = hiltViewModel(),
63
+ onProductClick: (String) -> Unit,
64
+ ) {
65
+ val uiState by viewModel.uiState.collectAsStateWithLifecycle()
66
+
67
+ Scaffold(
68
+ topBar = { TopAppBar(title = { Text("Products") }) },
69
+ ) { padding ->
70
+ when (val state = uiState) {
71
+ is UiState.Loading -> Box(Modifier.fillMaxSize(), Alignment.Center) {
72
+ CircularProgressIndicator()
73
+ }
74
+ is UiState.Empty -> EmptyContent()
75
+ is UiState.Error -> ErrorContent(state.message, onRetry = viewModel::load)
76
+ is UiState.Success -> LazyColumn(
77
+ modifier = Modifier.padding(padding),
78
+ contentPadding = PaddingValues(16.dp),
79
+ verticalArrangement = Arrangement.spacedBy(8.dp),
80
+ ) {
81
+ items(state.data, key = { it.id }) { product ->
82
+ ProductCard(product, onClick = { onProductClick(product.id) })
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ sealed interface UiState<out T> {
90
+ data object Loading : UiState<Nothing>
91
+ data object Empty : UiState<Nothing>
92
+ data class Success<T>(val data: T) : UiState<T>
93
+ data class Error(val message: String) : UiState<Nothing>
94
+ }
95
+ ```
96
+
97
+ ## ViewModel (Hilt)
98
+
99
+ ```kotlin
100
+ @HiltViewModel
101
+ class ProductListViewModel @Inject constructor(
102
+ private val getProducts: GetProductsUseCase,
103
+ ) : ViewModel() {
104
+ private val _uiState = MutableStateFlow<UiState<List<Product>>>(UiState.Loading)
105
+ val uiState = _uiState.asStateFlow()
106
+
107
+ init { load() }
108
+
109
+ fun load() {
110
+ viewModelScope.launch {
111
+ _uiState.value = UiState.Loading
112
+ getProducts()
113
+ .catch { _uiState.value = UiState.Error(it.message ?: "Error") }
114
+ .collect { items ->
115
+ _uiState.value = if (items.isEmpty()) UiState.Empty else UiState.Success(items)
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## DI (Hilt)
123
+
124
+ ```kotlin
125
+ @Module @InstallIn(SingletonComponent::class)
126
+ object NetworkModule {
127
+ @Provides @Singleton
128
+ fun provideRetrofit(): Retrofit = Retrofit.Builder()
129
+ .baseUrl(BuildConfig.API_BASE_URL)
130
+ .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
131
+ .build()
132
+ }
133
+
134
+ @Module @InstallIn(SingletonComponent::class)
135
+ abstract class RepositoryModule {
136
+ @Binds @Singleton
137
+ abstract fun bindProductRepo(impl: ProductRepositoryImpl): ProductRepository
138
+ }
139
+ ```
140
+
141
+ ## Data Layer (Retrofit + Room — Offline-First)
142
+
143
+ ```kotlin
144
+ // data/remote/ProductApi.kt
145
+ interface ProductApi {
146
+ @GET("products") suspend fun getProducts(): ApiResponse<List<ProductDto>>
147
+ }
148
+
149
+ // data/local/ProductDao.kt
150
+ @Dao interface ProductDao {
151
+ @Query("SELECT * FROM products") fun getAll(): Flow<List<ProductEntity>>
152
+ @Upsert suspend fun upsertAll(items: List<ProductEntity>)
153
+ }
154
+
155
+ // data/repository/ProductRepositoryImpl.kt
156
+ class ProductRepositoryImpl @Inject constructor(
157
+ private val api: ProductApi, private val dao: ProductDao,
158
+ ) : ProductRepository {
159
+ override fun getProducts(): Flow<List<Product>> = flow {
160
+ val cached = dao.getAll().first()
161
+ if (cached.isNotEmpty()) emit(cached.map { it.toDomain() })
162
+ try {
163
+ val fresh = api.getProducts()
164
+ dao.upsertAll(fresh.data.map { it.toEntity() })
165
+ } catch (e: Exception) { if (cached.isEmpty()) throw e }
166
+ emitAll(dao.getAll().map { it.map { e -> e.toDomain() } })
167
+ }
168
+ }
169
+ ```
170
+
171
+ ## Secure Storage
172
+
173
+ ```kotlin
174
+ val prefs = EncryptedSharedPreferences.create(
175
+ context, "secure_prefs",
176
+ MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
177
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
178
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
179
+ )
180
+ ```
181
+
182
+ ## Multi-Module Gradle Setup
183
+
184
+ ```kotlin
185
+ // settings.gradle.kts
186
+ include(":app", ":data", ":common")
187
+
188
+ // app/build.gradle.kts
189
+ dependencies {
190
+ implementation(project(":data"))
191
+ implementation(project(":common"))
192
+ }
193
+ ```
194
+
195
+ ## Common Pitfalls
196
+
197
+ | Pitfall | Fix |
198
+ |---------|-----|
199
+ | `!!` assertion | `?.` / `?:` / `requireNotNull` |
200
+ | `collectAsState` | `collectAsStateWithLifecycle()` |
201
+ | Context leak | `@ApplicationContext`, never Activity |
202
+ | Missing ProGuard | Test release builds |
203
+ | Main thread blocking | `Dispatchers.IO` |
204
+
205
+ ---
206
+
207
+ > Multi-module Gradle + Hilt + Compose + offline-first.
208
+ > Clean Architecture with domain module having zero dependencies.
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @buivietphi/skill-mobile installer
5
+ *
6
+ * Installs skill-mobile-mt/ folder with subfolders for each platform.
7
+ *
8
+ * Usage:
9
+ * npx @buivietphi/skill-mobile # Interactive
10
+ * npx @buivietphi/skill-mobile --all # All detected agents
11
+ * npx @buivietphi/skill-mobile --claude # Claude Code only
12
+ * npx @buivietphi/skill-mobile --gemini # Gemini CLI
13
+ * npx @buivietphi/skill-mobile --kimi # Kimi
14
+ * npx @buivietphi/skill-mobile --antigravity # Antigravity
15
+ * npx @buivietphi/skill-mobile --auto # Auto-detect (postinstall)
16
+ * npx @buivietphi/skill-mobile --path DIR # Custom path
17
+ */
18
+
19
+ import { existsSync, mkdirSync, cpSync, readFileSync } from 'node:fs';
20
+ import { join, resolve, dirname } from 'node:path';
21
+ import { homedir } from 'node:os';
22
+ import { fileURLToPath } from 'node:url';
23
+ import { createInterface } from 'node:readline';
24
+
25
+ const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ const PKG_ROOT = resolve(__dirname, '..');
27
+ const SKILL_NAME = 'skill-mobile-mt';
28
+ const HOME = homedir();
29
+
30
+ // Structure: root files + subfolders
31
+ const ROOT_FILES = ['SKILL.md', 'AGENTS.md'];
32
+
33
+ const SUBFOLDERS = {
34
+ 'react-native': ['react-native.md'],
35
+ 'flutter': ['flutter.md'],
36
+ 'ios': ['ios-native.md'],
37
+ 'android': ['android-native.md'],
38
+ 'shared': ['code-review.md', 'bug-detection.md', 'prompt-engineering.md', 'release-checklist.md', 'common-pitfalls.md', 'error-recovery.md', 'document-analysis.md', 'anti-patterns.md', 'performance-prediction.md', 'platform-excellence.md', 'version-management.md', 'observability.md', 'claude-md-template.md', 'agent-rules-template.md'],
39
+ };
40
+
41
+ const AGENTS = {
42
+ claude: { name: 'Claude Code', dir: join(HOME, '.claude', 'skills'), detect: () => existsSync(join(HOME, '.claude')) },
43
+ codex: { name: 'Codex', dir: join(HOME, '.codex', 'skills'), detect: () => existsSync(join(HOME, '.codex')) },
44
+ gemini: { name: 'Gemini CLI', dir: join(HOME, '.gemini', 'skills'), detect: () => existsSync(join(HOME, '.gemini')) },
45
+ kimi: { name: 'Kimi', dir: join(HOME, '.kimi', 'skills'), detect: () => existsSync(join(HOME, '.kimi')) },
46
+ antigravity: { name: 'Antigravity', dir: join(HOME, '.agents', 'skills'), detect: () => existsSync(join(HOME, '.agents')) },
47
+ cursor: { name: 'Cursor', dir: join(HOME, '.cursor', 'skills'), detect: () => existsSync(join(HOME, '.cursor')) },
48
+ windsurf: { name: 'Windsurf', dir: join(HOME, '.windsurf', 'skills'), detect: () => existsSync(join(HOME, '.windsurf')) },
49
+ copilot: { name: 'Copilot', dir: join(HOME, '.copilot', 'skills'), detect: () => existsSync(join(HOME, '.copilot')) },
50
+ };
51
+
52
+ const c = { reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', red: '\x1b[31m' };
53
+ const log = m => console.log(m);
54
+ const ok = m => log(` ${c.green}✓${c.reset} ${m}`);
55
+ const info = m => log(` ${c.blue}ℹ${c.reset} ${m}`);
56
+ const fail = m => log(` ${c.red}✗${c.reset} ${m}`);
57
+
58
+ function banner() {
59
+ log(`\n${c.bold}${c.cyan} ┌──────────────────────────────────────────────┐`);
60
+ log(` │ 📱 @buivietphi/skill-mobile-mt v1.0.0 │`);
61
+ log(` │ Master Senior Mobile Engineer │`);
62
+ log(` │ │`);
63
+ log(` │ Claude · Codex · Gemini · Kimi │`);
64
+ log(` │ Antigravity · Cursor · Windsurf · Copilot │`);
65
+ log(` │ React Native · Flutter · iOS · Android │`);
66
+ log(` └──────────────────────────────────────────────┘${c.reset}\n`);
67
+ }
68
+
69
+ function tokenCount(filePath) {
70
+ if (!existsSync(filePath)) return 0;
71
+ return Math.ceil(readFileSync(filePath, 'utf-8').length / 3.5);
72
+ }
73
+
74
+ function showContext() {
75
+ log(`${c.bold} 📊 Context:${c.reset}`);
76
+ let total = 0;
77
+
78
+ // Root files
79
+ for (const f of ROOT_FILES) {
80
+ const t = tokenCount(join(PKG_ROOT, f));
81
+ total += t;
82
+ log(` ${c.dim} ${f.padEnd(30)} ~${t.toLocaleString()} tokens${c.reset}`);
83
+ }
84
+
85
+ // Subfolders
86
+ for (const [folder, files] of Object.entries(SUBFOLDERS)) {
87
+ let folderTotal = 0;
88
+ for (const f of files) {
89
+ folderTotal += tokenCount(join(PKG_ROOT, folder, f));
90
+ }
91
+ total += folderTotal;
92
+ log(` ${c.dim} ${(folder + '/').padEnd(30)} ~${folderTotal.toLocaleString()} tokens${c.reset}`);
93
+ }
94
+
95
+ log(`${c.dim} ─────────────────────────────────────────${c.reset}`);
96
+ log(` ${c.bold} All loaded:${c.reset} ~${total.toLocaleString()} tokens`);
97
+ log(` ${c.green} Smart load (1 platform):${c.reset} ~${Math.ceil(total * 0.55).toLocaleString()} tokens\n`);
98
+ }
99
+
100
+ function install(baseDir, agentName) {
101
+ const dst = join(baseDir, SKILL_NAME);
102
+ mkdirSync(dst, { recursive: true });
103
+ let n = 0;
104
+
105
+ // Copy root files
106
+ for (const f of ROOT_FILES) {
107
+ const src = join(PKG_ROOT, f);
108
+ if (!existsSync(src)) continue;
109
+ cpSync(src, join(dst, f), { force: true });
110
+ n++;
111
+ }
112
+
113
+ // Copy subfolders
114
+ for (const [folder, files] of Object.entries(SUBFOLDERS)) {
115
+ const dstFolder = join(dst, folder);
116
+ mkdirSync(dstFolder, { recursive: true });
117
+ for (const f of files) {
118
+ const src = join(PKG_ROOT, folder, f);
119
+ if (!existsSync(src)) continue;
120
+ cpSync(src, join(dstFolder, f), { force: true });
121
+ n++;
122
+ }
123
+ }
124
+
125
+ ok(`${c.bold}${SKILL_NAME}/${c.reset} → ${agentName} ${c.dim}(${dst})${c.reset}`);
126
+ return n;
127
+ }
128
+
129
+ async function ask(q) {
130
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
131
+ return new Promise(r => { rl.question(q, a => { rl.close(); r(a.trim()); }); });
132
+ }
133
+
134
+ async function main() {
135
+ const args = process.argv.slice(2);
136
+ const flags = new Set(args.map(a => a.replace(/^--?/, '')));
137
+
138
+ banner();
139
+ showContext();
140
+
141
+ let targets = [];
142
+
143
+ if (flags.has('all')) targets = Object.keys(AGENTS);
144
+ else if (flags.has('auto')) {
145
+ targets = Object.keys(AGENTS).filter(k => AGENTS[k].detect());
146
+ if (!targets.length) { info('No agents found. Using Claude.'); targets = ['claude']; }
147
+ } else if (flags.has('path')) {
148
+ const p = args[args.indexOf('--path') + 1];
149
+ if (!p) { fail('--path needs dir'); process.exit(1); }
150
+ install(resolve(p), 'Custom');
151
+ log(`\n${c.green}${c.bold} ✅ Done!${c.reset}\n`); return;
152
+ } else {
153
+ for (const k of Object.keys(AGENTS)) if (flags.has(k)) targets.push(k);
154
+ }
155
+
156
+ if (!targets.length) {
157
+ const det = Object.keys(AGENTS).filter(k => AGENTS[k].detect());
158
+ log(`${c.bold} Detected agents:${c.reset}`);
159
+ det.forEach(k => log(` ${c.green}●${c.reset} ${AGENTS[k].name}`));
160
+ Object.keys(AGENTS).filter(k => !det.includes(k)).forEach(k => log(` ${c.dim}○ ${AGENTS[k].name}${c.reset}`));
161
+ log('');
162
+ const a = await ask(' Install to detected agents? [Y/n] ');
163
+ if (a.toLowerCase() === 'n') { info('Cancelled.'); return; }
164
+ targets = det.length ? det : ['claude'];
165
+ }
166
+
167
+ log(`\n${c.bold} Installing...${c.reset}\n`);
168
+ for (const k of targets) install(AGENTS[k].dir, AGENTS[k].name);
169
+
170
+ log(`\n${c.green}${c.bold} ✅ Done!${c.reset} → ${targets.length} agent(s)\n`);
171
+ log(` ${c.bold}Usage:${c.reset}`);
172
+ log(` ${c.cyan}@skill-mobile-mt${c.reset} Pre-built patterns (18 production apps)`);
173
+ log(` ${c.cyan}@skill-mobile-mt project${c.reset} Read current project, adapt to it\n`);
174
+ log(` ${c.bold}Installed structure:${c.reset}`);
175
+ log(` ${SKILL_NAME}/`);
176
+ log(` ├── SKILL.md Entry point + auto-detect`);
177
+ log(` ├── AGENTS.md Multi-agent config`);
178
+ log(` ├── react-native/`);
179
+ log(` │ └── react-native.md React Native patterns`);
180
+ log(` ├── flutter/`);
181
+ log(` │ └── flutter.md Flutter patterns`);
182
+ log(` ├── ios/`);
183
+ log(` │ └── ios-native.md iOS Swift patterns`);
184
+ log(` ├── android/`);
185
+ log(` │ └── android-native.md Android Kotlin patterns`);
186
+ log(` └── shared/`);
187
+ log(` ├── code-review.md Review checklist`);
188
+ log(` ├── bug-detection.md Bug scanner`);
189
+ log(` ├── prompt-engineering.md Auto-think`);
190
+ log(` ├── anti-patterns.md PII/cardinality detection`);
191
+ log(` ├── performance-prediction.md Frame budget calculator`);
192
+ log(` ├── platform-excellence.md iOS 18+ vs Android 15+`);
193
+ log(` ├── version-management.md SDK compatibility matrix`);
194
+ log(` ├── observability.md Sessions as 4th pillar`);
195
+ log(` ├── claude-md-template.md CLAUDE.md template for Claude Code`);
196
+ log(` └── agent-rules-template.md Rules template for ALL agents\n`);
197
+ }
198
+
199
+ main().catch(e => { fail(e.message); process.exit(1); });
@@ -0,0 +1,246 @@
1
+ # Flutter — Production Patterns
2
+
3
+ > Battle-tested patterns from production Flutter apps.
4
+ > State: Riverpod (standard)
5
+ > DI: get_it + injectable
6
+ > Networking: Dio + Retrofit, or http
7
+ > Navigation: GoRouter
8
+
9
+ ---
10
+
11
+ ## Clean Architecture
12
+
13
+ ```
14
+ lib/
15
+ ├── main.dart # Entry: Firebase init, Hive init, ProviderScope
16
+ ├── app/
17
+ │ ├── app.dart # MaterialApp.router
18
+ │ ├── router.dart # GoRouter config
19
+ │ └── theme/
20
+ ├── features/
21
+ │ ├── auth/
22
+ │ │ ├── domain/ # Entities + use cases + repository interfaces
23
+ │ │ ├── data/ # Repository impl, API client, DTOs, mappers
24
+ │ │ ├── presentation/ # Screens + widgets
25
+ │ │ └── providers/ # Riverpod providers
26
+ │ └── [feature]/
27
+ │ └── ... (same structure)
28
+ ├── shared/
29
+ │ ├── widgets/ # Reusable UI
30
+ │ ├── extensions/
31
+ │ └── utils/
32
+ └── core/
33
+ ├── network/ # Dio client setup, interceptors
34
+ ├── storage/ # Hive + SharedPrefs + flutter_secure_storage
35
+ ├── di/ # get_it + injectable setup
36
+ └── constants/
37
+ ```
38
+
39
+ ### Dependency Rule
40
+ ```
41
+ presentation/ → domain/ ← data/
42
+
43
+ Presentation depends on Domain. Data depends on Domain.
44
+ Domain depends on NOTHING.
45
+ Never import data/ from presentation/ directly.
46
+ Use cases call repository interfaces (defined in domain/).
47
+ Data layer provides implementations.
48
+ ```
49
+
50
+ ## State Management (Riverpod)
51
+
52
+ ```dart
53
+ // domain/usecases/get_products.dart
54
+ class GetProducts {
55
+ final ProductRepository repository;
56
+ GetProducts(this.repository);
57
+ Future<List<Product>> call() => repository.getProducts();
58
+ }
59
+
60
+ // features/product/providers/product_providers.dart
61
+ @riverpod
62
+ class ProductList extends _$ProductList {
63
+ @override
64
+ FutureOr<List<Product>> build() async {
65
+ final repo = ref.read(productRepositoryProvider);
66
+ return repo.getProducts();
67
+ }
68
+
69
+ Future<void> refresh() async {
70
+ state = const AsyncLoading();
71
+ state = await AsyncValue.guard(() async {
72
+ final repo = ref.read(productRepositoryProvider);
73
+ return repo.getProducts();
74
+ });
75
+ }
76
+ }
77
+
78
+ // features/product/presentation/product_screen.dart
79
+ class ProductScreen extends ConsumerWidget {
80
+ const ProductScreen({super.key});
81
+
82
+ @override
83
+ Widget build(BuildContext context, WidgetRef ref) {
84
+ final products = ref.watch(productListProvider);
85
+ return products.when(
86
+ data: (list) => list.isEmpty
87
+ ? const EmptyState()
88
+ : ListView.builder(
89
+ itemCount: list.length,
90
+ itemBuilder: (_, i) => ProductCard(product: list[i]),
91
+ ),
92
+ loading: () => const Center(child: CircularProgressIndicator()),
93
+ error: (e, _) => ErrorState(onRetry: () => ref.invalidate(productListProvider)),
94
+ );
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Dependency Injection (get_it + injectable)
100
+
101
+ ```dart
102
+ // core/di/injection.dart
103
+ import 'package:get_it/get_it.dart';
104
+ import 'package:injectable/injectable.dart';
105
+
106
+ final getIt = GetIt.instance;
107
+
108
+ @InjectableInit()
109
+ void configureDependencies() => getIt.init();
110
+
111
+ // Usage: annotate classes
112
+ @injectable
113
+ class AuthRepository {
114
+ final ApiClient _client;
115
+ AuthRepository(this._client);
116
+ }
117
+ ```
118
+
119
+ ## Navigation (GoRouter)
120
+
121
+ ```dart
122
+ final router = GoRouter(
123
+ redirect: (context, state) {
124
+ final isAuth = authNotifier.isAuthenticated;
125
+ final isAuthRoute = state.matchedLocation.startsWith('/auth');
126
+ if (!isAuth && !isAuthRoute) return '/auth/login';
127
+ if (isAuth && isAuthRoute) return '/';
128
+ return null;
129
+ },
130
+ routes: [
131
+ ShellRoute(
132
+ builder: (_, __, child) => MainScaffold(child: child),
133
+ routes: [
134
+ GoRoute(path: '/', builder: (_, __) => const HomeScreen()),
135
+ GoRoute(path: '/profile', builder: (_, __) => const ProfileScreen()),
136
+ ],
137
+ ),
138
+ GoRoute(path: '/auth/login', builder: (_, __) => const LoginScreen()),
139
+ ],
140
+ );
141
+ ```
142
+
143
+ ## Networking
144
+
145
+ ### Dio + Retrofit
146
+
147
+ ```dart
148
+ @RestApi(baseUrl: ApiConstants.baseUrl)
149
+ abstract class ApiClient {
150
+ factory ApiClient(Dio dio) = _ApiClient;
151
+
152
+ @GET('/products')
153
+ Future<ApiResponse<List<ProductDto>>> getProducts();
154
+
155
+ @POST('/auth/login')
156
+ Future<ApiResponse<AuthDto>> login(@Body() LoginInput input);
157
+ }
158
+
159
+ // Dio setup with interceptors
160
+ final dio = Dio(BaseOptions(
161
+ baseUrl: ApiConstants.baseUrl,
162
+ connectTimeout: const Duration(seconds: 15),
163
+ ))
164
+ ..interceptors.addAll([
165
+ AuthInterceptor(secureStorage),
166
+ PrettyDioLogger(requestBody: true, responseBody: true),
167
+ RetryInterceptor(dio: dio, retries: 2),
168
+ ]);
169
+ ```
170
+
171
+ ### HTTP (simpler pattern)
172
+
173
+ ```dart
174
+ final response = await http.get(
175
+ Uri.parse('$baseUrl/endpoint'),
176
+ headers: {'Authorization': 'Bearer $token'},
177
+ );
178
+ ```
179
+
180
+ ## Local Storage
181
+
182
+ ```dart
183
+ // Hive (structured data)
184
+ await Hive.initFlutter();
185
+ Hive.registerAdapter(UserAdapter());
186
+ final box = await Hive.openBox<User>('users');
187
+
188
+ // Floor (Room-like SQL)
189
+ @dao
190
+ abstract class ProductDao {
191
+ @Query('SELECT * FROM products')
192
+ Future<List<ProductEntity>> getAll();
193
+
194
+ @Insert(onConflict: OnConflictStrategy.replace)
195
+ Future<void> insertAll(List<ProductEntity> products);
196
+ }
197
+
198
+ // SharedPreferences (simple key-value)
199
+ final prefs = await SharedPreferences.getInstance();
200
+
201
+ // flutter_secure_storage (tokens)
202
+ final storage = const FlutterSecureStorage();
203
+ await storage.write(key: 'token', value: token);
204
+ ```
205
+
206
+ ## Firebase
207
+
208
+ ```dart
209
+ // Firebase init in main.dart
210
+ await Firebase.initializeApp();
211
+ await FirebaseMessaging.instance.requestPermission();
212
+
213
+ // Firestore
214
+ final doc = await FirebaseFirestore.instance.collection('users').doc(uid).get();
215
+
216
+ // FCM push
217
+ FirebaseMessaging.onMessage.listen((message) {
218
+ // Handle foreground notification
219
+ });
220
+ ```
221
+
222
+ ## Key Libraries
223
+
224
+ | Purpose | Library |
225
+ |---------|---------|
226
+ | State | flutter_riverpod |
227
+ | DI | get_it + injectable |
228
+ | HTTP | dio + retrofit |
229
+ | HTTP | http |
230
+ | DB | floor |
231
+ | DB | hive |
232
+ | Router | go_router |
233
+ | UI | flutter_screenutil |
234
+ | Anim | flutter_animate |
235
+ | Auth | local_auth (biometric) |
236
+ | Crypto | encrypt, crypto |
237
+ | Forms | formz |
238
+ | Func | dartz (Either, Option) |
239
+ | Models | freezed_annotation |
240
+ | Firebase | firebase_core, messaging, firestore |
241
+ | Image | cached_network_image |
242
+
243
+ ---
244
+
245
+ > Standard: Riverpod + get_it/injectable + Clean Architecture.
246
+ > Dio/Retrofit for complex APIs. Floor for offline-first. Firebase for push/analytics.