구글 안드로이드이에서는 이상적인 아키텍쳐와 앱 제작 가이드를 위해 몇 개의 샘플 프로젝트를 공개하고 있습니다.
그 중에 하나가 바로
Now in Android
입니다.
https://github.com/android/nowinandroid
GitHub - android/nowinandroid: A fully functional Android app built entirely with Kotlin and Jetpack Compose
A fully functional Android app built entirely with Kotlin and Jetpack Compose - android/nowinandroid
github.com
현재는 구글 스토어에도 릴리즈되어있으며
https://play.google.com/store/apps/details?id=com.google.samples.apps.nowinandroid
Now in Android - Google Play 앱
이제 Android에서 - 새로운 오픈 소스 실제 샘플 앱
play.google.com
주요 기능은 구글에서 제공하는 안드로이드 및 코틀린 관련 뉴스를 제공하는 앱입니다.
Now in Android 아키텍쳐 살펴보기
Now in Android 프로젝트에서는 아키텍처를 어떻게 설계했는지에 대한 설명도 나와있습니다.
이번 포스팅에서는 해당 아키텍처가 어떤식으로 이루어져 있는지 확인해보겠습니다.
https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md
nowinandroid/docs/ArchitectureLearningJourney.md at main · android/nowinandroid
A fully functional Android app built entirely with Kotlin and Jetpack Compose - android/nowinandroid
github.com
구글 공식 안드로이드 앱 샘플 답게 기본적으로 공식 문서에 있는 가이드 라인을 따라서 만들었다고 합니다.
https://developer.android.com/topic/architecture?hl=ko
앱 아키텍처 가이드 | App architecture | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. 앱 아키텍처 가이드 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 가이드에는 고품질의 강력한
developer.android.com
즉, 다음과 같은 아키텍처를 따라서 Now in Android 앱도 만들었다고 볼 수 있습니다.

참고로 Now in Android 앱은 흔히 앱 제작에서 이야기 하는 Clean Architecture를 따라서 만든 앱은 아니라고 합니다.

원래 클린 아키텍쳐는 도메인에 순수하게 비지니스 로직만 정의되어있어야하지만
Now in Android 프로젝트의 도메인 레이어는 데이터 레이어에 의존하고 있기 때문이라고 합니다.
실제로 그런지는 나중에 살펴보겠습니다.
단방향 데이터 흐름으로 상태 관리()
Now in Android 앱은 단방향으로 데이터 흐름을 관리한다고 되어있습니다.
예를 들면 다음과 같이 UI를 표시하기 위한 UiState를 갖고 있으며
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,
...
)
이 UiState는 불변성을 유지하면서 단방향으로 처리되어야한다는 의미입니다.
여기서 단방향이란 Data Layer → UI Layer → Data Layer → UI Layer ... (반복) 흐름으로만 데이터가 전달되어야하며
이 과정에서 오류가 발생하지 않는 한 처리가 되돌아가서는 안됩니다.
UI Layer는 다음과 같이 두 가지로 이루어지며
- UI Elements
- UI를 그리는 부분
- 사용자와 UI 상호작용
- ViewModel
- UI에 넣을 데이터를 수신하여 전달하는 부분
- UI의 반응에 의해 데이터를 다시 Data Layer에 전달
위 내용을 그림으로 표시하면 다음과 같이 나타낼 수 있습니다.

Now in Android의 설명에서는 News 표시 부분을 예시로 들고 있습니다.
Now in Android에서 UDF사용 예시 - News Screen
Now in Android 샘플에서는 다음과 같은 방식으로 UDF를 구현하고 있습니다.

위 과정을 하나씩 따라가보겠습니다.
(그림 안에 번호가 있는데 그 순서대로 진행됩니다.)
1. WorkManaer로 모든 Repository를 동작 시키기
앱이 시작되면 Sync.initialize 함수를 사용해서
모든 Repository의 동작을 WorkManager에 enque하여 순서대로 실시하게 합니다.
Sync.initialize 함수는 sync라는 모듈 안에 있는 이를 통해
sync 모듈은 WorkManager를 사용하여 비동기 처리를 하고 있는 역할을 한다고 예측할 수 있습니다.
2. NewsFeed에서 사용할 데이터 로딩 = NewsFeedUiState.Loading
이후 ForYouScreen이라는 UI를 열게 되는데
여기서 사용할 데이터를 ForYouViewModel에 있는
userNewsResourceRepository.observeAllForFollowedTopics를 사용해서
UI에서 사용할 데이터를 가져와서 feedState에 세팅합니다.
참고로 ForYouScreen과 ForYouViewModel은 feature라는 모듈 안에 있는데
이를 통해 feature 모듈은 Now in Android의 UI Layer 부분이라고 예상할 수 있습니다.
3. UserData 가져오기
userNewsResourceRepository.observeAllForFollowedTopics은 userData를 필요로 하기 때문에
UserDataRepository에 있는 userData를 가져와야합니다.
이 때 OfflineFirstUserDataRepository에서 NiaPreferencesDataSource를 통해 데이터를 가져오게 됩니다.
NiaPreferencesDataSource 안을 살펴보면 userData는 preference로 데이터를 저장하며
해당 데이터를 가져오는 역할을 합니다.
지금 단계에서는 userData의 stream만 수신하고 있는 상태이며 아직까지는 데이터가 들어있지 않은 상태입니다.
Repository와 DataSoruce는 core 모듈 안에 있기 때문에
core 모듈은 직접적으로 데이터를 송수신하는 역할을 한다고 볼 수 있습니다.
4. 각 종 데이터 Sync 하기
SyncWorker.doWork() 함수를 통해 각 종 데이터를 원격 환경에서 가져오는 작업을 실시합니다.
이 작업은 WorkManager로 비동기로 실시됩니다
5. OfflineFirstNewsRepository 데이터 셋업하기
doWork() 함수로 인해 OfflineFirstNewsRepository.syncWith 함수를 동작한다는 것을 알 수 있습니다.
syncWith 함수는 Synchronizer.changeListSync 함수만 동작 시키는데
Synchronizer.changeListSync를 자세히 보면 특정 모델을 업데이트 하는 역할을 한다는 것을 알 수 있습니다.
modelUpdater를 보면 어떤 모델을 어떻게 업데이트 하는지 람다 함수로 정의되있다는 것도 확인할 수 있습니다.
suspend fun Synchronizer.changeListSync(
versionReader: (ChangeListVersions) -> Int,
changeListFetcher: suspend (Int) -> List<NetworkChangeList>,
versionUpdater: ChangeListVersions.(Int) -> ChangeListVersions,
modelDeleter: suspend (List<String>) -> Unit,
modelUpdater: suspend (List<String>) -> Unit,
)
6 & 7. RetrofitNiaNetwork.getNewsResources 함수를 사용해서 뉴스 데이터 가져오기
REST API 통신을 통해 뉴스 데이터를 가져옵니다.
val networkNewsResources = network.getNewsResources(ids = chunkedIds)
8 & 9. Dao 객체를 사용해서 가져온 데이터를 Local에 저장하기
topicDao, newResourceDao 객체를 사용해서 각각의 Room DB에 가져온 데이터를 Local 환경에 저장합니다.
topicDao.insertOrIgnoreTopics(
topicEntities = networkNewsResources
.map(NetworkNewsResource::topicEntityShells)
.flatten()
.distinctBy(TopicEntity::id),
)
newsResourceDao.upsertNewsResources(
newsResourceEntities = networkNewsResources.map(
NetworkNewsResource::asEntity,
),
)
newsResourceDao.insertOrIgnoreTopicCrossRefEntities(
newsResourceTopicCrossReferences = networkNewsResources
.map(NetworkNewsResource::topicCrossReferences)
.distinct()
.flatten(),
)
10. 저장된 데이터를 불러와서 새롭게 셋팅한다.
UDF 실현을 위해 기존에 갖고 있던 데이터가 아니라 Dao에 저장해둔 값을 다시 불러와서 데이터를 세팅합니다.
데이터가 로컬에 완벽히 저장되었기 때문에 OfflineFirstNewsRepository.getNewsResources를 호출한다면
갱신된 데이터를 획득할 수 있습니다.
11. 유저 데이터에 알맞은 뉴스 세팅하기
유저마다 관심사 설정이 다르기 때문에 설정에 맞는 뉴스를 보여줘야합니다.
CompositeUserNewsResourceRepository.observeAll 함수를 통해 유저 정보와 뉴스를 통합한 정보를 반환하게 한다.
override fun observeAll(
query: NewsResourceQuery,
): Flow<List<UserNewsResource>> =
newsRepository.getNewsResources(query)
.combine(userDataRepository.userData) { newsResources, userData ->
newsResources.mapToUserNewsResources(userData)
}
12. 유저에게 맞는 데이터만 골라서 UI에 세팅하기
ForYouViewModel.feedState에서 유저별로 맞는 뉴스를 골라낸 값을 셋팅한다.
ForYouViewModel.feedState는 flow 객체이기 때문에 값이 바뀐다면 UI 쪽에서 감지하고 데이터를 셋팅한다.
val feedState: StateFlow<NewsFeedUiState> =
userNewsResourceRepository.observeAllForFollowedTopics()
.map(NewsFeedUiState::Success)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = NewsFeedUiState.Loading,
)
Now in Android 아키텍쳐 살펴보기 - 정리
여기까지 해서 Now in Android의 아키텍쳐를 살펴봤습니다.
Now in Android의 아키텍처를 한 마디로 정의 하자면 다음과 같이 정의할 수 있겠네요!!
UI Layer와 데이터 Layer의 흐름은 단방향으로만 이루어진다.
- 예를 들어 UI Layer → Data Layer 또는 Data Layer → UI Layer
다음은 DataLayoer에 대한 부분을 살펴보겠습니다.
https://android-developer.tistory.com/116
Now in Android 샘플 분석하기 -2
앞에 포스팅에서는 Now in Android의 아키텍처를 분석했습니다. https://android-developer.tistory.com/113 Now in Android 샘플 분석하기 -1구글 안드로이드이에서는 이상적인 아키텍쳐와 앱 제작 가이드를 위해
android-developer.tistory.com
'안드로이드(kotlin)' 카테고리의 다른 글
mockK에서 mockStatic과 mockObject 차이 (0) | 2025.03.09 |
---|---|
안드로이드에서 API 데이터를 mock 데이터로 받기 - node.js (0) | 2025.03.04 |
안드로이드 XML로 된 프로젝트를 Compose로 변환하기 (6) | 2024.10.21 |
안드로이드 특정 Cookie 값을 얻고 setCookie로 삭제하는 방법 (0) | 2024.10.10 |
안드로이드 현재 액티비티 Stack 확인하기 (0) | 2023.12.27 |
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
댓글