Preview_심폐소생술 - boostcampwm-2024/and04-Nature-Album GitHub Wiki

✅ 진행한 기능

  • Preview 안 보이는 버그 수정
    • Home ✅
    • Album ✅
    • AlbumFolder ✅
    • LabelSearch ✅
    • MyPage ✅
    • PhotoInfo ✅
    • SavePhoto ✅

💡더미 데이터를 받아 사용할 수 있는 하위 컴포저블 만들기!

우선 ViewModel 사용으로 인해 Preview가 보이지 않는 문제를 해결하고자 했다.

Github Compose-Samples | Jetsnack | Cart.kt

compose-samples에서 사용한 방식을 분석하였고, 여기서 정말 많은 아이디어를 얻었다.

사용한 방법을 간단하게 설명하면 다음과 같다.

  1. viewmodel 인스턴스를 생성한 뷰모델 컴포저블을 생성한다.
  2. 해당 뷰모델에서 필요한 상태와 값을 전달받는 하위 컴포저블을 생성한다.
  3. Preview에서는 2번 과정에서 생성한 컴포저블을 연결한다.

아래는 프로젝트 진행 중 수정한 과정이다.

현재 Preview가 작동하지 않는 SavePhotoScreen 을 해결하기 위하여 똑같은 이름을 가진 SavePhotoScreen 컴포저블을 만들었고 이 컴포저블에 필요한 값들을 전달하는 방식이다.

@Composable
fun SavePhotoScreen(
    location: Location?,
    model: Uri,
    onBack: () -> Unit,
    onSave: () -> Unit,
    onLabelSelect: () -> Unit,
    description: String = "",
    label: Label? = null,
    onNavigateToMyPage: () -> Unit,
    viewModel: SavePhotoViewModel = hiltViewModel(),
) {

    SavePhotoScreen( // 이 부분의 컴포저블을 Preview로 사용
        model = model,
        label = label,
        location = location,
        photoSaveState = photoSaveState,
        rememberDescription = rememberDescription,
        onDescriptionChange = { newDescription -> rememberDescription.value = newDescription },
        isRepresented = isRepresented,
        onRepresentedChange = { isRepresented.value = !isRepresented.value },
        onNavigateToMyPage = onNavigateToMyPage,
        onLabelSelect = onLabelSelect,
        onBack = onBack,
        savePhoto = viewModel::savePhoto
    )
}

즉, 똑같은 이름의 컴포저블을 2개 만들었고 viewmodel와 상태들을 넘겨받는 하위 SavePhotoScreen을 구현해 이 컴포저블을 Preview로 연결하여 이 문제를 해결했다.

더 간단한 코드의 예시이다.

@Composable
fun A(viewModel: MainViewModel: viewModel()){
    val value by myPageViewModel.value.collectAsStateWithLifecycle()

    B(value, viewModel::postValue)
}

@Composable
fun B(value: String, postValue: () -> Unit){
    Button(onClick = postValue){
        Text(text = value)
    }
}

@Preview
fun Preview(){
    MaterialTheme{
        B("dummy Data", { })
    }
}

최상의 컴포저블인 A에서 뷰모델 인스턴스를 생성한다. 이 A 컴포저블에서는 UI를 그리지 않고 상태와 필요한 값들을 그대로 B 컴포저블에 전달한다.

B 컴포저블은 A 컴포저블에게 받은 상태와 값을 통해 UI를 그린다.

그렇게되면 B 컴포저블 파라미터에 더미데이터를 넣어준다면 Preview가 정상적으로 작동하기 시작한다.

❓왜 뷰모델의 상태와 함수를 파라미터로 전달해야 할까?

Android Developers Compose 및 기타 라이브러리

우선 우리 팀에서 작성한 코드 중 하위 컴포저블에서 viewmodel 인스턴스를 사용하는 부분이 정말 많았다.

이렇게 viewmodel 인스턴스를 마구자비로 여러 곳에서 생성하면 안된다는 것을 공식문서를 통해 찾을 수 있었다.

이 가이드라인은 ViewModel 인스턴스를 여러 컴포저블에서 생성하는 것을 피하라는 의미이다.

즉, MyScreen과 MyScreen2에서 각각 viewModel()을 호출하여 새로운 ViewModel 인스턴스를 생성하는 것이 아니라, 같은 ViewModel을 공유하는 것이 중요하다는 뜻이다.

우리가 구현한 컴포저블 중에 상위 컴포저블과 하위 컴포저블 둘 다 뷰모델을 생성하는 코드가 존재해 전부 제거하고, 상위 컴포저블의 상태나 함수를 전달하는 식으로 전면 수정했다.

ViewModel은 특정 화면(액티비티나 내비게이션 그래프의 스코프) 내에서만 하나의 인스턴스가 생성되어야 한다.

이를 통해 ViewModel이 해당 화면의 수명 주기와 맞춰 관리되고, 여러 컴포저블 간에 상태가 공유될 수 있다.

따라서, MyScreen과 MyScreen2가 같은 ViewModel을 공유해야 한다면, 두 컴포저블 모두에서 viewModel()을 호출하는 대신, 상위 컴포저블에서 ViewModel을 한 번만 호출하고, 필요한 데이터나 함수만 하위 컴포저블로 전달하는 것이 좋다.

이렇게 하면 코드의 재사용성이 높아지고, 테스트도 쉬워지며, 예기치 않은 ViewModel 인스턴스의 중복 생성도 방지할 수 있다.

💡 즉, viewmodel 인스턴스는 상위 컴포저블에서 생성하고 필요한 값들은 파라미터로 넘겨주는 방식으로 구현해야하는 것이다.

넘어와서 viewmodel과 여러 상태 관련해서는 상위 컴포저블에 위치시키고 UI와 관련된 부분만 따로 뺀 컴포저블을 만들어 Preview로 사용한다는 것이 아이디어이다.

이런식으로 구현한다면 Preview에선 더미데이터만 넣어준다면 다시 정상적으로 동작하는 것을 확인할 수 있다!

또 Compose-samples를 참고하면서 아이디어를 하나 더 얻었다.

기존에 상태 관리를 할 때, = remember 을 사용해 하위 컴포저블에게 State<타입> 을 넘겨주고 .value 를 통해 실제 값을 사용했었다.

하지만 by 키워드를 사용하면 상태 값을 직접 접근할 수 있게 된다. 즉 .value 를 사용하지 않고 실제 값을 바로 사용할 수 있게 되는 것이다.

또한 by 키워드를 사용하고, State<T> 를 파라미터 타입으로 넘겨주는 것보다 실제 값 자체를 넘겨주는 것 자체가 Preview에서의 사용이 간편하다는 것을 알게되었다.

실제로 = 키워드를 사용하여 State<T> 를 파라미터로 넘겨주게 된다면 State 는 인터페이스라 초기화 할 수 없다는 에러메세지를 띄운다.

image

그렇다면 다른 방식으로mutableStateOf()를 통해 더미 데이터를 넣어주려고 하니 이번에는 remember 키워드랑 같이 써야한다는 에러메세지를 만날 수 있었다.

image

즉, State<T> 타입을 더미데이터로 만들기 위해서는 Preview에서도 아래와 같은 코드를 작성하여 직접 상태를 만들고 더미데이터로 넣어줘야하는 번거로움이 있었다.

val uiState = rememberSaveable { mutableStateOf(UiState.Success) }

실제 Compose-samples에서는 상위 컴포저블에서 상태 관리는 by 키워드를 사용하여 위임하고 있었다.

그렇게 되면 하위 컴포저블로 넘겨주는 파라미터로 State<T> 가 아닌, 실제 값을 넣어주고 있기에 Preview에서도 간편하게 더미데이터를 생성하여 사용하고 있었다.

= 키워드와 by 키워드의 선언은 동일한 것이라는 것을 공식 문서에서 직접 확인했기에 이 정도면 충분한 근거라고 생각하여 Compose-samples의 아이디어를 바탕으로 우리의 Preview 문제를 해결했다.

실제로 팀원분께서 이런 부분에 대해 PR에 질문을 주셨고 이 근거를 바탕으로 답변을 남겼다.

image