[Android] 앱 아키텍처 - UI Layer

Taewoo Kim·2022년 9월 20일
1

[Android]

목록 보기
3/8

모바일 앱 사용자 환경

앱의 구조는 앞서 정리한 것 처럼 Activity, service, content provider, broadcast reciver와 fregment를 비롯하여 어러 앱 구성요소가 포함되어 있다.

이러한 구성요소들은 대부분 Manifest에서 선언을 한다. 그러면 OS에서 기기의 전반적인 사용자 환경에 앱을 통합하는 방법을 결정하게 되는데 Android 앱은 여러 구성 요소를 포함하고, 짧은 시간 내에 여러 앱과 상호작용을 할 때도 많기 때문에 다양한 워크플로와 작업에 맞게 조정도리 수 있어야 한다.

그렇기 때문에 앱 구성요소는 개별적이고 비순차적으로 실행될 수 있으며, 운영체제나 사용자가 언제든지 앱 구성 요소를 제거할 수 있어야 한다. 이러한 이벤트는 직접 제어할 수 없기 때문에 앱 구성요소에 애플리케이션 데이터나 상태를 저장해서는 안 되며 앱 구성요소가 서로 종속되면 안된다.

일반 아키텍처 원칙

1. 관심사 분리 (Separation of concerns)

Activity 또는 Fragment에 모든 코드를 작성은 X
-> UI 기반의 클래스는 UI 및 운영체제 상호작용을 처리하는 로직만 포함해야함.
이러한 클래스를 최대한 가볍게 유지하여 구성요소 수명 주기와 관련된 많은 문제를 피하고 그러한 클래스의 테스트 가능성을 개선할 수 있다.

[Activity 및 Fregment 구현 클래스는 Android OS와 앱 사이를 이어주는 연결에 불과하다.]
그렇기 때문에 메모리 부족과 같은 시스템 조건으로 인해 OS가 언제든지 클래스를 제거할 수 있다. 만족스러운 UX와 더욱 수월한 앱 관리 환경을 위해 UI 클래스에 대한 의존성을 최소화 하여야 한다.

2. 데이터 모델에서 UI 도출하기

다음 원칙은 데이터 모델에서 UI를 도출해야 한다는 것이다.

가급적 지속적인 모델을 권장한다. 왜냐하면 Data Model은 앱의 데이터를 나타내며, 앱의 UI 요소 및 기타 구성요소로부터 독립되어 있다. 그렇기 때문에 앱의 수명주기나 관련 문제의 영향을 받지 않는다.

  • Android OS에서 리소스를 호가보하기 위해 앱을 제거해도 사용자 데이터가 삭제되지 않음.
  • 네트워크 연결이 취약하거나 연결되어 있지 않아도 앱이 계속 작동.

데이터 모델 클래스를 기반으로 앱 아키텍처를 구축하면 앱의 테스트 가능서오가 견고성이 더 높아짐.

3. 단일 소스 저장소

새로운 유형을 정의할 때는 데이터 유형에 단일 소스 저장소(SSOT)를 할당.

SSOT는 데이터의 소유자이며, SSOT만 데이터를 수정하거나 변경할 수 있음.
SSOT는 이를 위해 불변 유형을 사용하여 데이터를 노출하며, 다른 유형이 호출할 수 있는 이벤트를 수신하거나 함수를 노출하여 데이터를 수정.

3가지의 이점으로는

  • 특정 유형 데이터의 모든 변경사항을 한곳으로 일원화
  • 다른 유형이 조작할 수 없도록 데이터를 보호
  • 데이터 변경사항을 더 쉽게 추적할 수 있도록 합니다. 따라서 버그를 발견하기가 쉬워짐.

4.단반향 데이터 흐름

SSOT는 UDF 패턴과 함께 사용됨.

UDF는 상태와 데이터가 한 방향으로 이동한다.
-> 이는 데이터가 일관성을 강화하고, 오류가 발생할 확률을 줄여 주며, 디버그하기 쉽고, SSOT 패턴의 모든 이점을 제공.

권장 앱 아키텍처

  • 화면에 애플리케이션 데이터를 표시하는 UI Layer
  • 앱의 비즈니스 로직을 포함하고 애플리케이션 데이터를 노출하는 Data Layer
  • UI와 Data Layer 간의 상호작용을 간소화하고 재사용하기 위한 Domain Layer라는 Layer를 추가할 수 있음.

1. UI Layer

UI의 역할은 화면에 애플리케이션 데이터를 표시하고 사용자 상호작용의 기본 지점으로서의 역할을 수행.

사용자 상호작용(ex: 버튼누르기) 또는 외부 입력(ex : 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트 되어야 합니다.

일반적으로 데이터 레이어에서 가져오는 APP Data는 표시해야 하는 정보와 다른 형식이다. UI레이어는 애플리케이션 데이터 변경사항을 UI가 표시할 수 있는 형식으로 변환한 후에 표시하는 파이프라인이다.

1-1 UI Layer 아키텍처

UI라는 용어는 API에 상관없이 데이터를 표시하는 활동 및 프래그먼트와 같은 UI 요소를 가리킨다.

데이터 레이어의 역할은 앱 데이터를 보유하고 관리하며 앱 데이터에 액세스할 권한을 제공하는 것이므로 UI Layer에서 다음 단계를 실행해야 합니다.

  1. 앱 데이터를 사용하고 UI에서 쉽게 렌더링할 수 있는 데이터로 변환.
  2. UI 렌더링 가능 데이터를 사용하고 사용자에게 표시할 UI 요소로 변환.
  3. 이렇게 조합된 UI 요소의 사용자 입력 이벤트를 사용하고 입력 이벤트의 결과를 필요에 따라 UI 데이터에 반영.
  4. 1~3단계를 필요한 만큼 반복

1-2 UI 상태 정의

UI에는 각 메타데이터와 함께 UI가 표시되고, 앱에서 사용자에게 표시하는 이 정보가 UI 상태이다.

즉, 사용자가 보는 항목이 UI라면 UI상태는 앱에서 사용자가 봐야 한다고 지정하는 항목.

불변성

UI 상태 정의는 변경할 수 없다. 불변성의 주요 이점은 변경 불가능한 (immutable)한 객체가 순간의 애플리케이션 상태를 보장한다는 점이다.

-> 변경 불가능한 스냅샷

1-3 단방향 데이터 흐름으로 상태 관리

UI 상태가 immutable한 스냅샷임을 확인했다. 하지만 App Data의 동적 특성에 따라 상태는 시간이 지나면서 변경될 수 있으며 이는 앱을 채우는 데 사용되는 기본 데이터를 수정하는 사용자 상호작용이나 기타 이벤트로 인해 발생하기도 한다.

상태 홀더

  • UI 상태를 생성하는 역할을 담당하고 생성 작업에 필요한 로직을 포함하는 클래스
  • 상태홀더의 크기는 하단 앱 바와 같은 단일 위젯부터 전체 화면이나 탐색 대상에 이르기까지 관리 대상 UI 요소의 범위에 따라 다양.

UI와 상태 생성자 간의 상호 종소을 모델링하는 방법은 다양하다. 하지만 UI와 ViewModel 클래스 사이의 상호작용은 대체로 이벤트 입력과 입력의 후속 상태인 출력으로 간주될 수 있으므로 관계는 다음 다이어그램과 같음.

단방향 데이터 흐름(UDF)으로 이 패턴이 앱 아키텍처에 미치는 영향은 다음과 같음.

  • ViewModel이 UI에 사용될 상태를 보유하고 노출합니다. UI 상태는 ViewModel에 의해 변환된 애플리케이션 데이터.
  • UI가 ViewModel에 사용자 이벤트를 알림.
  • ViewModel이 사용자 작업을 처리하고 상태를 업데이트.
  • 업데이트된 상태가 렌더링할 UI에 다시 제공.
  • 상태 변경을 야기하는 모든 이벤트에 위의 작업이 반복.

로직의 유형

  • 비즈니스 로직
  • UI 동작 로직 : Android Resouces를 사용하여 화면에 표시할 올바른 텍스트를 가져오거나, 버튼을 클릭할 때 특정 화면 이동, Toasts 메시지 or Snackbar를 사용하여 화면에 사용자 메시지를 표시.

특히 Context 같은 UI 유형의 경우 UI 로직은 ViewModel이 아닌 UI에 있어야 함.

UDF를 사용하는 이유

  • 데이터 일관성
  • 테스트 가능성
  • 유지 가능성

1-4 UI 상태 노출

UI상태를 정의하고 이 상태의 생성을 관리할 방법을 결정한 후에는 상태를 UI에 표시하는 단계를 진행함.

UDF를 사용하여 상태 생성을 관리하므로 생성된 상태를 스트림으로 간주.

시간 경과에 따라 여러 버전의 상태가 생성됨. 따라서 LiveData, StateFlow와 같이 관찰 가능한 데이터 홀더에 UI 상태를 노출.

이유는 ViewModel에서 데이터를 직접 가져오지 않고도 UI가 상태 변경사항에 반응할 수 있도록 하기 위해서임. 이러한 유형은 최신 버전의 UI 상태를 캐시한다는 이점도 있음. 이는 구성 변경 후 빠른 상태 복원에 유용.

UI State객체에 필드가 많을수록 스트림이 내보내질 가능성이 크다. -> distinctUntilChange()를 통해 완화 작업이 필요.

1-4 UI 상태 사용

UI에서 UiState 객체의 스트림을 사용하려면 사용 중인 관찰 가능한 데이터 유형에 터미널 연산자를 사용합니다. (ex: LivaData = observe() , Kotlin flow = collect() 메서드를 사용합니다.

LiveData를 사용하면 LifecycleOwner가 수명 주기 문제를 암시적으로 처리. flow를 사용할 경우 적절한 코루틴 범위와 repeatOnLifecycle API로 처리하는 것이 가장 좋다.

1-5 스레딩 및 동시 실행

ViewModel에서 실행되는 모든 작업은 기본 스레드에서 안전하게 호출된다는 기본 안전성을 갖추어야 합니다. 데이터 레이어와 도메인 레이어가 작업을 다른 스레드로 옮기는 역할을 담당하기 때문입니다.

Kotlin의 Coroutine은 동시 실행 작업을 관리하는 좋은 방법이다.

2. UI 이벤트

UI 레이어에서 ViewModel로 처리해야 하는 작업.

ViewModel은 일반적으로 특정 사용자 이벤트의 비즈니스 로직을 처리 (예: 사용자가 일부 데이터를 새로고침하는 버튼을 클릭하는 경우). 보통 UI에서 호출할 수 있는 함수를 노출하여 이러한 로직을 처리. 사용자 이벤트에는 UI에서 직접 처리할 수 있는 UI 동작 로직이 있을 수도 있음.(예: 화면 이동, Snackbar표시)

비즈니스 로직은 결제 또는 사용자 환경설정 저장과 같은 상태 변경과 관련하여 필요한 조치를 말한다. 도메인과 데이터 레이어는 일반적으로 이 로직을 처리함.

이 가이드에선 ACC ViewModel 클래스가 비즈니스 로직을 처리하는 클래스의 추천 솔루션으로 사용.

2-1 UI 이벤트 결정 트리

2-2 사용자 이벤트 처리

확장 가능한 항목의 상태와 같이 UI 요소의 상태 수정과 관련된 경우 UI에서 사용자 이벤트를 직접 처리할 수 있음. 비즈니스 로직 같은 경우 ViewModel에서 처리

RecyclerView의 사용자 이벤트

RecyclerView item or custom View처럼 UI 트리 아래쪽에서 작업이 생성되는 경우에도 ViewModel이 사용자 이벤트를 처리해야 함.

RecyclerView의 Item에서 비즈니스 로직 처리가 필요한 이벤트가 발생한다면

  • Item에 바인딩되는 UI State 객체에 함수 타입의 프로퍼티를 두는 방법,

RecycleView 어댑터가 필요한 데이터에만 접근할 수 있게되어 ViewModel의 모든 부분에 접근할 수 없어져 ViewModel이 노출되는 부분이 악용될 가능성이 낮아짐.

어댑터를 ViewModel 클래스와 긴밀하게 결합하므로 ViewModel을 RecyclerView 어댑터에 전달하는 것은 좋지 않음.

2-3 ViewModel 이벤트 처리

ViewModel 이벤트에서 발생하는 UI작업(ViewModel 이벤트)은 항상 UI State 업데이트로 이어짐 이는 (UDF의 원칙을 준수)

UI state의 업데이트를 통해 Configuration Change와 같은 이벤트 이후에도 UI이벤트를 재현할 수 있으며 UI 이벤트가 손실되지 않음.

2-4 기타 사용 사례

UI State 업데이트로 UI 이벤트 사용 사례를 해결할 수 없다고 생각되면 App의 Data Flow 방식을 다시 고려해야 할 수도 있음.

  • 각 클래스에서 각자의 역할만을 수행해야 함.
  • 이벤트가 발생하는 위치를 생각.
  • 소비자가 여러 명이고 이벤트가 여러 번 소비될 것이 우려된다면 앱 아키텍처를 다시 고려.
  • 상태를 소비해야 하는 경우를 생각.

0개의 댓글