---
id: mobile-flutter-patterns
title: Flutter — Widget / State / Riverpod
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [mobile, flutter, dart, vibe-coding]
tech_stack: { language: "Dart / Flutter", applicable_to: ["iOS", "Android", "Web", "Desktop"] }
applied_in: []
aliases: [Flutter, Dart, Riverpod, Bloc, GoRouter, Material 3]
---

# Flutter

> Google 의 cross-platform UI. **단일 codebase → iOS / Android / Web / Desktop**. **Skia / Impeller 가 그림 — native 만큼 빠름**. Material 3 / Cupertino UI built-in.

## 📖 핵심 개념
- Widget: 모든 거. immutable.
- State: StatefulWidget 의 mutable.
- Riverpod / Bloc: state management.
- Hot reload: 변경 즉시 반영.

## 💻 코드 패턴

### 기본 widget
```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter')),
      body: Center(child: Text('$_count', style: Theme.of(context).textTheme.displayLarge)),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => _count++),
        child: const Icon(Icons.add),
      ),
    );
  }
}
```

### Layout
```dart
Scaffold(
  body: SafeArea(
    child: Column(
      children: [
        const Text('Header'),
        Expanded(
          child: ListView.builder(
            itemCount: items.length,
            itemBuilder: (ctx, i) => ListTile(
              title: Text(items[i].name),
              onTap: () => Navigator.push(ctx, MaterialPageRoute(builder: (_) => DetailPage(items[i]))),
            ),
          ),
        ),
      ],
    ),
  ),
)
```

### Riverpod (state management)
```yaml
# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.4.0
  riverpod_annotation: ^2.3.0
```

```dart
@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;
  void increment() => state++;
}

@riverpod
Future<List<User>> users(UsersRef ref) async {
  final dio = ref.watch(dioProvider);
  final r = await dio.get('/users');
  return (r.data as List).map((j) => User.fromJson(j)).toList();
}

// Widget
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext ctx, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Column(
      children: [
        Text('$count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('+'),
        ),
      ],
    );
  }
}
```

### AsyncValue (network state)
```dart
class UsersPage extends ConsumerWidget {
  @override
  Widget build(BuildContext ctx, WidgetRef ref) {
    final asyncUsers = ref.watch(usersProvider);
    return asyncUsers.when(
      data: (users) => ListView.builder(...),
      loading: () => const CircularProgressIndicator(),
      error: (e, _) => Text('Error: $e'),
    );
  }
}
```

### GoRouter (navigation)
```dart
final router = GoRouter(
  routes: [
    GoRoute(path: '/', builder: (_, __) => const HomePage()),
    GoRoute(
      path: '/user/:id',
      builder: (_, state) => UserPage(id: state.pathParameters['id']!),
    ),
  ],
);

MaterialApp.router(routerConfig: router, ...);
```

### Network (Dio)
```dart
final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'));
dio.interceptors.add(InterceptorsWrapper(
  onRequest: (options, handler) {
    options.headers['Authorization'] = 'Bearer $token';
    handler.next(options);
  },
  onError: (e, handler) {
    if (e.response?.statusCode == 401) refreshAuth();
    handler.next(e);
  },
));

final r = await dio.get('/users/$id');
final user = User.fromJson(r.data);
```

### Freezed (immutable model)
```dart
@freezed
class User with _$User {
  const factory User({
    required String id,
    required String email,
    String? name,
  }) = _User;
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
```

```bash
dart run build_runner build  # 코드 generate
```

### Async / await
```dart
Future<void> _load() async {
  setState(() => _loading = true);
  try {
    final users = await api.fetchUsers();
    setState(() => _users = users);
  } catch (e) {
    showError(e.toString());
  } finally {
    setState(() => _loading = false);
  }
}
```

### 새 architecture (riverpod hooks 같이)
```dart
class HomePage extends HookConsumerWidget {
  @override
  Widget build(BuildContext ctx, WidgetRef ref) {
    final controller = useTextEditingController();
    final search = useState('');
    
    final users = ref.watch(searchUsersProvider(search.value));
    
    return Column(
      children: [
        TextField(controller: controller, onChanged: (v) => search.value = v),
        users.when(...),
      ],
    );
  }
}
```

### Test
```dart
testWidgets('counter increments', (tester) async {
  await tester.pumpWidget(const MyApp());
  expect(find.text('0'), findsOneWidget);
  
  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();
  
  expect(find.text('1'), findsOneWidget);
});

// Riverpod
test('counter', () {
  final container = ProviderContainer();
  expect(container.read(counterProvider), 0);
  container.read(counterProvider.notifier).increment();
  expect(container.read(counterProvider), 1);
});
```

### Build / release
```bash
# iOS
flutter build ipa --release

# Android
flutter build appbundle --release

# Web
flutter build web --release

# Desktop
flutter build macos --release
flutter build windows --release
```

### Performance
- `const` constructor 사용 (rebuild skip).
- Widget trees 작게 + split.
- `RepaintBoundary` 큰 widget 분리.
- Image cache (cached_network_image).
- Lists = ListView.builder (lazy).

### vs React Native / Native
```
Flutter:
+ 빠름 (Skia compile)
+ 같은 UI 모든 platform
+ Hot reload 강력
- Dart language (학습)
- Native 통합 = platform channel
- Bundle 큼 (~5-15MB)

React Native:
+ JS / TS 친숙
+ 큰 ecosystem
- JS bridge / new arch 학습
- Native UI 위주

Native:
+ Best UX / performance
- 두 번 개발
```

## 🤔 의사결정 기준
| 상황 | 추천 |
|---|---|
| Cross-platform + 일관 UI | Flutter |
| 작은 팀 / 빠른 MVP | Flutter / RN |
| Native UX strict | Native |
| Web 도 같이 | Flutter Web |
| Desktop 도 같이 | Flutter |
| 큰 native ecosystem 의존 | Native / RN |

## ❌ 안티패턴
- **`const` 안 씀**: 매 rebuild.
- **거대 build method**: 분리.
- **setState 큰 tree**: 작은 stateful widget.
- **Provider 너무 많음 (각 var)**: combine.
- **Dio interceptor 안 + auth 매번**: 통일.
- **Async/await 없는 callback hell**: future 변환.
- **Native channel 직접 (필요 시)**: package 사용 / pigeon.

## 🤖 LLM 활용 힌트
- Riverpod + Freezed + GoRouter + Dio = 표준 stack.
- AsyncValue.when 으로 loading/error 깔끔.
- const constructor + ListView.builder.

## 🔗 관련 문서
- [[Mobile_KMP_Compose]]
- [[React_Native_Bridge_Performance]]
- [[Android_Compose_State_Hoisting]]
