
Compose 에서 remember는 Composable 함수에서 상태를 기억하기 위해 사용됩니다.
하지만 remember로 저장한 값은 Activity가 재생성되면 사라집니다.
즉, 화면 회전, 다크 모드 전환 등 구성 변경(Configuration Change) 이 발생하면 Activity가 다시 만들어지면서 기존 상태가 초기화되죠.
이때 사용하는 것이 바로 rememberSaveable입니다.
rememberSaveable은 구성 변경이 일어나더라도 값을 자동으로 복원해주는 기능을 제공합니다.
내부적으로는
SavedInstanceState를 활용해 직렬화 가능한 데이터를 보존합니다.
구성 변경 시 상태를 유지하는 방법은 여러 가지가 있습니다.
아래는 대표적인 네 가지 접근 방식이에요 👇
| 구분 | 설명 |
|---|---|
| 1. UI 상태 저장 및 복원 | onSaveInstanceState() / onRestoreInstanceState()를 직접 구현해 일시적으로 상태를 저장 후 복원 |
| 2. ViewModel | UI 관련 데이터를 메모리에 보관하여 구성 변경에도 상태를 유지 (가장 권장되는 방식) |
| 3. 수동 구성 변경 처리 | AndroidManifest.xml의 android:configChanges 속성을 이용해 구성 변경을 직접 처리 (onConfigurationChanged() 재정의) |
| 4. Compose의 rememberSaveable | Compose 전용 상태 저장 함수로, 내부적으로 Bundle 기반 상태 복원을 자동 처리 |
ViewModel은 단순히 비즈니스 로직을 분리하는 것뿐만 아니라,
UI 상태를 안정적으로 저장하고 유지하는 역할도 매우 중요하다고 생각합니다.
그렇담 ViewModel도 대표적인 구성 변경을 예방하는 방법 중 하나인데, 어떻게 데이터를 복원할까요? ViewModel은 Activity 와 독립적인 생명주기를 가지는 걸까요?
✅ 사용자가 기기를 회전하면 Activity 또는 Fragment가 소멸되고
다시 생성되지만 ViewModel은 파괴되지 않아 데이터가 그대로 유지되도록 보장합니다.
이를 이해하려면 먼저 ViewModel이 어떻게 생성되고 저장되는지 알아야 합니다.
// 1️⃣ 기본 ViewModelProvider 사용
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// 2️⃣ Kotlin delegate 사용 (간결한 KTX 방식)
val viewModel by viewModels<MainViewModel>()
// 3️⃣ Compose 환경에서 사용
val viewModel: MainViewModel = viewModel()
가장 기본적인 방식으로, 직접 ViewModelProvider를 통해 ViewModel을 가져옵니다.
명시적으로 ViewModelStoreOwner(예: Activity, Fragment)를 지정할 수 있습니다.
by viewModels)KTX 확장을 활용한 간결한 방식으로, 내부적으로는 ViewModelProvider와 동일하게 동작합니다.
factoryProducer가 있으면 해당 팩토리를, 없으면 defaultViewModelProviderFactory 사용extrasProducer가 있으면 호출하여 CreationExtras 생성ViewModelLazy 인스턴스를 반환 (Lazy<VM> 형태로 by 사용 가능)ViewModelProvider(viewModelStore, factory, extras).get(VM::class.java)
Compose 환경에서는 별도의 Delegate 없이 androidx.lifecycle.viewmodel.compose.viewModel()을 사용합니다.
LocalViewModelStoreOwner를 자동으로 참조key, factory, owner 등을 직접 지정해 범위를 제어할 수도 있습니다.@Composable
fun MainScreen() {
val viewModel: MainViewModel = viewModel()
}
ViewModel이 실제로 어떻게 만들어지는지 한 번 따라가볼게요 👇

ViewModelProvider를 통해 ViewModel 인스턴스를 요청ViewModelStoreOwner를 참조하여 ViewModelStore를 가져옴ViewModelStore에 기존 인스턴스가 있는지 확인Factory를 통해 새 ViewModel 생성ViewModelStore에 저장이후 같은 Owner 내에서 다시 요청하더라도 동일한 인스턴스를 반환합니다.

Map 구조로 관리됩니다.ViewModelStore를 소유하고 관리하는 주체입니다.Activity, Fragment, NavBackStackEntry가 이에 해당합니다. 실제로 ViewModelStoreOwner 를 상속 받고 있어요.ViewModelStore의 clear()가 호출되어 ViewModel이 정리됩니다.✅ Compose의 경우, NavBackStackEntry가 네비게이션 스택에서 제거될 때 ViewModel이 함께 해제됩니다.

ViewModel은 Activity나 Fragment의 생명주기와 1:1로 묶이지 않습니다.
대신 ViewModelStoreOwner에 의해 관리되며,
기본적으로 Activity가 완전히 종료(onDestroy)될 때 함께 제거됩니다.
그러면 이러한 질문이 생길 수도 있습니다.
configuration change 가 일어날때 destory 가 발생했다가 다시 create 되면 ViewModel 도 제거되는거 아닌가요??
정답 ViewModel은 Activity나 Fragment의 생명주기와 직접적으로 연결되어 있지 않습니다. 대신, LifecycleEventObserver를 통해 Activity의 라이프사이클 이벤트를 감지하며 “구성 변경 중인지”를 검사한 뒤, ViewModelStore를 clear할지 결정합니다.내부 동작을 통해 어떻게 처리되고 있는지 보겠습니다.
(1) LifecycleEventObserver 내부 동작
public class ComponentActivity {
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// Context 정리
mContextAwareHelper.clearAvailableContext();
// 구성 변경 중이 아니라면 ViewModelStore 제거
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
mReportFullyDrawnExecutor.activityDestroyed();
}
}
});
}
이 코드에서 핵심은 구성 변경 중(isChangingConfigurations() == true)이면 ViewModelStore.clear()를 호출하지 않는다는 점입니다.
→ 덕분에 ViewModel은 구성 변경 중에 ViewModel 이 삭제가 되지 않습니다.
(2) onRetainNonConfigurationInstance()에서 ViewModelStore 보존
한가지 과정이 더 남았는데요. Android는 Activity가 파괴되기 전에
onRetainNonConfigurationInstance()를 통해 NonConfigurationInstances 라는 곳에 ViewModelStore를 시스템에 임시 보관합니다.
이후 Activity 가 다시 그려질 때 ensureViewModelStore() 를 호출해 ViewModelStore 를 복구합니다.

// Activity 에서 자동적으로 화면 회전 동안 ViewModel Store를 보유합니다.
public final Object onRetainNonConfigurationInstance() {
ViewModelStore viewModelStore = mViewModelStore;
if (viewModelStore == null) {
NonConfigurationInstances nc = getLastNonConfigurationInstance();
if (nc != null) {
viewModelStore = nc.viewModelStore;
}
}
if (viewModelStore == null) {
return null; // Nothing to retain
}
// Package ViewModelStore for Android to preserve
NonConfigurationInstances nci = new NonConfigurationInstances();
nci.viewModelStore = viewModelStore; // This survives destruction!
return nci;
}
// 회전후 자동적으로 ViewModelStore 를 복구 합니다.
void ensureViewModelStore() {
if (mViewModelStore == null) {
NonConfigurationInstances nc = getLastNonConfigurationInstance();
if (nc != null) {
mViewModelStore = nc.viewModelStore; // Restoration
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
}
// === 앱 처음 실행 ===
class MainActivity : AppCompatActivity() {
lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModelProvider를 통해 UserViewModel 가져오기
// 처음 실행 시: ViewModelStore에 없음 → 새 인스턴스 생성
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
// 사용자가 데이터 입력
viewModel.counter = 5
viewModel.userInput = "Hello"
}
}
// === 화면 회전 발생 ===
// 1. Activity의 onRetainNonConfigurationInstance() 호출
// -> ViewModelStore를 시스템 메모리에 보관
// 2. 기존 Activity 종료, 새 Activity 생성
class MainActivity : AppCompatActivity() {
lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ViewModelProvider를 통해 UserViewModel 가져오기
// 화면 회전 시: ViewModelStore에 기존 인스턴스 존재 → 새로 생성하지 않고 기존 인스턴스 반환
viewModel = ViewModelProvider(this)[UserViewModel::class.java]
// 이전 Activity에서 입력한 데이터 유지
// viewModel.counter == 5
// viewModel.userInput == "Hello"
}
}
ViewModel의 생명주기는 ViewModelStoreOwner(Activity, Fragment 또는 기타 생명주기 인식 컴포넌트일 수 있음)에 연결됩니다. ViewModel은 ViewModelStoreOwner의 범위 내에서 존재하며, onRetainNonConfigurationInstance()를 통해 화면 회전과 같은 구성 변경 시에도 데이터와 상태가 유지되도록 보장합니다.