Flutter, '아' 다르고 '어' 다른 Extension 키워드 활용 포맷팅 로직

Ximya(심야)·2023년 3월 17일
2

Plotz 개발일지

목록 보기
2/12
post-thumbnail

해당 포스팅은 유튜브 영화&드라마 리뷰 영상 큐레이션 플랫폼 Plotz를 개발하면서 도입된 기술 및 방법론에 대한 내용을 다루고 있습니다.
다운로드 링크 : 앱스토어 / 플레이스토어

여러분들은 보통 원시 타입의 데이터(String, Int ...)를 특정 형태로 포맷팅하는 기능을 어떻게 구현하시나요?
정수를 천 단위마다 콤마로 구분하고 '원'이라는 단위를 표시하는 형태로 변환해본다고 가정해봅시다.

리턴 함수

먼저 간단하게 정수를 인자로 받고 반환 타입이 문자열인 함수를 만들어 볼 수 있겠죠.

formatNumbWithWonSuffix 함수에서는 정규식을 이용해서 천 단위로 콤마(,)를 삽입하고 '원' 단위로 붙이는 기능을 수행하고 있습니다.

좀 더 Fancy한 방법이 없을까?

하지만 만약 해당 포맷팅 로직이 여러 곳에서 자주 사용된다면 어떻게 할까요?
물론 매번 함수를 작성하거나, 해당 함수를 전역으로 관리하는 것도 하나의 방법일 수 있겠지만
조금 더 Fancy한 방법이 있을 것 같습니다.

Dart 2.6버전에 추가된 Extension 키워드를 이용하면 보다 좀 더 직관적으로 포맷팅 로직을 구현할 수 있습니다.
그럼 동일하게 정수를 천 단위로 나누고 '원' 단위를 붙이는 로직의 extension 활용 버전을 확인해봅시다.

extension NumForammter on int {
  String get numWithWonSuffix  {
  String numberStr = '$this';
  RegExp regExp = RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))');
  final foramattedNum = numberStr.replaceAllMapped(
    regExp,
    (Match match) => '${match[1]},',
  );
  return foramattedNum + '원';
  }
}


void main() {
  int number = 1000000;
  print(number.numWithWonSuffix); // 출력: 1,000,000
}

실행 함수 부분을 보시면 정수에 접근 연산자 구문으로 쉽게 포맷팅 기능을 실행하고 있습니다.
더 직관적인 형태죠.

이처럼 Dart에서 일반 메서드와 함께 확장 메소드를 제안하는 Extension에서 키워를 이용하면 조금 더 가독성 고려해 데이터를 포맷팅 로직을 구성할 수 있고 전역으로 사용할 수 있기 때문에 편리함을 제공하기도 합니다.
또한 단순히 포맷팅 로직뿐만 아니라 다양한 Operating로직도 구현이 가능합니다.
extension 사용 유무에 따른 예시를 통해 알아보죠.


1. Dart Extension (Basic Usecase)

유저의 아이디의 최소 글자 수(최소 7자 이상)를 제한하는 클래스를 구현한다고 가정합니다.
String Extension 사용 여부에 따라 아래와 같이 구분할 수 있습니다.

Not Using Extension

class UserIdValidation {
  static bool isValidLenght(String str) {
    return str.length >= 7 ? true : false;
  }
}

class UserIdValidation {
  static bool isValidLenght(String str) {
    return str.length >= 7 ? true : false;
  }
}

void main() {
  UserIdValidation.isValidLenght("vkdl370528") // true
	UserIdValidation.isValidLenght("Ximya") // false
}
  • String을 인자로 받아 boolean 값을 리턴하는 클래스 메소드.
  • 메소드가 static으로 선언되었기 때문에 별도의 인스턴스를 생성하지 않음.

Using Extension

extension UserIdValidation on String {
  bool get isIdValidLength {
    return this.length >= 7 ? true : false;
  }
}

void main() {
  "vkdl370528".isValidLenght // true
	"Ximya".isValidLength // false
}
  • Extension의 on 키워드를 통해 Extend 할 타입을 설정
  • Get 접근자를 통해 Extension 메소드에 접근할 수 있음.
  • 좀 더 직관적으로 Extension 메소드를 사용할 수 있음.

2. Dart Extension With Operater

이번에는 두 개의 단어를 더하는 기능을 제공하는 Extension에 대한 예시입니다.

Without Using Operator

 extension StringExtension on String {
  String concatWithSpace(String other) {
    return '$this $other';
  }
}

void main () {
	"XimYa".concatWithSpace("Kim") // Ximya Kim
}
  • String을 인자로 받아서 String Join이 가능
  • 인자로 받는 String과 extend 하는 String은 $ (dollarSign) 의 키워드로 참조하여 구분함.

Using Operator Syntax

extension StringExtension on String {
  String operator &(String other) => '$this $other';
}

void main() {
	"Ximya" & "Kim" // Ximya Kim
}
  • 위에 코드도 기능적으로 이상이 없지만 extension 메소드 내에 operator 를 이용하여 좀 더 직관적으로 표현이 가능함.

실제 적용 사례

그럼 순삭앱에서는 어떻게 extension이 활용되었을까요?

1. Image Path Prefix Extension

순삭에서 Tmdb API를 이용해서 영화 & 드라마 콘텐츠의 이미지 제목 등의 정보를 불러옵니다.

  "backdrops": [
    {
      "aspect_ratio": 1.778,
      "height": 2160,
      "iso_639_1": null,
      "file_path": "/8ZTVqvKDQ8emSGUEMjsS4yHAwrp.jpg", <-- image url
      "vote_average": 5.522,
      "vote_count": 4,
      "width": 3840
    },
    {
      "aspect_ratio": 1.778,
      "height": 1080,
      "iso_639_1": null,
      "file_path": "/s3TBrRGB1iav7gFOCNx3H31MoES.jpg",
      "vote_average": 5.396,
      "vote_count": 12,
      "width": 1920
    },
    ...

위 TMDB API JSON 응답 코드를 보시면 url 이미지 주소가 "./{....}.jpg"로 구성되어 있는데요.
Flutter 이미지 위젯 속성에 값을 전달하기 위해서는 네트워크 이미지 형태가 되어야 합니다.
https://image.tmdb.org/t/p/original문자열과 서버에서 받아온 image_path 데이터가 결합되어야 하는거죠.

extension TmdbFormatter on String {
  String get prefixTmdbImgPath {
    return 'https://image.tmdb.org/t/p/original$this';
  }
}
CachedNetworkImage(imageUrl: vm.imgUrl.prefixTmdbImgPath)

그래서 저는 TmdbFormatter extension을 통해 보다 쉽게 image_path에 필요한 문자열을 쉽게 prefix할 수 있도록 했습니다.


2. Comparison Helper Extension With Enum

또한 Extension은 Enum과 같이 사용할 때 굉장히 편리합니다.
순삭에서는 ContentType 이라는 Enum 클래스를 정의하여 콘텐츠의 영화 & 드라마 타입을 구분하고 있습니다.

enum ContentType {
  tv('드라마'),
  movie('영화');

  const ContentType(this.name);

  final String name;

  factory ContentType.fromString(String originStr) {
    switch (originStr) {
      case 'tv':
        return ContentType.tv;
      case 't':
        return ContentType.tv;
      case 'movie':
        return ContentType.movie;
      case 'm':
        return ContentType.movie;
      default:
        throw Exception('enum not found');
    }
  }
}

순삭앱의 여러 핵심 도메인 로직에서 컨텐츠 타입을 판단하는 로직이 사용됩니다.

final ContentType contentA;

예를 들어, contentA 변수의 타입이 movie인지 tv인지 판단하기 위해서 비교연산자를 이용할 수 있겠지만,

if(contentA == ContentType.movie) {
	... some method
} 

ContentType의 조건을 판별하는 extension을 만들어준다면

extension DeterminContentType on ContentType {
  bool get isMovie {
    return this == ContentType.movie ? true : false;
  }

  bool get isTv {
    return this == ContentType.tv ? true : false;
  }
}

아래와 같이 조금 더 직관적으로 비교 연산 구문을 작성할 수 있습니다.

if(contentA.isMovie) {
	... some  method
} 

마무리하면서

이번 글에서 extension 키워드를 활용해서 조금 더 직관적인 operating, formatting 등의 로직을 작성하는 방법에 대해 다루어봤습니다.
사실 extension이 없더라도 기능 명세를 구현하는데 큰 제약이 없지만,
조금 더 코드의 가독성과 효율적인 구조를 위해 extension을 적극 도입하는 게 좋다고 생각합니다😉

profile
https://medium.com/@ximya

2개의 댓글

comment-user-thumbnail
2023년 12월 9일

좋은 글 잘 보고 갑니다~ 정리를 잘해주셔서 도움이 되네요 extension은 잘 사용 중이었는데 operator 새로 알아갑니다!

1개의 답글