Flutter 개발자를 위한 Dart 언어 완벽 가이드
Android/iOS 개발자가 Flutter로 전환할 때 반드시 알아야 할 Dart 언어의 핵심 개념들
들어가며
안녕하세요! 모바일 개발 경험이 있으시거나 웹 프론트엔드에서 모바일로 영역을 확장하려는 개발자분들을 위한 Dart 언어 가이드를 준비했습니다.
Flutter를 배우기 위해서는 먼저 Dart 언어를 이해해야 합니다. 하지만 걱정하지 마세요! Java/Kotlin, Swift, JavaScript를 사용해보신 분이라면 Dart는 매우 친숙하게 느껴질 것입니다.
이 글에서는 실제 Flutter 앱 개발에서 자주 사용되는 Dart의 핵심 개념들을 실무 예제와 함께 설명드리겠습니다.
1. Dart 언어 소개
왜 Dart인가?
Flutter팀이 Dart를 선택한 이유는 명확합니다:
- AOT(Ahead-of-Time) 컴파일: 네이티브 수준의 성능
- JIT(Just-in-Time) 컴파일: 개발 중 Hot Reload 지원
- 가비지 컬렉션: 메모리 관리 자동화
- 단일 스레드 모델: 복잡한 동시성 문제 해결
다른 언어와의 비교
// Java/Kotlin 스타일과 매우 유사
class User {
String name;
int age;
User(this.name, this.age); // 생성자 단축 문법
}
// JavaScript처럼 동적 타이핑도 지원
var data = {'name': 'John', 'age': 30};
2. 변수와 타입 시스템
변수 선언의 베스트 프랙티스
Dart는 강타입 언어이지만 타입 추론을 지원하여 개발 편의성을 제공합니다.
// 타입 추론 활용 (권장)
var userName = 'John Doe'; // String으로 추론
var userAge = 25; // int로 추론
var isActive = true; // bool로 추론
// 명시적 타입 선언 (API 경계에서 권장)
String getUserName() => userName;
int calculateAge(DateTime birthDate) =>
DateTime.now().year - birthDate.year;
// 컬렉션 타입 명시
List<String> tags = ['flutter', 'dart', 'mobile'];
Map<String, dynamic> apiResponse = {'status': 'success', 'data': []};
final vs const: 실무에서의 활용
class AppConfig {
// 컴파일 타임 상수 - 빌드 시점에 결정
static const String appName = 'MyAwesomeApp';
static const int maxRetryCount = 3;
static const Duration requestTimeout = Duration(seconds: 30);
// 런타임 상수 - 앱 실행 중 한 번만 설정
final String deviceId;
final DateTime appStartTime;
AppConfig()
: deviceId = _generateDeviceId(),
appStartTime = DateTime.now();
static String _generateDeviceId() {
// 디바이스 고유 ID 생성 로직
return DateTime.now().millisecondsSinceEpoch.toString();
}
}
// 사용 예
void main() {
final config = AppConfig();
print('앱 이름: ${AppConfig.appName}');
print('시작 시간: ${config.appStartTime}');
}
late 키워드: 지연 초기화 패턴
class ApiService {
late final Dio _dio;
late final String _baseUrl;
// 초기화 메서드에서 late 변수들을 설정
Future<void> initialize() async {
_baseUrl = await _loadBaseUrlFromConfig();
_dio = Dio(BaseOptions(
baseUrl: _baseUrl,
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 10),
));
}
Future<String> _loadBaseUrlFromConfig() async {
// 설정 파일이나 환경 변수에서 URL 로드
return 'https://api.example.com';
}
Future<Map<String, dynamic>> get(String endpoint) async {
final response = await _dio.get(endpoint);
return response.data;
}
}
3. 함수와 메서드
함수형 프로그래밍 스타일
Dart는 함수를 일급 객체로 취급하여 함수형 프로그래밍 패턴을 지원합니다.
// 고차 함수 활용
List<T> processData<T>(
List<T> data,
bool Function(T) filter,
T Function(T) transform,
) {
return data
.where(filter)
.map(transform)
.toList();
}
// 실사용 예제
class UserService {
List<User> filterActiveUsers(List<User> users) {
return processData(
users,
(user) => user.isActive && user.lastLoginDays < 30,
(user) => user.copyWith(displayName: user.name.toUpperCase()),
);
}
}
// 익명 함수와 클로저
class EventHandler {
final List<VoidCallback> _listeners = [];
void addListener(VoidCallback callback) => _listeners.add(callback);
void fireEvent() {
for (final listener in _listeners) {
listener();
}
}
// 클로저를 활용한 이벤트 리스너
VoidCallback createLoggingListener(String tag) {
return () => print('[$tag] Event fired at ${DateTime.now()}');
}
}
선택적 매개변수 패턴
// Named Parameters (권장 패턴)
class HttpClient {
Future<Response> request(
String url, {
Map<String, String>? headers,
Duration? timeout,
int maxRetries = 3,
bool followRedirects = true,
}) async {
// 구현...
}
}
// 사용 시 가독성이 매우 높음
final response = await httpClient.request(
'https://api.example.com/users',
headers: {'Authorization': 'Bearer $token'},
timeout: Duration(seconds: 30),
maxRetries: 5,
);
// 빌더 패턴과 결합
class ApiRequestBuilder {
String? _url;
Map<String, String> _headers = {};
Duration _timeout = Duration(seconds: 30);
ApiRequestBuilder url(String url) {
_url = url;
return this;
}
ApiRequestBuilder header(String key, String value) {
_headers[key] = value;
return this;
}
ApiRequestBuilder timeout(Duration timeout) {
_timeout = timeout;
return this;
}
Future<Response> execute() async {
if (_url == null) throw ArgumentError('URL is required');
// HTTP 요청 실행
}
}
// 사용 예
final response = await ApiRequestBuilder()
.url('https://api.example.com/users')
.header('Authorization', 'Bearer $token')
.header('Content-Type', 'application/json')
.timeout(Duration(seconds: 45))
.execute();
4. 클래스와 객체지향 프로그래밍
모던 클래스 설계 패턴
// 불변 클래스 패턴 (권장)
class User {
final String id;
final String name;
final String email;
final DateTime createdAt;
final bool isActive;
const User({
required this.id,
required this.name,
required this.email,
required this.createdAt,
this.isActive = true,
});
// copyWith 패턴으로 불변성 유지하면서 수정
User copyWith({
String? id,
String? name,
String? email,
DateTime? createdAt,
bool? isActive,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
email: email ?? this.email,
createdAt: createdAt ?? this.createdAt,
isActive: isActive ?? this.isActive,
);
}
// 팩토리 생성자 패턴
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as String,
name: json['name'] as String,
email: json['email'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
isActive: json['is_active'] as bool? ?? true,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'created_at': createdAt.toIso8601String(),
'is_active': isActive,
};
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User &&
other.id == id &&
other.name == name &&
other.email == email &&
other.createdAt == createdAt &&
other.isActive == isActive;
}
@override
int get hashCode {
return Object.hash(id, name, email, createdAt, isActive);
}
@override
String toString() {
return 'User(id: $id, name: $name, email: $email, '
'createdAt: $createdAt, isActive: $isActive)';
}
}
추상 클래스와 인터페이스 패턴
// 데이터 레이어 추상화
abstract class UserRepository {
Future<List<User>> getAllUsers();
Future<User?> getUserById(String id);
Future<User> createUser(User user);
Future<User> updateUser(User user);
Future<void> deleteUser(String id);
}
// 로컬 저장소 구현
class LocalUserRepository implements UserRepository {
final Box<Map<dynamic, dynamic>> _userBox;
LocalUserRepository(this._userBox);
@override
Future<List<User>> getAllUsers() async {
return _userBox.values
.map((json) => User.fromJson(Map<String, dynamic>.from(json)))
.toList();
}
@override
Future<User?> getUserById(String id) async {
final json = _userBox.get(id);
return json != null
? User.fromJson(Map<String, dynamic>.from(json))
: null;
}
@override
Future<User> createUser(User user) async {
await _userBox.put(user.id, user.toJson());
return user;
}
// ... 나머지 메서드 구현
}
// API 기반 구현
class ApiUserRepository implements UserRepository {
final Dio _dio;
ApiUserRepository(this._dio);
@override
Future<List<User>> getAllUsers() async {
final response = await _dio.get('/users');
final List<dynamic> usersJson = response.data['users'];
return usersJson
.map((json) => User.fromJson(json as Map<String, dynamic>))
.toList();
}
// ... 나머지 메서드 구현
}
Mixin을 활용한 코드 재사용
// 로깅 기능을 mixin으로 분리
mixin LoggerMixin {
String get logTag => runtimeType.toString();
void logInfo(String message) {
print('[$logTag] INFO: $message');
}
void logError(String message, [Object? error, StackTrace? stackTrace]) {
print('[$logTag] ERROR: $message');
if (error != null) print('Error: $error');
if (stackTrace != null) print('StackTrace: $stackTrace');
}
void logDebug(String message) {
if (kDebugMode) {
print('[$logTag] DEBUG: $message');
}
}
}
// 네트워크 상태 확인 mixin
mixin NetworkAwareMixin {
Future<bool> get isConnected async {
final connectivityResult = await Connectivity().checkConnectivity();
return connectivityResult != ConnectivityResult.none;
}
Future<T> executeWithNetworkCheck<T>(Future<T> Function() operation) async {
if (!await isConnected) {
throw NetworkException('No internet connection');
}
return await operation();
}
}
// mixin들을 활용한 서비스 클래스
class UserService with LoggerMixin, NetworkAwareMixin {
final UserRepository _repository;
UserService(this._repository);
Future<List<User>> getUsers() async {
logInfo('Fetching users...');
try {
final users = await executeWithNetworkCheck(
() => _repository.getAllUsers(),
);
logInfo('Successfully fetched ${users.length} users');
return users;
} catch (e, stackTrace) {
logError('Failed to fetch users', e, stackTrace);
rethrow;
}
}
}
5. 컬렉션과 제네릭
실무에서 자주 사용하는 컬렉션 패턴
class DataProcessor {
// List 변환과 필터링
List<String> extractUserNames(List<User> users) {
return users
.where((user) => user.isActive)
.map((user) => user.name)
.toList();
}
// Map을 활용한 그룹핑
Map<String, List<User>> groupUsersByDomain(List<User> users) {
final groupedUsers = <String, List<User>>{};
for (final user in users) {
final domain = user.email.split('@').last;
groupedUsers.putIfAbsent(domain, () => []).add(user);
}
return groupedUsers;
}
// Set을 활용한 중복 제거
Set<String> getUniqueEmails(List<User> users) {
return users.map((user) => user.email).toSet();
}
// 함수형 스타일 체이닝
List<User> processUsers(List<User> users) {
return users
.where((user) => user.isActive)
.where((user) => user.email.contains('@company.com'))
.map((user) => user.copyWith(
name: user.name.trim().toUpperCase(),
))
.toList()
..sort((a, b) => a.name.compareTo(b.name));
}
}
제네릭을 활용한 타입 안전성
// API 응답 래퍼 클래스
class ApiResponse<T> {
final bool success;
final T? data;
final String? error;
final int statusCode;
const ApiResponse({
required this.success,
this.data,
this.error,
required this.statusCode,
});
factory ApiResponse.success(T data, {int statusCode = 200}) {
return ApiResponse(
success: true,
data: data,
statusCode: statusCode,
);
}
factory ApiResponse.error(String error, {int statusCode = 500}) {
return ApiResponse(
success: false,
error: error,
statusCode: statusCode,
);
}
// 결과 처리를 위한 패턴 매칭 스타일 메서드
R when<R>({
required R Function(T data) success,
required R Function(String error) error,
}) {
if (this.success && data != null) {
return success(data as T);
} else {
return error(this.error ?? 'Unknown error');
}
}
}
// 제네릭 저장소 패턴
abstract class CacheRepository<T> {
Future<void> save(String key, T item);
Future<T?> get(String key);
Future<void> delete(String key);
Future<void> clear();
Future<List<T>> getAll();
}
class HiveCacheRepository<T> implements CacheRepository<T> {
final Box<String> _box;
final T Function(Map<String, dynamic>) _fromJson;
final Map<String, dynamic> Function(T) _toJson;
HiveCacheRepository(
this._box,
this._fromJson,
this._toJson,
);
@override
Future<void> save(String key, T item) async {
final jsonString = jsonEncode(_toJson(item));
await _box.put(key, jsonString);
}
@override
Future<T?> get(String key) async {
final jsonString = _box.get(key);
if (jsonString == null) return null;
final json = jsonDecode(jsonString) as Map<String, dynamic>;
return _fromJson(json);
}
// ... 나머지 메서드 구현
}
// 사용 예
final userCache = HiveCacheRepository<User>(
userBox,
(json) => User.fromJson(json),
(user) => user.toJson(),
);
6. 비동기 프로그래밍
Future와 async/await 실무 패턴
class NetworkService {
final Dio _dio;
final Duration defaultTimeout;
NetworkService(this._dio, {this.defaultTimeout = const Duration(seconds: 30)});
// 기본적인 HTTP 요청 패턴
Future<ApiResponse<T>> request<T>(
String method,
String path, {
dynamic data,
Map<String, dynamic>? queryParameters,
Duration? timeout,
T Function(Map<String, dynamic>)? parser,
}) async {
try {
final response = await _dio.request(
path,
data: data,
queryParameters: queryParameters,
options: Options(
method: method,
receiveTimeout: timeout ?? defaultTimeout,
),
);
if (response.statusCode == 200) {
final responseData = parser != null
? parser(response.data)
: response.data as T;
return ApiResponse.success(responseData);
} else {
return ApiResponse.error(
'Request failed with status: ${response.statusCode}',
statusCode: response.statusCode!,
);
}
} on DioException catch (e) {
return _handleDioException(e);
} catch (e) {
return ApiResponse.error('Unexpected error: $e');
}
}
// 에러 처리 패턴
ApiResponse<T> _handleDioException<T>(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return ApiResponse.error(
'Request timeout',
statusCode: 408,
);
case DioExceptionType.badResponse:
return ApiResponse.error(
'Server error: ${e.response?.statusCode}',
statusCode: e.response?.statusCode ?? 500,
);
case DioExceptionType.cancel:
return ApiResponse.error('Request cancelled');
default:
return ApiResponse.error('Network error: ${e.message}');
}
}
// 병렬 요청 처리
Future<(List<User>, List<Post>)> fetchUserDataParallel(String userId) async {
final futures = await Future.wait([
getUserProfile(userId),
getUserPosts(userId),
]);
return (
futures[0] as List<User>,
futures[1] as List<Post>,
);
}
}
Stream을 활용한 실시간 데이터 처리
class ChatService {
final WebSocketChannel _channel;
late final StreamController<ChatMessage> _messageController;
ChatService(this._channel) {
_messageController = StreamController<ChatMessage>.broadcast();
_listenToMessages();
}
Stream<ChatMessage> get messages => _messageController.stream;
void _listenToMessages() {
_channel.stream.listen(
(data) {
try {
final json = jsonDecode(data) as Map<String, dynamic>;
final message = ChatMessage.fromJson(json);
_messageController.add(message);
} catch (e) {
print('Failed to parse message: $e');
}
},
onError: (error) {
_messageController.addError(error);
},
onDone: () {
_messageController.close();
},
);
}
void sendMessage(String text) {
final message = {
'type': 'message',
'text': text,
'timestamp': DateTime.now().toIso8601String(),
};
_channel.sink.add(jsonEncode(message));
}
void dispose() {
_channel.sink.close();
_messageController.close();
}
}
// Stream 변환과 필터링
class DataStreamProcessor {
Stream<List<User>> processUserStream(Stream<List<User>> userStream) {
return userStream
.where((users) => users.isNotEmpty)
.map((users) => users.where((user) => user.isActive).toList())
.distinct((previous, current) =>
const ListEquality().equals(previous, current))
.debounceTime(Duration(milliseconds: 300));
}
// 여러 스트림 결합
Stream<SearchResult> combineSearchStreams(
Stream<String> queryStream,
Stream<List<String>> suggestionsStream,
) {
return Rx.combineLatest2(
queryStream.debounceTime(Duration(milliseconds: 500)),
suggestionsStream,
(query, suggestions) => SearchResult(
query: query,
suggestions: suggestions
.where((s) => s.toLowerCase().contains(query.toLowerCase()))
.toList(),
),
);
}
}
7. 널 안전성 (Null Safety)
실무에서의 널 안전성 패턴
class UserValidator {
// 널 안전 메서드 체이닝
String? validateEmail(String? email) {
return email
?.trim()
.toLowerCase()
.let((email) => email.contains('@') ? email : null);
}
// 널 병합 연산자 활용
String getDisplayName(User? user) {
return user?.name ??
user?.email?.split('@').first ??
'Unknown User';
}
// 안전한 타입 캐스팅
int? parseAge(dynamic value) {
if (value is int) return value;
if (value is String) return int.tryParse(value);
return null;
}
// 조건부 멤버 접근
void updateUserIfActive(User? user, String newName) {
user?.let((user) {
if (user.isActive) {
// 사용자 업데이트 로직
updateUser(user.copyWith(name: newName));
}
});
}
}
// Extension을 활용한 널 안전성 향상
extension NullableExtensions<T> on T? {
R? let<R>(R Function(T) transform) {
final value = this;
return value != null ? transform(value) : null;
}
T orElse(T defaultValue) {
return this ?? defaultValue;
}
T orElseGet(T Function() defaultValueProvider) {
return this ?? defaultValueProvider();
}
}
// 사용 예
final user = await userService.getUser(userId);
final avatar = user
?.profileImageUrl
?.let((url) => CachedNetworkImage(imageUrl: url))
?? const CircleAvatar(child: Icon(Icons.person));
안전한 JSON 파싱 패턴
class SafeJsonParser {
static T? tryParse<T>(
Map<String, dynamic>? json,
String key,
T Function(dynamic) parser,
) {
try {
final value = json?[key];
return value != null ? parser(value) : null;
} catch (e) {
print('Failed to parse $key: $e');
return null;
}
}
static List<T> parseList<T>(
Map<String, dynamic>? json,
String key,
T Function(Map<String, dynamic>) itemParser,
) {
try {
final list = json?[key] as List<dynamic>?;
if (list == null) return [];
return list
.whereType<Map<String, dynamic>>()
.map(itemParser)
.toList();
} catch (e) {
print('Failed to parse list $key: $e');
return [];
}
}
}
// 안전한 모델 클래스
class SafeUser {
final String id;
final String name;
final String? email; // 선택적 필드
final int age;
final List<String> tags;
final DateTime? lastLogin; // 선택적 필드
SafeUser({
required this.id,
required this.name,
this.email,
required this.age,
this.tags = const [],
this.lastLogin,
});
factory SafeUser.fromJson(Map<String, dynamic> json) {
return SafeUser(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? 'Unknown',
email: json['email'] as String?,
age: SafeJsonParser.tryParse(json, 'age', (v) => v as int) ?? 0,
tags: SafeJsonParser.parseList(
json,
'tags',
(item) => item['name'] as String,
),
lastLogin: SafeJsonParser.tryParse(
json,
'last_login',
(v) => DateTime.parse(v as String),
),
);
}
}
8. 함수형 프로그래밍 패턴
불변성과 순수 함수
// 순수 함수 예제
class MathUtils {
// 입력에만 의존하고 부작용이 없는 순수 함수
static double calculateDistance(Point a, Point b) {
final dx = a.x - b.x;
final dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
// 리스트를 변경하지 않고 새로운 리스트 반환
static List<int> addToAll(List<int> numbers, int value) {
return numbers.map((n) => n + value).toList();
}
}
// 함수 조합 패턴
class StringProcessor {
static String Function(String) compose(
List<String Function(String)> functions,
) {
return (input) => functions.fold(input, (acc, fn) => fn(acc));
}
static final processors = {
'trim': (String s) => s.trim(),
'lower': (String s) => s.toLowerCase(),
'removeSpaces': (String s) => s.replaceAll(' ', ''),
'capitalize': (String s) => s.isEmpty
? s
: s[0].toUpperCase() + s.substring(1),
};
// 사용 예
static final nameProcessor = compose([
processors['trim']!,
processors['lower']!,
processors['capitalize']!,
]);
}
// 함수형 에러 처리
class Result<T, E> {
final T? _value;
final E? _error;
final bool _isSuccess;
const Result._(this._value, this._error, this._isSuccess);
factory Result.success(T value) => Result._(value, null, true);
factory Result.error(E error) => Result._(null, error, false);
bool get isSuccess => _isSuccess;
bool get isError => !_isSuccess;
T get value {
if (_isSuccess) return _value as T;
throw StateError('Cannot get value from error result');
}
E get error {
if (!_isSuccess) return _error as E;
throw StateError('Cannot get error from success result');
}
// 함수형 변환
Result<R, E> map<R>(R Function(T) transform) {
return _isSuccess
? Result.success(transform(_value as T))
: Result.error(_error as E);
}
Result<R, E> flatMap<R>(Result<R, E> Function(T) transform) {
return _isSuccess
? transform(_value as T)
: Result.error(_error as E);
}
T orElse(T defaultValue) {
return _isSuccess ? _value as T : defaultValue;
}
// 패턴 매칭 스타일
R when<R>({
required R Function(T) success,
required R Function(E) error,
}) {
return _isSuccess
? success(_value as T)
: error(_error as E);
}
}
// 실사용 예제
class UserService {
Future<Result<User, String>> getUser(String id) async {
try {
final user = await _apiService.getUser(id);
return Result.success(user);
} catch (e) {
return Result.error('Failed to fetch user: $e');
}
}
Future<Result<String, String>> processUserData(String userId) async {
final userResult = await getUser(userId);
return userResult
.map((user) => user.name.toUpperCase())
.map((name) => 'Processed: $name');
}
}
9. 성능 최적화를 위한 Dart 패턴
메모리 효율적인 코드 작성
class PerformanceOptimizedService {
// 지연 초기화를 통한 메모리 절약
late final RegExp _emailRegex = RegExp(r'^[w-.]+@([w-]+.)+[w-]{2,4}
비동기 코드 최적화
class AsyncOptimizer {
// 병렬 처리로 성능 향상
Future<UserProfile> loadUserProfile(String userId) async {
// 순차 실행 (느림)
// final user = await userService.getUser(userId);
// final posts = await postService.getUserPosts(userId);
// final followers = await followService.getFollowers(userId);
// 병렬 실행 (빠름)
final futures = await Future.wait([
userService.getUser(userId),
postService.getUserPosts(userId),
followService.getFollowers(userId),
]);
return UserProfile(
user: futures[0] as User,
posts: futures[1] as List<Post>,
followers: futures[2] as List<User>,
);
}
// 조건부 비동기 실행
Future<List<Post>> getPostsConditionally(String userId) async {
final cachedPosts = await cacheService.getPosts(userId);
if (cachedPosts.isNotEmpty) {
return cachedPosts; // 캐시된 데이터 사용
}
// 네트워크에서 가져오기
final posts = await apiService.getPosts(userId);
// 백그라운드에서 캐시 저장 (await 없이)
unawaited(cacheService.savePosts(userId, posts));
return posts;
}
// 타임아웃과 재시도 패턴
Future<T> executeWithRetry<T>(
Future<T> Function() operation,
int maxRetries,
Duration timeout,
) async {
for (int attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation().timeout(timeout);
} catch (e) {
if (attempt == maxRetries) rethrow;
// 지수 백오프
await Future.delayed(Duration(seconds: math.pow(2, attempt).toInt()));
}
}
throw StateError('This should never be reached');
}
}
마무리
이제 Dart 언어의 핵심 개념들을 실무 중심으로 살펴보았습니다. 각 개념은 Flutter 앱 개발에서 실제로 자주 사용되는 패턴들입니다.
다음 단계
- Flutter 위젯 시스템 학습
- 상태 관리 (Riverpod, Bloc) 적용
- 실제 프로젝트로 연습
추천 학습 자료
- Dart 공식 문서
- Flutter 공식 문서
- Dart Pad - 온라인 코드 실습
궁금한 점이 있으시면 언제든 댓글로 남겨주세요! 함께 성장하는 Flutter 개발자가 되어봅시다! 🚀
관련 글
);
late final DateFormat _dateFormatter = DateFormat('yyyy-MM-dd');
// 싱글톤 패턴으로 인스턴스 재사용
static PerformanceOptimizedService? _instance;
static PerformanceOptimizedService get instance {
return _instance ??= PerformanceOptimizedService._();
}
PerformanceOptimizedService._();
// 캐시를 통한 중복 계산 방지
final Map<String, bool> _validationCache = {};
bool isValidEmail(String email) {
return _validationCache.putIfAbsent(
email,
() => _emailRegex.hasMatch(email),
);
}
// 스트림 구독 관리
final List<StreamSubscription> _subscriptions = [];
void addSubscription(StreamSubscription subscription) {
_subscriptions.add(subscription);
}
void dispose() {
for (final subscription in _subscriptions) {
subscription.cancel();
}
_subscriptions.clear();
_validationCache.clear();
}
}
// 효율적인 컬렉션 사용
class CollectionOptimizer {
// Set을 사용한 중복 제거와 빠른 조회
static List<String> removeDuplicatesEfficient(List<String> items) {
return items.toSet().toList();
}
// Map을 사용한 빠른 조회
static User? findUserByIdEfficient(List<User> users, String id) {
// O(n) 매번 순회하는 대신
// final user = users.firstWhere((u) => u.id == id);
// O(1) 조회를 위한 Map 사용
final userMap = {for (final user in users) user.id: user};
return userMap[id];
}
// 대용량 데이터 청크 단위 처리
static Future<List<T>> processInChunks<T>(
List<T> items,
Future<T> Function(T) processor,
int chunkSize,
) async {
final results = <T>[];
for (int i = 0; i < items.length; i += chunkSize) {
final chunk = items.sublist(
i,
math.min(i + chunkSize, items.length),
);
final chunkResults = await Future.wait(
chunk.map(processor),
);
results.addAll(chunkResults);
}
return results;
}
}
비동기 코드 최적화
마무리
이제 Dart 언어의 핵심 개념들을 실무 중심으로 살펴보았습니다. 각 개념은 Flutter 앱 개발에서 실제로 자주 사용되는 패턴들입니다.
다음 단계
- Flutter 위젯 시스템 학습
- 상태 관리 (Riverpod, Bloc) 적용
- 실제 프로젝트로 연습
추천 학습 자료
- Dart 공식 문서
- Flutter 공식 문서
- Dart Pad – 온라인 코드 실습
궁금한 점이 있으시면 언제든 댓글로 남겨주세요! 함께 성장하는 Flutter 개발자가 되어봅시다! 🚀
관련 글
답글 남기기