Android 아키텍처 권장사항

이 페이지에서는 여러 아키텍처 권장사항을 설명합니다. 앱의 품질과 견고성, 확장성을 개선하는 데 참고하세요. 이를 통해 앱을 더 쉽게 유지관리하고 테스트할 수도 있습니다.

아래 권장사항은 주제별로 그룹화되어 있습니다. 팀에서 권장하는 강도를 반영하여 각각 우선순위가 지정되어 있습니다. 우선순위 목록은 다음과 같습니다.

  • 적극 권장됨: 자체 접근 방식과 기본적으로 충돌하지 않는 한 권장사항을 구현해야 합니다.
  • 권장됨: 이 권장사항으로 앱이 개선될 가능성이 높습니다.
  • 선택사항: 특정 상황에서 이 권장사항으로 앱이 개선될 수 있습니다.

계층화된 아키텍처

권장되는 계층화된 아키텍처는 관심사 분리를 선호합니다. 데이터 모델에서 UI를 구동하고 단일 정보 소스 원칙을 준수하며 단방향 데이터 흐름 원칙을 따릅니다. 다음은 계층화된 아키텍처에 관한 권장사항입니다.

권장사항 설명
명확하게 정의된 데이터 레이어를 사용합니다.
적극 권장됨
데이터 레이어는 앱의 나머지 부분에 애플리케이션 데이터를 노출하고 앱의 대다수 비즈니스 로직을 포함합니다.
  • 단일 데이터 소스만 포함하더라도 저장소를 만들어야 합니다.
  • 작은 앱에서는 data 패키지 또는 모듈에 데이터 레이어 유형을 배치할 수 있습니다.
명확하게 정의된 UI 레이어를 사용합니다.
적극 권장됨
UI 레이어는 화면에 애플리케이션 데이터를 표시하고 사용자 상호작용의 기본 지점으로 기능합니다.
  • 작은 앱에서는 ui 패키지 또는 모듈에 데이터 레이어 유형을 배치할 수 있습니다.
자세한 UI 레이어 권장사항 확인하기
데이터 레이어는 저장소를 사용하여 애플리케이션 데이터를 노출해야 합니다.
적극 권장됨

컴포저블, 활동, ViewModel과 같은 UI 레이어의 구성요소는 데이터 소스와 직접 상호작용해서는 안 됩니다. 데이터 소스의 예는 다음과 같습니다.

  • 데이터베이스, DataStore, SharedPreferences, Firebase API
  • GPS 위치 정보 제공자
  • 블루투스 데이터 제공자
  • 네트워크 연결 상태 제공자
코루틴 및 흐름을 사용합니다.
적극 권장됨
코루틴 및 흐름을 사용하여 레이어 간에 통신합니다.

자세한 코루틴 권장사항 확인하기

도메인 레이어를 사용합니다.
큰 앱에 권장됨
여러 ViewModel에서 데이터 레이어와 상호작용하는 비즈니스 로직을 재사용해야 하는 경우 또는 특정 ViewModel의 비즈니스 로직 복잡성을 단순화하려는 경우 도메인 레이어 사용 사례를 활용합니다.

UI 레이어

UI 레이어의 역할은 화면에 애플리케이션 데이터를 표시하고 사용자 상호작용의 기본 지점으로 기능하는 것입니다. 다음은 UI 레이어에 관한 권장사항입니다.

권장사항 설명
단방향 데이터 흐름(UDF)을 따릅니다.
적극 권장됨
단방향 데이터 흐름(UDF) 원칙을 따릅니다. 여기서 ViewModel은 관찰자 패턴을 사용하여 UI 상태를 노출하고 메서드 호출을 통해 UI에서 작업을 수신합니다.
앱에 이점이 적용되는 경우 AAC ViewModel을 사용합니다.
적극 권장됨
AAC ViewModel을 사용하여 비즈니스 로직을 처리하고, 애플리케이션 데이터를 가져와 UI 상태를 UI(Compose 또는 Android 뷰)에 노출합니다.

자세한 ViewModel 권장사항 확인하기

ViewModel의 이점 확인하기

수명 주기 인식 UI 상태 컬렉션을 사용합니다.
적극 권장됨
적절한 수명 주기 인식 코루틴 빌더(뷰 시스템의 repeatOnLifecycle, Jetpack Compose의 collectAsStateWithLifecycle)를 사용하여 UI에서 UI 상태를 수집합니다.

repeatOnLifecycle에 관해 자세히 알아보세요.

collectAsStateWithLifecycle에 관해 자세히 알아보세요.

ViewModel에서 UI로 이벤트를 전송하면 안 됩니다.
적극 권장됨
ViewModel에서 즉시 이벤트를 처리하고 이벤트 처리 결과로 상태 업데이트를 발생시킵니다. UI 이벤트에 관해 자세히 알아보세요.
단일 활동 애플리케이션을 사용합니다.
권장됨
앱에 화면이 두 개 이상 있으면 Navigation Fragments 또는 Navigation Compose를 사용하여 화면 간에 이동하고 딥 링크로 앱에 연결합니다.
Jetpack Compose를 사용합니다.
권장됨
Jetpack Compose를 사용하여 스마트폰, 태블릿, 폴더블, Wear OS용 새 앱을 빌드합니다.

다음 스니펫은 수명 주기 인식 방식으로 UI 상태를 수집하는 방법을 설명합니다.

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

Compose

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

ViewModel

ViewModel은 UI 상태와 데이터 레이어 액세스 권한을 제공합니다. 다음은 ViewModel 권장사항입니다.

권장사항 설명
ViewModel은 Android 수명 주기에 구애받지 않아야 합니다.
적극 권장됨
ViewModel은 Lifecycle 관련 유형에 대한 참조를 보유해서는 안 됩니다. Activity, Fragment, Context 또는 Resources를 종속 항목으로 전달하면 안 됩니다. ViewModel에 Context가 필요한 경우 올바른 레이어에 있는지 적극적으로 평가해야 합니다.
코루틴 및 흐름을 사용합니다.
적극 권장됨

ViewModel은 다음을 사용하여 데이터 또는 도메인 레이어와 상호작용합니다.

  • 애플리케이션 데이터 수신을 위한 Kotlin 흐름
  • viewModelScope를 사용하여 작업을 실행하는 suspend 함수
화면 수준에서 ViewModel을 사용합니다.
적극 권장됨

UI의 재사용 가능한 부분에서 ViewModel을 사용하면 안 됩니다. 다음에서 ViewModel을 사용해야 합니다.

  • 화면 수준 컴포저블
  • 뷰의 활동/프래그먼트
  • Jetpack Navigation을 사용하는 경우 대상 또는 그래프
재사용 가능한 UI 구성요소에서 일반 상태 홀더 클래스를 사용합니다.
적극 권장됨
일반 상태 홀더 클래스를 사용하여 재사용 가능한 UI 구성요소의 복잡성을 처리합니다. 이렇게 하면 상태를 외부에서 끌어올려 제어할 수 있습니다.
AndroidViewModel을 사용하면 안 됩니다.
권장됨
AndroidViewModel이 아닌 ViewModel 클래스를 사용합니다. Application 클래스는 ViewModel에서 사용하면 안 됩니다. 대신 종속 항목을 UI 또는 데이터 레이어로 이동합니다.
UI 상태를 노출합니다.
권장됨
ViewModel은 단일 속성 uiState를 통해 UI에 데이터를 노출해야 합니다. UI에 관련 없는 여러 데이터가 표시되면 VM은 여러 UI 상태 속성을 노출할 수 있습니다.
  • uiStateStateFlow로 만들어야 합니다.
  • 계층 구조의 다른 레이어에서 데이터 스트림으로 데이터가 제공되는 경우 WhileSubscribed(5000) 정책(예)과 함께 stateIn 연산자를 사용하여 uiState를 만들어야 합니다.
  • 데이터 레이어에서 발생하는 데이터 스트림이 없는 더 간단한 사례의 경우 변경 불가능한 StateFlow(예)로 노출된 MutableStateFlow를 사용해도 됩니다.
  • 데이터, 오류, 로드 신호가 포함될 수 있는 데이터 클래스로 ${Screen}UiState를 보유할 수 있습니다. 이 클래스는 다른 상태가 배타적일 경우 봉인 클래스일 수도 있습니다.

다음 스니펫은 ViewModel에서 UI 상태를 노출하는 방법을 설명합니다.

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

수명 주기

다음은 Android 수명 주기를 사용하기 위한 권장사항입니다.

권장사항 설명
활동 또는 프래그먼트에서 수명 주기 메서드를 재정의하면 안 됩니다.
적극 권장됨
활동이나 프래그먼트에서 onResume과 같은 수명 주기 메서드를 재정의하면 안 됩니다. 대신 LifecycleObserver를 사용하세요. 수명 주기가 특정 Lifecycle.State에 도달할 때 앱에서 작업을 실행해야 하는 경우 repeatOnLifecycle API를 사용합니다.

다음 스니펫은 특정 수명 주기 상태에서 작업을 실행하는 방법을 설명합니다.

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

Compose

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

종속 항목 처리

구성요소 간 종속 항목을 관리할 때는 다음과 같은 권장사항을 따라야 합니다.

권장사항 설명
종속 항목 삽입을 사용합니다.
적극 권장됨
종속 항목 삽입 권장사항, 가능하면 주로 생성자 삽입을 사용합니다.
필요한 경우 구성요소로 범위를 지정합니다.
적극 권장됨
공유해야 하는 변경 가능한 데이터가 유형에 포함되어 있거나, 유형이 초기화 비용이 많이 들고 앱에서 널리 사용되는 경우 종속 항목 컨테이너로 범위를 지정합니다.
Hilt를 사용합니다.
권장됨
간단한 앱에서는 Hilt 또는 수동 종속 항목 삽입을 사용합니다. 프로젝트가 충분히 복잡하다면 Hilt를 사용합니다. 다음과 같은 경우를 예로 들 수 있습니다.
  • ViewModel이 있는 여러 화면: 통합
  • WorkManager 사용: 통합
  • 탐색 그래프로 범위가 지정된 ViewModel과 같은 고급 탐색 사용: 통합

테스트

다음은 테스트 권장사항입니다.

권장사항 설명
테스트 항목을 파악합니다.
적극 권장됨

프로젝트가 Hello World 앱만큼이나 간단하지 않은 이상 최소한 다음을 사용하여 테스트해야 합니다.

  • Flow를 포함한 ViewModel 단위 테스트
  • 데이터 레이어 항목 단위 테스트. 즉, 저장소와 데이터 소스입니다.
  • CI에서 회귀 테스트로 유용한 UI 탐색 테스트
모의 테스트보다 가짜 테스트를 선호합니다.
적극 권장됨
자세한 내용은 Android에서 테스트 더블 사용 문서를 참고하세요.
StateFlow를 테스트합니다.
적극 권장됨
StateFlow 테스트 시:

자세한 내용은 Android DAC에서 테스트할 항목 가이드를 참고하세요.

모델

앱에서 모델을 개발할 때 다음 권장사항을 준수해야 합니다.

권장사항 설명
복잡한 앱에서는 레이어별 모델을 만듭니다.
권장됨

복잡한 앱에서는 적절하다면 다양한 레이어나 구성요소에서 새 모델을 만듭니다. 다음 예를 고려하세요.

  • 원격 데이터 소스는 네트워크를 통해 수신하는 모델을 앱에 필요한 데이터만으로 더 간단한 클래스에 매핑할 수 있습니다.
  • 저장소는 UI 레이어에 필요한 정보만 사용하여 DAO 모델을 더 간단한 데이터 클래스에 매핑할 수 있습니다.
  • ViewModel은 UiState 클래스에 데이터 레이어 모델을 포함할 수 있습니다.

이름 지정 규칙

코드베이스의 이름을 지정할 때는 다음 권장사항에 유의하세요.

권장사항 설명
메서드의 이름을 지정합니다.
선택사항
메서드는 동사구여야 합니다. 예를 들어 makePayment()가 이에 해당합니다.
속성 이름을 지정합니다.
선택사항
속성은 명사구여야 합니다. 예를 들어 inProgressTopicSelection이 이에 해당합니다.
데이터 스트림 이름을 지정합니다.
선택사항
클래스가 Flow 스트림, LiveData 또는 기타 스트림을 노출하는 경우 이름 지정 규칙은 get{model}Stream()입니다. getAuthorStream(): Flow<Author>를 예로 들 수 있습니다. 이 함수가 모델 목록을 반환하면 모델 이름은 다음과 같이 복수형이어야 합니다. getAuthorsStream(): Flow<List<Author>>
인터페이스 구현 이름을 지정합니다.
선택사항
인터페이스 구현의 이름은 의미가 있어야 합니다. 더 나은 이름을 찾을 수 없는 경우 접두사로 Default를 사용합니다. 예를 들어 NewsRepository 인터페이스의 경우 OfflineFirstNewsRepository 또는 InMemoryNewsRepository가 있을 수 있습니다. 적절한 이름을 찾을 수 없는 경우 DefaultNewsRepository를 사용합니다. 가짜 구현은 FakeAuthorsRepository와 같이 Fake 접두사를 붙여야 합니다.