[Flutter] 아토믹 패턴에 대해서

myming·2023년 11월 27일
0

Atomic 패턴

React, Vue, Flutter의 공통 특징은 Component 중심 설계 패턴으로 되어 있습니다.

이전 방식은 하나의 페이지를 통으로 설계하는 방식이 대부분이지만 이제는 컴포넌트 중심 설계 패턴 라이브러리와 프레임워크들이 유행하면서 컴포넌트들을 잘 쪼개서 설계하고 이를 합쳐 하나의 페이지로 만드는 방식으로 UI개발 패러다임의 전환이 일어났습니다.

아토믹 설계의 핵심

  1. UI의 재사용성을 생각하여 개발하는 것
  2. UI의 결합도를 낮추는 것
  3. 테스트를 용이하게 작성할 수 있도록 하여야 합니다.

Atomic Design Pattern

소개

왜 아토믹 패턴을 사용해야 하는가?

  • 체계적인 코드 관리 (미리 정해진 룰대로 하므로 UI 영역이 잘 분리 되어 있다.)
  • 코드 재사용 (전역적인 관리 가능)
  • 렌더링 최적화(state가 있는 위젯을 세부 분야까지 체계적으로 모두 나누면서)
  • 기능 별로 분리 (UI 별로 단일 책임 원칙을 지킬 수 있도록 한다.)
  • 테스트 용이성 (어느 부분을 기준으로 위젯테스트를 할지 정확히 팀원과 정할 수 있다.)

🔥 참고 : UI에서 재사용되는 앱로직의 경우 계속 구현하지 않도록 아래와 같이 미리 mixin에 재사용할 수 있도록 구현해 놓으면 해당 믹스인으로 앱로직을 관리할 수 있으므로 좋습니다. 또한 앱로직과 비즈니스로직과는 섞이지 않도록 구현하는 방식이 방식이 가장 좋습니다.

참고에 대한 예시


/// 썸네일 리스트 관련 속성들을 모아놓은 믹스인
mixin ThumbnailListUiMixin {
  /// 썸네일에서 데이터를 보여줄 UI 모델 리스트
  final thumbnailUIList = <ThumbnailModel>[].obs;

  /// 썸네일 리스트의 height
  double? height;

  /// 체크박스가 활성화 되었는지?
  final isCheckBoxEnabled = false.obs;

  /// 썸네일 위젯이 선택되었을 경우의 색상
  Color? primary;

  /// 썸네일들의 패딩 값
  EdgeInsetsGeometry? padding;

  /// 스크롤바를 보일지 여부
  bool? isScrollbarVisible;

  /// 해당 썸네일 데이터가 로딩 중인지?
  final isThumbnailLoading = false.obs;

  /// 체크박스의 값들의 리스트
  final checkBoxIndexList = RxList<int>();

  /// 썸네일 리스트의 스크롤 컨트롤러
  final thumbnailListScrollController = AutoScrollController();

  /// 체크박스 인덱스 리스트가 비어있는지?
  bool get isCheckBoxIndexListEmpty => checkBoxIndexList.isEmpty;

  /// 보여지는 썸네일 UI 리스트가 비어있는지?
  bool get isThumbnailUIList => thumbnailUIList.isEmpty;
  
  

  /// 썸네일을 탭 했을 경우 작동하는 함수
  ///
  /// - [thumbnailIndex] : 썸네일 인덱스
  /// 해당 믹스인에서는 체크박스 UI 관련 동작만 하고,
  /// 확장하고 싶다면 오버라이드를 통해 사용하시면 됩니다.
  Future onThumbnailTap(int thumbnailIndex) async {
    // 체크박스 활성화 상태라면?
    if (isCheckBoxEnabled.value) {
      toggleCheckBoxValue(thumbnailIndex);
      return true;
    }
    // 체크박스 활성화 상태가 아니라면?
    return false;
  }

  /// 해당 썸네일이 체크되었는지 확인하는 함수
  ///
  /// - [thumbnailIndex] : 썸네일 인덱스
  /// - [isCheckBoxEnabled] 변수에 종속되어 있어서 [isCheckBoxEnabled]가 true일 경우에만 동작합니다.
  bool isChecked(int thumbnailIndex) {
    // thumbnailIndex가 checkBoxValueList에 포함되어 있다면 이미 체크 된 값이라는 뜻이다.
    // 그러므로 체크 되어 있으면 true, 안되어 있으면 false를 리턴한다.
    return checkBoxIndexList.contains(thumbnailIndex);
  }

  /// 체크박스 값 리스트에서 썸네일 인덱스에 해당하는 값들에 대해서 추가하고 삭제하는 작업을 하는 함수
  ///
  /// - [thumbnailIndex] : 썸네일 인덱스
  void toggleCheckBoxValue(int thumbnailIndex) {
    // 체크박스 값 리스트에 썸네일 인덱스가 들어있다면?
    if (isChecked(thumbnailIndex)) {
      // 체크박스 값 리스트에 썸네일 인덱스를 제거합니다.
      checkBoxIndexList.remove(thumbnailIndex);
      return;
    }
    // 체크박스 값 리스트에 썸네일 인덱스를 추가합니다.
    checkBoxIndexList.add(thumbnailIndex);
    return;
  }

  /// 체크박스 관련된 로직을 클리어하는 함수
  void clearCheckBox() {
    // 체크박스를 다 풀어준다.
    checkBoxIndexList.clear();
    // 체크박스가 가능하지 않다는 상태로 변경합니다.
    isCheckBoxEnabled.value = false;
  }

  /// 썸네일 리스트를 마지막 스크롤로 이동시키는 함수
  void moveScrollToLastIndex() {
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      // 스크롤 컨트롤러가 위젯에 붙은 경우에만 스크롤 진행합니다.
      if (thumbnailListScrollController.hasClients) {
        // 위젯에 잘 붙었을 경우 마지막 인덱스로 이동하도록 합니다.
        thumbnailListScrollController
            .jumpTo(thumbnailListScrollController.position.maxScrollExtent);
      }
    });
  }
}

단점

  • Props Drilling 때문에 코드 짜기 어려움 → 서버 api에서 내려오는 값을 기준으로 Wrapper를 만들어 그 곳에 지역 컨트롤러 역할을 배치하고 배치된 컨트롤러에서 비즈니스 로직을 관리하고, 전역 위젯을 만들었다면 전역 위젯에 대한 앱로직을 미리 믹스인으로 구현해 한 세트로 구현할 수 있도록 한다.

  • 어떤 기준으로 분류할지 구분하기 어려움 → atoms, molecules, organisms 구분을 어떻게 할지에 대해 헷갈릴 수 있다. 하지만 atoms의 경우 플러터가 제공하는 기본 버튼은 따로 만들지 않고 molecules에 배치, molecules의 경우 두 개 이상의 위젯이 들어갈 경우에 대해서와 같이 구분 기준을 팀원들과 함께 정하고 해당 기준대로 개발해 나가면 됩니다. 테스트도 atoms 부터 위젯테스트를 작성할지, 아니면 더 큰 범위의 테스트부터 작성할지에 대해 기준을 팀원들과 함께 정하고 해당 기준대로 개발해 나가면 됩니다.

  • 디자이너와 함께 디자인 시스템을 잡으며 원자, 분자, 유기체, 템플릿, 페이지를 협업하며 나누지 않으면 전역으로 사용할 수 있는게 적어짐 -> 디자이너와 함께 잡아가면 좋겠지만 불가능한 환경이라면 UI 부분을 전해지는 Props에 따라 변경 가능하도록 구현하여 전역적으로 재사용 가능하도록 만든다.

Screen 나누기

  • Atoms (원자)
  • Molecules (분자)
  • Organisms(유기체)
  • Templates(템플릿)
  • Pages(페이지)

Atoms 폴더

버튼, 체크박스, 더 작은 범위의 텍스트 스타일, 버튼 스타일와 같은 원자적 요소들을 미리 정의해 놓는 폴더입니다. 더 작은 범위는 토큰 개념을 만들어서 관리할 수도 있습니다. (참고링크)

Molecules 폴더

분자는 결합된 원자의 그룹이며 화합물의 가장 작은 기본 단위입니다. 분자는 속성을 취하고 필요한 경우 임시 상태를 가지며 UI 시스템의 백본 역할을 합니다. 이렇게 하면 임시 상태의 요구 사항에 따라 분자가 상태 비저장 및 상태 저장 위젯으로 존재하게 됩니다.

Organisms 폴더

독립형, 이식 가능 및 재사용 가능한 위젯의 생성을 권장

Templates 폴더

Organisms 폴더에 있는 값들에 전역적인 Props들을 제공하고 Organisms를 재사용하면서 만들어집니다. Templates가 재사용 가능하다면 가장 좋겠지만 재사용 하기 위한 과정에서 디자이너, 서버 개발자와의 협업이 필수 불가결이므로 불가능하다면 Organisms 까지만 재사용 가능하도록 구현하는 것도 괜찮습니다.

Pages 폴더

페이지는 컨텍스트에 올바른 데이터를 제공하여 주어진 템플릿의 특정 인스턴스입니다.

따라서 실제로 구성 요소가 아니라 애플리케이션 화면의 최종 제품에 있는 페이지(예: 요리 앱에 포함된 다양한 레시피 페이지). 모든 레시피는 동일한 템플릿에서 작동하지만 주어진 데이터와 컨텍스트는 레시피를 고유하게 만듭니다.

임시 상태

임시 상태는 다른 위젯에서 거의 액세스할 필요가 없으므로 직렬화할 필요가 없고 복잡한 방식으로 변경되지 않는 단일 위젯 내부에 깔끔하게 포함된 상태입니다. 임시 상태의 경우 ViewModel이나 다른 형태의 상태 관리가 필요하지 않으며 Stateful 위젯만 있으면 됩니다.

앱 상태

앱 상태는 앱의 여러 부분에서 공유하고 사용자 세션 간에 유지하려는 상태입니다. 이것은 사용자 기본 설정, 로그인 정보, 데이터 개체 상태 또는 위젯 간에 공유해야 하는 기타 형태의 상태일 수 있습니다.

UI 컴포넌트 구성의 아키텍처

Project Screens - atomic (재사용이 불가능한 위젯, 없으면 더 좋다.)
Project Root - atomic (이 앱에서만 특정하게 사용될 글로벌 위젯)
Packages - atomic (모노레포 형식을 사용해 다른 프로그램에서도 사용할 수 있도록 모아놓는다.)

Flutter 에서 아토믹 패턴 적용하기

결론적으로 UI에 SRP를 적용하고 블록을 조립해서 하나의 완성품을 만드는 개념 입니다.

페이지 → 이 페이지는 몇개의 organism들로 이루어져 있구나

organism → 이 유기체는 몇개의 분자로 이루어져 있구나

molecules → 이 분자는 몇개의 원자로 이루어져 있구나

또한 유기체 → 유기체, 분자, 이런 식으로 섞여서 만들 수도 있다.

디자인과 함께 작업을 한다면 효율적으로 모두 global widgets로 뺄 수 있겠지만 디자인의 공통 컴포넌트 개념이 없다면 공통된 부분은 빼고 공통되지 않은 부분은 modules의 views 안에 넣는 방법밖에 없습니다.


참고 링크

아토믹 디자인 책

https://atomicdesign.bradfrost.com/chapter-1/

https://zenn.dev/nagakuta/articles/25c8aaf7744830bdab3d

https://kciter.so/posts/effective-atomic-design

https://devaronius.medium.com/atomic-design-with-flutter-a8f69b68f39d

디자인 시스템을 토큰 개념을 추가하여 만들기
https://medium.com/bancolombia-tech/building-a-design-system-using-atomic-design-methodology-in-flutter-327142bf30c2

profile
플러터로 개발하는걸 가장 선호합니다.

0개의 댓글