Apple is Apple
Published 2024. 1. 13. 16:01
2024-01-12 TIL
package com.example.compose.rally

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.compose.rally.ui.components.RallyTabRow
import com.example.compose.rally.ui.theme.RallyTheme

/**
 * This Activity recreates part of the Rally Material Study from
 * https://material.io/design/material-studies/rally.html
 */
class RallyActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RallyApp()
        }
    }
}

// 앱의 최상단 컴포저블에 내비게이션 컨트롤러 설정
// NavController
// compose에서 탐색을 사용할 때 중심이 되는 구성요소
// 백 스택 컴포저블 항목을 추적하고, 스택을 앞으로 이동하고, 백 스택 조작을 사용 설정하고, 대상 상태 간에 이동

@Composable
fun RallyApp() {
    RallyTheme {
        // 내비게이션 컨트롤러 설정
        // NavController는 항상 컴포저블 계층 최상위 수준에서 만들고 배치해야함
        // 그래야 NavController를 참조해야하는 컴포저블이 접근할 수 있기 때문에
        // -> 이는 상태 호이스팅 원칙을 준수하고 컨트롤러가 컴포저블 이동간 백스택을 유지하기 위한 기본 정보가 되도록 함

        // compose에서 navigation을 사용할 때는 탐색 그래프의 각 컴포저블 대상에 경로가 연결된다.
        // 경로는 컴포저블의 경로를 정의하는 문자열로 표현되고 올바른 위치로 이동할 수 있도록 navController가 안내한다.
        // 이를 암시적 딥링크라고 한다. (각 대상에는 고유 경로가 있어야한다.)
        val navController = rememberNavController()

        // 81번 라인의 2번 문제
        // 기존에는 currentScreen 이라는 변수로 탭 UI로 조정했지만
        // navigation으로 바꾸면서 사용하지 않게 되어, 탭 Ui를 조정할 수 없게됨
        // navigation을 통해 찾아야함 그렇기에 각 시점에 현재 표시되 대상이 무엇인지를 알아야함
        // 그 후, 변경 될 떄마다 TabRow를 업데이트 해야함

        // 각 시점에 현재 표시되 대상이 무엇인지를 알기 위해 State 형식의 실시간 상태를 알아야함
        val currentBackStack by navController.currentBackStackEntryAsState()
        // NavDestination 객체를 반환하는데 여기서 어느 컴포저블이 표시되어 있는 지 확인해야함
        val currentDestination = currentBackStack?.destination
        // 일종의 id 값으로 탭의 id와 비교해서 찾음
        val currentScreen = rallyTabRowScreens.find { it.route == currentDestination?.route } ?: Overview

        Scaffold(
            topBar = {
                RallyTabRow(
                    allScreens = rallyTabRowScreens,
                    // 탭이 선택 될 떄, navigation 탐색 시작
//                    onTabSelected = { screen -> currentScreen = screen },
                    onTabSelected = { newScreen ->
                        // 이동을 하는데, 문제가 있음
                        // 1. 같은 탭을 다시 탭하면 동일한 탭이 다시 실행 됨 -> 여러개의 사본이 만들어짐  해결 - navigateSingTopTo()
                        // 2. 탭의 UI와 화면이 일치하지 않음 -> 탭 펼치기 접기가 제대로 동작하기 않음

                        navController.navigateSingleTopTo(newScreen.route)
                    },
                    currentScreen = currentScreen
                )
            }
        ) { innerPadding ->
            // Navigation의 주요 세가지 NavController, NavGraph, NavHost
            // NavController는 항상 단일 NavHost 컴포저블에 연결됨
            // NavHost는 컨테이너 역할을 하여 그래프의 현재 대상은 표시함. 여러 컴포저블 이동 간 navHost의 콘텐츠가 자동으로 재구성 됨
            // 또, NavController를 이동 가능한 컴포저블 대상을 매핑하는 NavGraph에 연결함
            // 여기서 NavHost는 가져올 수 있는 컴포저블의 모음

            NavHost(
                // NavController를 NavHost에 연결
                navController = navController,
                // navigation의 시작점을 연결
                startDestination = Overview.route,
                modifier = Modifier.padding(innerPadding)
            ) {
                // builder: NavGraphBuilder.() -> Unit - 탐색 그래프를 정의하고 만드는 일을 담당

                // NavGraph에 3개의 컴포저블을 추가
                composable(route = Overview.route) {
                    Overview.screen()
                }
                composable(route = Accounts.route) {
                    Accounts.screen()
                }
                composable(route = Bills.route) {
                    Bills.screen()
                }
            }
            Box(Modifier.padding(innerPadding)) {
                currentScreen.screen()
            }
        }
    }
}

fun NavHostController.navigateSingleTopTo(route: String) =
    this.navigate(route) {
        // 내비게이션의 옵션 중, 백스택에 사본이 최대 1개까지만 유지 될 수 있도록 설정
        launchSingleTop = true

        // 탭을 선택했을 때 백 스택에 대규모 대상 스택이 빌드되지 않도록 그래프의 시작 대상을 팝업으로 만듦
        popUpTo(
            // 그래프의 시작 대상의 id
            this@navigateSingleTopTo.graph.findStartDestination().id
        ) {
            // 상태를 저장하면 뒤로 가기 클릭 시, 백스택 전체가 "Overview"로 팝업됨
            saveState = true
        }

        // PopUpToBuilder.saveState 또는 popUpToSaveState 속성에 의해 저장된 상태를 복원하는지 여부를 결정
        // 이동할 대상 ID를 사용하여 이전에 저장된 상태가 없다면 이 옵션은 효과가 없다.
        restoreState = true
    }

'TIL' 카테고리의 다른 글

2024-01-18  (0) 2024.01.18
2024-01-16  (0) 2024.01.16
2023-12-28  (0) 2023.12.28
2023-12-22  (0) 2023.12.23
2023-12-19  (1) 2023.12.19
profile

Apple is Apple

@mjjjjjj