Flutter 완벽 가이드 | 크로스플랫폼 앱·Dart·Widgets·State·실전 활용

Flutter 완벽 가이드 | 크로스플랫폼 앱·Dart·Widgets·State·실전 활용

이 글의 핵심

Flutter로 iOS/Android/Web 앱을 개발하는 완벽 가이드입니다. Widgets, State Management, Navigation, API, 배포까지 실전 예제로 정리했습니다.

실무 경험 공유: Flutter로 iOS/Android/Web 앱을 동시에 출시하면서, 일관된 UI와 60fps 성능을 제공할 수 있었던 경험을 공유합니다.

들어가며: “일관된 UI가 필요해요”

실무 문제 시나리오

시나리오 1: 플랫폼마다 UI가 달라요
React Native는 Native 컴포넌트를 사용합니다. Flutter는 자체 렌더링으로 일관됩니다.

시나리오 2: 성능이 중요해요
Bridge 오버헤드가 있습니다. Flutter는 Native로 컴파일됩니다.

시나리오 3: 웹도 지원하고 싶어요
별도 개발이 필요합니다. Flutter는 웹도 지원합니다.


1. Flutter란?

핵심 특징

Flutter는 Google의 크로스플랫폼 UI 프레임워크입니다.

주요 장점:

  • 크로스플랫폼: iOS, Android, Web, Desktop
  • 빠른 성능: Native 컴파일
  • 일관된 UI: 자체 렌더링 엔진
  • Hot Reload: 즉시 반영
  • 풍부한 Widgets: Material + Cupertino

2. 설치 및 프로젝트 생성

설치

# macOS
brew install flutter

# Windows
# Flutter SDK 다운로드 및 PATH 추가

프로젝트 생성

flutter create my_app
cd my_app
flutter run

3. 기본 Widgets

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Hello Flutter!', style: TextStyle(fontSize: 24)),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                print('Button pressed');
              },
              child: Text('Click me'),
            ),
          ],
        ),
      ),
    );
  }
}

4. Stateful Widget

class CounterScreen extends StatefulWidget {
  @override
  _CounterScreenState createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count:', style: TextStyle(fontSize: 18)),
            Text('$_counter', style: TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: _incrementCounter,
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

5. Navigation

// 화면 이동
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => DetailsScreen()),
);

// 뒤로가기
Navigator.pop(context);

// 데이터 전달
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailsScreen(userId: 1),
  ),
);

Named Routes

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => HomeScreen(),
    '/details': (context) => DetailsScreen(),
  },
);

// 사용
Navigator.pushNamed(context, '/details');

6. State Management (Provider)

flutter pub add provider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => Counter(),
      child: MyApp(),
    ),
  );
}

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count:'),
            Consumer<Counter>(
              builder: (context, counter, child) {
                return Text('${counter.count}', style: TextStyle(fontSize: 48));
              },
            ),
            ElevatedButton(
              onPressed: () {
                context.read<Counter>().increment();
              },
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

7. API 호출

flutter pub add http
import 'package:http/http.dart' as http;
import 'dart:convert';

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

Future<List<User>> fetchUsers() async {
  final response = await http.get(Uri.parse('https://api.example.com/users'));

  if (response.statusCode == 200) {
    List<dynamic> body = jsonDecode(response.body);
    return body.map((json) => User.fromJson(json)).toList();
  } else {
    throw Exception('Failed to load users');
  }
}

class UserListScreen extends StatefulWidget {
  @override
  _UserListScreenState createState() => _UserListScreenState();
}

class _UserListScreenState extends State<UserListScreen> {
  late Future<List<User>> futureUsers;

  @override
  void initState() {
    super.initState();
    futureUsers = fetchUsers();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Users')),
      body: FutureBuilder<List<User>>(
        future: futureUsers,
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            return ListView.builder(
              itemCount: snapshot.data!.length,
              itemBuilder: (context, index) {
                final user = snapshot.data![index];
                return ListTile(
                  title: Text(user.name),
                  subtitle: Text(user.email),
                );
              },
            );
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }
          return Center(child: CircularProgressIndicator());
        },
      ),
    );
  }
}

8. Form

class LoginForm extends StatefulWidget {
  @override
  _LoginFormState createState() => _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _handleSubmit() {
    if (_formKey.currentState!.validate()) {
      print('Email: ${_emailController.text}');
      print('Password: ${_passwordController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: InputDecoration(labelText: 'Email'),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter email';
              }
              return null;
            },
          ),
          TextFormField(
            controller: _passwordController,
            decoration: InputDecoration(labelText: 'Password'),
            obscureText: true,
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter password';
              }
              return null;
            },
          ),
          SizedBox(height: 16),
          ElevatedButton(
            onPressed: _handleSubmit,
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}

정리 및 체크리스트

핵심 요약

  • Flutter: 크로스플랫폼 UI 프레임워크
  • Dart: 프로그래밍 언어
  • 빠른 성능: Native 컴파일
  • 일관된 UI: 자체 렌더링
  • Hot Reload: 즉시 반영
  • EAS: 클라우드 빌드

구현 체크리스트

  • Flutter 설치
  • 프로젝트 생성
  • 기본 Widgets 사용
  • State Management
  • Navigation 구현
  • API 호출
  • Form 구현
  • 배포

같이 보면 좋은 글

  • React Native 완벽 가이드
  • Dart 완벽 가이드
  • 모바일 앱 개발 가이드

이 글에서 다루는 키워드

Flutter, Dart, Mobile, iOS, Android, Cross-platform, Web

자주 묻는 질문 (FAQ)

Q. React Native와 비교하면 어떤가요?

A. Flutter가 더 빠르고 일관된 UI를 제공합니다. React Native는 JavaScript 생태계를 활용할 수 있습니다.

Q. 학습 곡선은 어떤가요?

A. Dart를 배워야 해서 초반에는 어렵지만, 익숙해지면 생산적입니다.

Q. 웹도 지원하나요?

A. 네, Flutter Web으로 웹 앱도 만들 수 있습니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, Google, Alibaba, BMW 등 대기업에서 사용하고 있습니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3