프로그램의 새로운 변경사항을 추가하는데 기존의 코드를 변경해야 하는 경우 기존의 코드를 약간만 변경해도 변경사항을 추가할 수 있다면 높은 생산성을 가진다.
일반적인 Android 앱에는 Activity, Fragment, Service, content Provider, Broadcast receiver를 비롯하여 앱 구성요소가 포함된다. 개발자는 App menifast에서 이러한 앱 구성요소 대부분을 선언하며, Android OS에서 이 파일을 사용하여 기기의 전반적인 사용자 환경에 앱을 통합하는 방법을 결정한다.
=> 앱은 사용자 중심의 다양한 워크플로 및 작업에 맞게 조정될 수 있어야 한다.
또한 휴대기기는 리소스가 제한되어 있으므로, 운영체제에서 새로운 앱을 위한 공간을 확보하도록 언제든지 일부 앱 프로세스를 종료해야 할 수 있다.
-> 이러한 이벤트는 직접 제어할 수 없다.
-> 앱 구성요소에 데이터나 상태를 저장해서는 안되며, 서로 종속되면 안된다.
따라서 가장 중요한 원칙은 관심사 분리이다.
=> 데이터 베이스를 활용한다.
Layer : 관심사에 따라서 분리된 계층
-> 같은 Layer라면 같은 성격을 가진 코드들이 모여있어야 한다.
UI와 Data Layer 간의 상호작용을 간소화하고 재사용하기 위한 Domain Layer를 추가할 수 있다.
UI Layer의 역할은 화면에 애플리케이션 데이터를 표시하는 것이다. 사용자 상호작용(Ex. 버튼 누르기) 또는 외부 입력(Ex. 네트워크 응답)으로 인해 데이터가 변할 때마다 변경사항을 반영하도록 UI가 업데이트 되어야 한다.
UI는 데이터 레이어에서 가져온 애플리케이션 상태를 시각적으로 나타낸다.
하지만 일반적으로 데이터 레이어에서 가져오는 어플리케이션 데이터는 표시해야 하는 정보와 다른 형식이다. 예를 들어 UI용으로 데이터의 일부만 필요하거나 사용자에게 관련성 있는 정보를 표시하기 위해 서로 다른 두 데이터 소스를 병합해야 할 수도 있다. 적용하는 로직과는 관계없이 완전히 렌더링하는 데 필요한 모든 정보를 UI에 전달해야한다.
UI 레이어는 애플리케이션 데이터 변경사항을 UI가 표시할 수 있는 형식으로 변환한 후에 표시하는 파이프라인이다.
UI 레이어는 다음 두 가지로 구성된다.
UI라는 용어하는 사용하는 API(view 또는 Jetpack Compose)와 관계없이 Activity 및 Fragment와 같은 UI 요소를 가리킨다.
데이터 레이어의 역할은 앱 데이터를 보유하고 관리하며 앱 데이터에 액세스할 권한을 제공하는 것이므로 UI 레이어에서 다음 단계를 실행해야 한다.
data class NewsUiState(
val isSignedIn: Boolean = false,
val isPremium: Boolean = false,
val newsItems: List<NewsItemUiState> = listOf(),
val userMessages: List<Message> = listOf()
)
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)
위 예에서 UI 상태 정의는 변경할 수 없다. 불변성의 주요 이점은 변경 불가능한 객체가 순간의 애플리케이션 상태를 보장한다는 점이다.
UI 상태를 생성하는 역할을 담당하고 생성 작업에 필요한 로직을 포함하는 클래스를 상태 홀더라고 한다.
전체 화면이나 탐색 대상의 경우 일반적인 구현은 ViewModel의 인스턴스이지만 애플리케이션의 요구사항에 따라 간단한 클래스로도 구현할 수 있다.
UI와 ViewModel 클래스 사이의 상호작용은 대체로 이벤트 입력과 입력의 후속 상태인 출력으로 간주될 수 있으므로 관계는 다음 다이어그램과 같다.
상태가 아래로 향하고 이벤트는 위로 향하는 패턴을 단방향 데이터 흐름(UDF)라고 한다. 이 패턴이 앱 아키텍처에 미치는 영향은 다음과 같다.
UI 상태를 정의하고 이 상태의 생성을 관리할 방법을 결정한 후에는 생성된 상태를 UI에 표시하는 단계를 진행한다. UDF를 사용하여 상태 생성을 관리하므로 생성된 상태를 스트림으로 간주할 수 있다.
LiveData 또는 StateFlow를 통해 상태를 노출한다.
UI에서 UiState(data class) 객체의 스트림을 사용하려면 사용 중인 observable 데이터 유형에 터미널 연산자를 사용한다. 예를 들어 LiveData의 경우 observe() 메서드를 사용하고 Kotlin Flow의 경우 collect() 메서드나 이 메서드의 변형을 사용한다.
UI에서 observable 데이터 홀더를 사용할 때는 UI의 수명 주기를 고려해야 한다. 수명 주기를 고려해야 하는 이유는 사용자에게 view가 표시되지 않을 때 UI가 UI상태를 관찰해서는 안 되기 때문이다.
LiveData를 사용하면 LifecycleOwner가 수명 주기 문제를 암시적으로 처리한다.
Flow를 사용할 때는 적절한 코루틴 범위와 repeatOnLifecycle API로 처리하는 것이 좋다.
데이터 영역에는 애플리케이션 데이터 및 비즈니스 로직이 포함된다.
비즈니스 로직은 앱에 가치를 부여하는 요소로, 애플리케이션의 데이터 생성, 저장, 변경 방식을 결정하는 실제 비즈니스 규칙으로 구성된다.
데이터 영역은 0개부터 여러 개의 데이터 소스를 각각 포함할 수 있는 저장소로 구성된다. 앱에서 처리하는 다양한 유형의 데이터 별로 저장소 클래스를 만들어야한다. 예를 들어 영화 관련 데이터에는 MoviesRepository
클래스를 만들거나 결게 관련 데이터에는 PaymentRepository
클래스를 만들 수 있다.
Repository 클래스에서 담당하는 작업은 다음과 같다.
각 데이터 소스 클래스는 파일, 네트워크 소스, 로컬 데이터베이스와 같은 하나의 데이터 소스만 사용해야 한다. 데이터 소스 클래스는 데이터 작업을 위해 애플리케이션과 시스템 간의 가교 역할을한다.
계층 구조의 다른 레이어는 데이터 소스에 직접 액세스해서는 안된다. 데이터 영역의 진입점은 항상 저장소 클래스여야 한다.