Files
2nd/10_Wiki/Topics/Coding/Mobile_Flutter_Patterns.md
T
2026-05-09 21:08:02 +09:00

7.3 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
mobile-flutter-patterns Flutter — Widget / State / Riverpod Coding draft B conceptual 2026-05-09 2026-05-09
mobile
flutter
dart
vibe-coding
language applicable_to
Dart / Flutter
iOS
Android
Web
Desktop
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

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

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)

# pubspec.yaml
dependencies:
  flutter_riverpod: ^2.4.0
  riverpod_annotation: ^2.3.0
@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)

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)

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)

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)

@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);
}
dart run build_runner build  # 코드 generate

Async / await

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 같이)

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

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

# 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.

🔗 관련 문서