Flutter

Flutter "Freezed"를 이용한 Model 관리하기

circlecircle 2024. 1. 15. 02:16

https://pub.dev 

 

The official repository for Dart and Flutter packages.

Pub is the package manager for the Dart programming language, containing reusable libraries & packages for Flutter and general Dart programs.

pub.dev

-Motivation 

Freezed- Dart는 훌륭하지만 "모델"을 정의하는것은 지루하다.라고 하며 아래 기능들을 직접적으로 수행해야 한다고 한다.

  • define a constructor + the properties
  • override toString, operator ==, hashCode
  • implement a copyWith method to clone the object
  • handling de/serialization

이 모든 것을 구현하려면 수백 줄이 필요할 수 있으며, 이는 오류가 발생하기 쉽고 모델의 가독성에 큰 영향을 미친다.

Freezed는 이 대부분을 구현하여 이 문제를 해결하려고 시도하므로 모델 정의에 집중할 수 있다.


freezed로 모델을 변경한다면 주로 코드의 안전성, 유지보수성 및 개발 효율성과 관련이 있다.

1. freezed는 불변 객체를 생성하여. 객체의 상태가 생성 후 변경되지 않기 때문에 버그 발생 가능성이 줄어 앱의 상태 관리가 더 예측 가능해 진다.

2. freezed는 패턴 매칭과 함께 사용될때 유용하다. 'when', 'map' 메소드를 통해 상태에 따라 다른 UI를 표시하는 것이 간편해진다.

3. freezed는 'copywith', 'toString', '==' 및 'hashCode' 메소드를 자동으로 생성한다. 

4. freezed는 Json 직렬화를 쉽게 구현할 수 있도록 지원한다. fromJson, toJson 메소드를 통해 Json데이터 간의 변환을 자동으로 처리한다.

5. freezed는 타입 안전성이 향상된다. 컴파일 타임에 타입 관련 오류를 포착할 수 있다. 이는 런타임 오류를 줄이는데 도움이 된다.

 

결론적으로 freezed는 모델에 적용하면 생산성을 향상시키고 앱의 안전성 및 유지보수성, 가독성을 높이는데 도움이 된다!


코드가 정말 간결해진 결과를 한번 보자!

전  /  후

// 사용자 모델
class UserInfo {
String? uid; // 고유uid
String? nickName; // 닉네임
int? age; // 나이
int? gender; // 성별
String? region; // 거주지
List<String>? images; // 프로필 이미지
String? introduce; // 한줄 소개
bool? loginStatus; // 로그인 상태
String? lastLoginTime; // 마지막 로그인 시간
int? point; // 포인트
List<String>? blockedUsers; // 차단유저
String? fcmToken;
String? country;

/// 생성자
UserInfo({
this.uid,
this.nickName,
this.age,
this.gender,
this.region,
this.images,
this.introduce,
this.loginStatus,
this.lastLoginTime,
this.point,
this.blockedUsers,
this.fcmToken,
this.country,
});

UserInfo.fromJson(Map<String, dynamic> json)

: uid =json['uid'],
nickName = json['nickName'],
age = json['age'],
gender = json['gender'],
region = json['region'],
lastLoginTime = json['lastLoginTime'],
loginStatus = json['loginStatus'],
images = json['images'] != null ? (json['images'] as List<dynamic>).cast<String>() : null,
introduce = json['introduce'],
fcmToken = json['fcmToken'],
blockedUsers = json['blockedUsers'] != null ? (json['blockedUsers'] as List<dynamic>).cast<String>() : null,
country = json['country'];


Map<String, dynamic> toJson() => {
'uid': uid,
'nickName': nickName,
'age': age,
'gender': gender,
'region': region,
'lastLoginTime' : lastLoginTime,
'loginStatus' : loginStatus,
'images': images,
'introduce': introduce,
'fcmToken' : fcmToken,
'blockedUsers' : blockedUsers,
'country' : country,
};

@override
String toString() {
return 'UserInfo(uid: $uid, nickName: $nickName, age: $age, gender: $gender, region: $region, images: $images, introduce: $introduce, loginStatus: $loginStatus, lastLoginTime: $lastLoginTime, point: $point, blockedUsers: $blockedUsers, fcmToken: $fcmToken, country: $country)';
}
}
import 'package:freezed_annotation/freezed_annotation.dart';

part 'user_info.freezed.dart';
part 'user_info.g.dart';

@freezed
class UserInfo with _$UserInfo {
const factory UserInfo({
String? uid,
String? nickName,
int? age,
int? gender,
String? region,
List<String>? images,
String? introduce,
bool? loginStatus,
String? lastLoginTime,
int? point,
List<String>? blockedUsers,
String? fcmToken,
String? country,
}) = _UserInfo;

factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
}

 

-freezed 설치

순서대로 terminal에 입력하면 된다. 필요에 따라 pubdev에서 따로 yaml에 설치해도된다.

flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed
flutter pub add json_annotation
flutter pub add --dev json_serializable

 

 

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user_info.freezed.dart';
part 'user_info.g.dart';

@freezed
class UserInfo with _$UserInfo {
  const factory UserInfo({
    String? uid,
    String? nickName,
    int? age,
    int? gender,
  }) = _UserInfo;

  factory UserInfo.fromJson(Map<String, dynamic> json) => _$UserInfoFromJson(json);
}

 

freezed를 적용 시키기 위해선 flutter pub run build_runner run을 실행해줘야한다. 

하지만, 그 전에 미리 선언해야 할 부분이 있다.

 

1. 클래스명 상단에 @freezed 입력 

2. part '모델 파일명.freezed.dart'

3. 경우에 따라 json 직렬화 추가할 경우 '모델 파일명.g.dart'를 추가해야한다. firebase를 연동하기 위해 사용하였다.

4. with _$UserInfo와 같이 '_$클래스명' 이름의 mixin 참조 

5. factory 생성자 정의 

 

코드 작성이 끝나면 빨간줄이 여러개 발생한다. part가 되는 파일과 미리 정의 해둔 mixin,class는 명령어를 통해 생성한다.

flutter pub run build_runner run

 

정상적으로 build가 실행되면 model 안에 part로 추가한 2개의 파일이 생성된것을 확인할 수 있다.

 

freezed의 기능을 살펴보면

-컨스트럭터 및 property 자동생성

final user1 = UserInfo(uid: 1, nickName: 'circle', age: 30, gender: 1);

print(user1.uid);
print(user1.nickName);
print(user1.age);
print(user1.gender);

//1
//circle
//30
//1

자동으로 클래스 property들을 제작해준다.

 

-CopyWith

freezed는 immutable(불변)객체를 생성하기 때문에 setter를 설정이 안된다. 그래서 1.원본객체복사,2.속성변경,3.새로운 객체 사용 순으로 copyWith 메소드를 사용한다.

final user1 = UserInfo(uid:1, nickName: 'circle', age: 30, gender: 1);
final user2 = user1.copyWith(nickName: 'square');

print(user2);

//UserInfo(id:1, nickName: 'square', age: 30, gender: 1)

 

-toString & Json

freezed는 toString() 메소드가 자동으로 @override 되어 유용하다.

final user1 = UserInfo(uid:1, nickName: 'circle', age: 30, gender: 1);

print('$user1');

//UserInfo(uid:1, nickName: 'circle', age: 30, gender: 1)

이런식으로 쉽게 모델의 정보를 확인할 수 있다.

-Operator ==  & hashCode

freezed는 == & hasCode 메소드도 자동으로 @override한다. 

1. '==' 일반적으로, 객체의 동등성 비교는 객체의 참조(메모리 주소)를 비교한다. 그러나 freezed를 사용하면 '==' 연산자는 객체의 모든 필드 값이 같은지 비교하여 판단한다.

2. 'hashCode'는 객체의 해시 코드를 제공하는 메서드이다. 마찬가지로 두 객체가 동일한 필드값을 가지면 동일한 해시 코드를 가질 것임을 보장한다.

final user1 = UserInfo(uid: '1', nickName: 'circle', age: 30, gender: 1);
final user2 = UserInfo(uid: '1', nickName: 'circle', age: 30, gender: 1);

if (user1 == user2) {
  print("두 사용자는 동일합니다.");
} else {
  print("두 사용자는 다릅니다.");
}


//두 사용자는 동일합니다. 
//라고 출력된다.

 

 


이 외에도 freezed는 Assert, deepCopy, Union, map, maybeWhen, maybeMap 기능을 사용할 수 있다. 

 

이 기능을 사용하게 된다면 적용해서 업데이트 하도록 하겠습니다.!