Android XML에서는 오른쪽 Preview에서 내가 만든 UI를 빌드하지 않고 바로 확인하여 개발할 수 있습니다.
JetPack Compose에서는 Preview 컴포저블을 만들어 내가 만든 컴포저블들을 확인하여 개발할 수 있습니다.
Android View Sytsem 에서는 Activity 혹은 Fragment에서 viewmodel의 인스턴스를 생성하지만, JetPack Compose에서는 아래와 같이 컴포저블 파라미터에서 viewModel() 함수를 호출하여 생성합니다.
하지만 여기서 중요한 점은! MyScreen 컴포저블은 뷰모델 인스턴스 생성으로 인해 Preview가 작동하지 않기 시작합니다.
물론 직접 앱을 빌드하여 확인하면 되겠지만,,, UI 하나 수정할 때 마다 빌드해서 확인해야하는 번거로움이 있기에 Preview를 다시 되살리기 위한 방법을 작성하고자 합니다.
💡 더미 데이터를 받아 사용할 수 있는 하위 컴포저블 만들기!
compose-samples/Jetsnack/app/src/main/java/com/example/jetsnack/ui/home/cart/Cart.kt at main · android/compose-samples
Official Jetpack Compose samples. Contribute to android/compose-samples development by creating an account on GitHub.
github.com
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가 정상적으로 작동하기 시작합니다.
❓왜 뷰모델의 상태와 함수를 파라미터로 전달해야 할까요?
Compose 및 기타 라이브러리 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose 및 기타 라이브러리 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Compose에서는 자주 이용하는
developer.android.com
우선 제가 현재 진행 중인 프로젝트 코드에서 하위 컴포저블에서 viewmodel 인스턴스를 사용하는 부분이 정말 많았습니다.
Preview도 작동하지 않았으며, 테스트하기도 정말 어려웠습니다.
하지만 이렇게 viewmodel 인스턴스를 여러 컴포저블에서 생성하면 안된다는 것을 공식문서를 통해 찾을 수 있었습니다.
이 가이드라인은 ViewModel 인스턴스를 여러 컴포저블에서 생성하는 것을 피하라는 의미입니다.
즉, MyScreen과 MyScreen2에서 각각 viewModel()을 호출하여 새로운 ViewModel 인스턴스를 생성하는 것이 아니라, 같은 ViewModel을 공유하는 것이 중요하다는 뜻입니다.
ViewModel은 특정 화면(액티비티나 내비게이션 그래프의 스코프) 내에서만 하나의 인스턴스가 생성되어야 합니다.
이를 통해 ViewModel이 해당 화면의 수명 주기와 맞춰 관리되고, 여러 컴포저블 간에 상태가 공유될 수 있습니다.
따라서, MyScreen과 MyScreen2가 같은 ViewModel을 공유해야 한다면, 두 컴포저블 모두에서 viewModel()을 호출하는 대신, 상위 컴포저블에서 ViewModel을 한 번만 호출하고, 필요한 데이터나 함수만 하위 컴포저블로 전달하는 것이 좋습니다.
이렇게 하면 코드의 재사용성이 높아지고, 테스트도 쉬워지며, 예기치 않은 ViewModel 인스턴스의 중복 생성도 방지할 수 있다.
💡 즉, viewmodel 인스턴스는 상위 컴포저블에서 생성. 필요한 값들은 파라미터로 넘겨주는 방식!
여기서 하나 더 중요한 부분이 있습니다.
저는 현재 = remember 방식이 아닌 by remember 방식을 사용하고 있습니다.
기존에는 = remember 방식을 사용하여 .value 를 통해 실제 값에 접근해 사용해왔습니다.
하지만 Compose-samples를 참고하면서 아이디어를 얻었습니다.
by 키워드를 사용하면 상태 값을 직접 접근할 수 있게 되기에, 기존에 .value 를 통해 접근했던 방식을 사용하지 않고 실제 값을 바로 사용할 수 있게 되는 것입니다.
또한 by 키워드를 사용하지 않고 State<T> 를 파라미터 타입으로 넘겨주는 것보다 실제 값 자체를 넘겨주는 것 자체가 Preview에서의 사용이 간편하다는 것을 알게되었습니다.
실제로 = 키워드를 사용하여 State<T> 를 파라미터로 넘겨주게 된다면 State 는 인터페이스라 초기화 할 수 없다는 에러메세지를 띄웁니다.
그렇다면 다른 방식으로 mutableStateOf()를 통해 더미 데이터를 넣어주려고 하니 이번에는 remember 키워드랑 같이 써야한다는 에러메세지를 만날 수 있었습니다..
즉, State<T> 타입을 더미데이터로 만들기 위해서는 Preview에서도 아래와 같은 코드를 작성하여 직접 상태를 만들고 더미데이터로 넣어줘야하는 번거로움이 있었습니다.
val uiState = rememberSaveable { mutableStateOf(UiState.Success) }
실제 Compose-samples에서는 상위 컴포저블에서 상태 관리는 by 키워드를 사용하여 위임하고 있었습니다.
그렇게 되면 하위 컴포저블로 넘겨주는 파라미터로 State<T> 가 아닌, 실제 값을 넣어주고 있기에 Preview에서도 간편하게 더미데이터를 생성하여 사용하고 있습니다.
= 키워드와 by 키워드의 선언은 동일한 것이라는 것을 공식 문서에서 직접 확인했기에 이 정도면 충분한 근거라고 생각하여 Compose-samples의 아이디어를 바탕으로 Preview 문제를 해결했습니다!!
실제로 팀원분께서 이런 부분에 대해 PR에 질문을 주셨고 이 근거를 바탕으로 답변을 남긴 경험도 있습니다~!
할 수 있다..! 👍
'안찌의 개발일기 > Android' 카테고리의 다른 글
[Android] Composition 뜯어보기 (0) | 2025.01.08 |
---|---|
[Android] by remember의 리컴포지션 문제 (Compose) (1) | 2024.11.19 |
[Android] 서버 통신 (2) | 2024.09.25 |
[Android] 코루틴 (Coroutine) (1) | 2024.06.18 |
[Android] RecyclerView, DiffUtil (1) | 2024.06.13 |