Android Dev/NowInAndroid / / 2022. 10. 25. 01:24

NowInAndroid(1) - revision at 2022-11-14

 제 마음대로 보고 싶은 부분을 우선순위로 해서 분석하였습니다.

공식 가이드는 아래의 링크를 보고 따라하면되겠습니다.

https://github.com/android/nowinandroid/blob/main/docs/ArchitectureLearningJourney.md

 

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 - GitHub - android/nowinandroid: A fully functional Android app built entirely with Kotlin and Jetpack Compose

github.com

 

 

 

1. 메인 화면

 

For you / Saved / Interests 메뉴는 

NiaNavHost.kt에 NavHost 에서 NavGraphBuilder로 구현되어 있다. 

 

NiaNavHost.kt

@Composable
fun NiaNavHost(
    navController: NavHostController,
    onBackClick: () -> Unit,
    modifier: Modifier = Modifier,
    startDestination: String = forYouNavigationRoute
) {
    NavHost(
        navController = navController,
        startDestination = startDestination,
        modifier = modifier,
    ) {
        forYouScreen()
        bookmarksScreen()
        interestsGraph(
            navigateToTopic = { topicId ->
                navController.navigateToTopic(topicId)
            },
            navigateToAuthor = { authorId ->
                navController.navigateToAuthor(authorId)
            },
            nestedGraphs = {
                topicScreen(onBackClick)
                authorScreen(onBackClick)
            }
        )
    }
}

 

 

2. "For you" navigation graph 분석

 

fun NavGraphBuilder.forYouScreen() {
    composable(route = forYouNavigationRoute) {
        ForYouRoute()
    }
}

 

 ForYouScreen Composable은 아래의 submodule로 구성되어있다.

 

app의 gradle에 해당 project(foryou)가 implementation 되어있으므로 사용가능

 

ForYouScreen.kt 은 아래와 같이 프리뷰를 제공한다

또한 ViewModel UnitTest와 androidTest를 제공하여 이를 보면 좋을 것같다.

 

3. ForYouScreen.kt

@OptIn(ExperimentalLifecycleComposeApi::class)
@Composable
internal fun ForYouRoute(
    modifier: Modifier = Modifier,
    viewModel: ForYouViewModel = hiltViewModel()
) {
    val interestsSelectionState by viewModel.interestsSelectionUiState.collectAsStateWithLifecycle()
    val feedState by viewModel.feedState.collectAsStateWithLifecycle()
    val isSyncing by viewModel.isSyncing.collectAsStateWithLifecycle()

    ForYouScreen(
        isSyncing = isSyncing,
        interestsSelectionState = interestsSelectionState,
        feedState = feedState,
        onTopicCheckedChanged = viewModel::updateTopicSelection,
        onAuthorCheckedChanged = viewModel::updateAuthorSelection,
        saveFollowedTopics = viewModel::saveFollowedInterests,
        onNewsResourcesCheckedChanged = viewModel::updateNewsResourceSaved,
        modifier = modifier
    )
}

viewModel은 collectAsStateWithLifecycle을 통해 state 변화를 ViewModel에서 감지하고 이를 ForYouScreen에 binding 시키는 것으로 보인다. 

 

알아보아야할 것 : collectAsStateWithLifecycle(https://witcheryoon.tistory.com/338)

 

3.1 ViewModel 부분

먼저, 

HiltViewModel을 인스턴스하며, 이는 외부 argument로 넣고 있지않으므로, DI 부분을 확인해야할 것이다.

@OptIn(SavedStateHandleSaveableApi::class)
@HiltViewModel
class ForYouViewModel @Inject constructor(
    syncStatusMonitor: SyncStatusMonitor,
    private val userDataRepository: UserDataRepository,
    private val getSaveableNewsResourcesStream: GetSaveableNewsResourcesStreamUseCase,
    getSortedFollowableAuthorsStream: GetSortedFollowableAuthorsStreamUseCase,
    getFollowableTopicsStream: GetFollowableTopicsStreamUseCase,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

...

}

 

ViewModel의 인자로 UserRepository와 UserCase를 Case별로 나누어 인자로 받아 들이고 있으며, SavedStateHandle로 state View 를 Handle한다 메소드는 아래를 참고하라.

 

알아보아야할 것 : SavedStateHandle(https://witcheryoon.tistory.com/337)

 

Viewmodel은 StateFlow를 리턴하며, 그 중간에 UseCase들이 동작하여 UseCase는 invoke가 구현되어 이것이 각 부분에 필요한 Repository의 정보를 flow로 리터한다.  Usecase는 대부분 예를 들어 아래와 같이 구성되어있다.

 

class GetSortedFollowableAuthorsStreamUseCase @Inject constructor(
    private val authorsRepository: AuthorsRepository
) {
    /**
     * Returns a list of authors with their associated followed state sorted alphabetically by name.
     *
     * @param followedTopicIds - the set of topic ids which are currently being followed.
     */
    operator fun invoke(followedAuthorIds: Set<String>): Flow<List<FollowableAuthor>> {
        return authorsRepository.getAuthorsStream().map { authors ->
            authors
                .map { author ->
                    FollowableAuthor(
                        author = author,
                        isFollowed = author.id in followedAuthorIds
                    )
                }
                .sortedBy { it.author.name }
        }
    }
}

 

 

3.2 ForYouScreen

@Composable
internal fun ForYouScreen(
    isSyncing: Boolean,
    interestsSelectionState: ForYouInterestsSelectionUiState,
    feedState: NewsFeedUiState,
    onTopicCheckedChanged: (String, Boolean) -> Unit,
    onAuthorCheckedChanged: (String, Boolean) -> Unit,
    saveFollowedTopics: () -> Unit,
    onNewsResourcesCheckedChanged: (String, Boolean) -> Unit,
    modifier: Modifier = Modifier,
) {


...

}

 

이 컴포저블은 크게 

LazyVerticalGrid 부분과 애니메이션 효과를 나타내는 AnimatedVisibility로 구성되어 있다

여기서 interestSelction과 newsFeed는 LazyGridScope의 extension function이며,  이둘을 Vertical Grid로 연결한 모습임.

 

 

추가된 부분 : 2022-11-14 ) project 전체 구조 중 재구성해 볼 부분을 아래와 같이 요약하였음

Simplied NiA Structure by overall codes

 

 

참고 :

jankstats:

https://developer.android.com/topic/performance/jankstats

SavedStateHandle :
https://witcheryoon.tistory.com/337
https://pluu.github.io/blog/android/2020/02/20/savedstatehandle/

collectAsStateWithLifecycle :

https://witcheryoon.tistory.com/338

https://hongbeomi.medium.com/jetpack-compose%EC%97%90%EC%84%9C-flow%EB%A5%BC-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-a394a679909b

 

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유