[Flutter] Freezed 사용해보기 #1

leeeeeoy·2021년 9월 20일
2

이 글은 공식 문서와 유튜브 강의를 보고 배운 내용을 바탕으로 정리한 글입니다.

Freezed란?

데이터 클래스에서 필요한 기능들을 Code Generation으로 제공해주는 라이브러리이다(저번에 사용한 Retrofit과 같이 필요한 코드를 생성해준다). 몇가지 편리한 기능들을 제공해주는데 json_serializable, copy, toString override, assert 등 편의성 기능들을 제공해준다. 기능들이 상당히 많지만 강의를 보면서 사용한 기능들 위주로 작성해보았다.

사용방법

Package 설정

pubspec.yaml에 다음과 같이 추가해준다.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  freezed_annotation:

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner:
  json_serializable:
  freezed:
  

필수 packages

  • freezed_annotation:
  • freezed:
  • build_runner:

json packages

  • json_serializable:
    필수 패키지는 아니지만 fromJson, toJson 기능을 같이 사용한다면 포함시켜야 한다.

코드 작성

1. 기본 기능들

user.dart

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.g.dart';
part 'user.freezed.dart';


class User with _$User {
  factory User({
    required int id,
    required String name,
    required String job,
  }) = _User;

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
  • @freezed annotation을 이용해 class를 생성한다
  • factory 생성자에 필요한 property를 작성한다
  • Json 기능을 같이 사용하려면 fromJson 메서드만 작성을 해준다.

1-1. constructor and property 자동 생성

final user1 = User(id: 1, name: 'leeeeeoy', job: 'student');

print(user1.id);
print(user1.name);
print(user1.job);
// 1
// leeeeeoy
// student
  • 자동으로 constructor와 property 들을 작성해준다.
    (별도로 작성 필요x)

1-2. toString override, Json method 생성

final user1 = User(id: 1, name: 'leeeeeoy', job: 'student');

print(user1.toString());
print(user1.toJson());
// User(id: 1, name: leeeeeoy, job: student)
// {id: 1, name: leeeeeoy, job: student}
  • toString() 함수를 override 해서 정보를 제공해주는 형태로 출력한다
    (일반적으로 instance에 toString()을 실행시키면 Instance of Class 라고 출력된다)
  • fromJson(), toJson() 함수를 자동으로 작성해준다
    (json_serilizable의 경우 둘 다 작성해야 했지만, freezed는 fromJson만 작성해도 두 함수 모두 다 작성된다. 한줄이라도 줄여주니 조금 더 나은 것 같다)

1-3. == 연산 비교 및 hasCode override

  final user1 = User(id: 1, name: 'leeeeeoy', job: 'student');
  final user2 = User(id: 1, name: 'leeeeeoy', job: 'student');
  final user3 = User(id: 3, name: 'leeeeeoy', job: 'programer');
  
  print(user1 == user2);
  print(user1.hashCode == user2.hashCode);
  print(user1 == user3);
  // true
  // true
  // false
  • == 연산 비교와 hasCode를 override 한다. 따라서 상식적인 비교가 가능하다
    (일반적으로 인스턴스를 비교하면 메모리 위치를 비교하기 때문에 같은 클래스 인스턴스와 같은 필드 값들을 가져도 false를 가지게 된다.)

2. 알아두면 유용한 기능들

기본 기능들 이외에도 Assert, Custom method, getter, copy, deep copy를 지원한다.

2-1. Assert


class Member with _$Member {
  ('name.length < 6', '이름은 5글자 이하로 작성해주세요.')
  factory Member({
    required int id,
    required String name,
  }) = _Member;
}

final member1 = Member(id: 1, name: 'Yoel');

final member2 = Member(id: 1, name: 'leeeeeoy');
// assert에러 --> 이름은 5글자 이하로 작성해주세요
  • @Assert annotation을 이용해 특정 property의 값을 제한 할 수 있다.
  • @Assert 조건에 벗어나면 인스턴스 생성 시 오류를 발생시킨다
  • 여러개를 작성하고 싶으면 factory 생성자 위에 이어서 작성하면 된다.

2-2. Custom method 와 getter


class Member with _$Member {
  factory Member({
    required int id,
    required String name,
  }) = _Member;

  // Custom getter or method 작성을 위해 필요
  Member._();

  get nameLength => name.length;

  void hello() {
    print('Member의 Custom method 입니다.');
  }
}

final member3 = Member(id: 1, name: 'leeeeeoy');

print(member3.nameLength);
member3.hello();
// 8
// Member의 Custom method 입니다.
  • custom method와 getter를 설정할 수 있다. 이 때 internal constructor를 필수로 추가해줘야 한다.
  • setter를 설정하는 건 불가능하다 (freezed는 immutable 사용을 목적으로 하기 때문이다).

2-3. Copy 와 Deep Copy


class Member with _$Member {
  factory Member({
    required int id,
    required String name,
    required Team team,
  }) = _Member;
}


class Team with _$Team {
  factory Team({
    required int id,
    required String name,
    required Company company,
  }) = _Team;
}


class Company with _$Company {
  factory Company({
    required int id,
    required String name,
  }) = _Company;
}


final company1 = Company(id: 1, name: 'KAU');
final team1 = Team(id: 1, name: 'Ateam', company: company1);
final member1 = Member(id: 1, name: 'Yoel', team: team1);

// copy, id만 변경
final member2 = member1.copyWith(id:2);

// deep copy, Company의 name만 변경
final member3 = member1.copyWith.team.company(name: 'Kakao');

print(member2);
print(member3);
// Member(id: 2, name: Yoel, team: Team(id: 1, name: Ateam, company: Company(id: 1, name: KAU)))
// Member(id: 1, name: Yoel, team: Team(id: 1, name: Ateam, company: Company(id: 1, name: Kakao)))
  • freezed는 앞서 말한 것처럼 immutable을 목적으로 한다. 따라서 setter 설정이 불가능하며 값을 변경하고 싶을 땐 새로운 인스턴스를 생성하거나 copy 메서드를 이용한다.
  • copyWith method를 override 해주기 때문에 예시처럼 간편하게 작성이 가능하다.

3. 그 외 기능들

써보지는 않았지만 공식 문서를 참고해보면 여러가지 기능들이 많이 있는 것 같다. 그 중 몇가지를 정리해보았다.

3-1. Default value

abstract class Example with _$Example {
  const factory Example([(42) int value]) = _Example;
}
  • freezed를 사용 할 때는 @Default annotation을 이용해 기본값을 설정 할 수 있다.
  • json 메서드를 이용하면 자동으로 @JsonKey(defaultValue: value)를 적용시켜준다

3-2. Union


class Person with _$Person {
  factory Person({
    required int id,
    required String name,
    required int age,
    required int statusCode,
  }) = _Person;

  factory Person.loading({required int statusCode}) = _Loading;

  factory Person.error(String message, {required int statusCode}) = _Error;
}
  • union 기능을 이용해 다른 인스턴스를 돌려주는 것도 가능하다
  • 이 때 공통적으로 제공하는 변수만 가져올 수 있다.
    ex) statusCode
  • when, maybeWhen, map, maybeMap 등을 사용해서 값을 불러올 수 있다.
  • 사실 자주 사용하지 않을 것 같아서 자세히 살펴보지 않았다...

정리

저번에 사용해본 retrofit과 마찬가지로 상당히 편리한 기능들이 많이 있는 것 같다. 작성해야 할 코드가 줄어드는 것만 해도 이미 좋아보인다. 아마 retrofit과 freezed 둘 다 적용해서 사용하면 더 편리하게 사용이 가능 할 것 같다. 다음에 한 번 적용해 봐야겠다.

소스코드 https://github.com/leeeeeoy/flutter_personal_study/tree/master/lib/pages/freezed


참고링크

profile
100년 후엔 풀스택

0개의 댓글