ViewModel의 저장된 상태 모듈   Android Jetpack의 구성요소

UI 상태 저장에서 언급했듯이 ViewModel 객체가 구성 변경사항을 처리할 수 있으므로 개발자는 회전이나 다른 상황에서 상태에 신경 쓸 필요가 없습니다. 그러나 시스템에서 시작된 프로세스 중단을 처리해야 한다면 onSaveInstanceState()를 백업으로 사용할 수 있습니다.

일반적으로 UI 상태는 활동이 아닌 ViewModel 객체에 저장되거나 참조됩니다. 따라서 onSaveInstanceState()를 사용하기 위해서는 저장된 상태 모듈이 개발자를 대신해 처리할 수 있는 상용구가 필요합니다.

이 모듈을 사용하면 ViewModel 객체는 생성자를 통해 SavedStateHandle 객체를 수신합니다. 이 객체는 저장된 상태에 객체를 작성하고 저장된 상태에서 객체를 검색할 수 있게 하는 키-값 맵입니다. 이러한 값은 시스템에서 프로세스가 중단된 후에도 유지되며 동일한 객체를 통해 계속 사용할 수 있습니다.

설정

Fragment 1.2.0 또는 전이 종속 항목인 Activity 1.1.0부터는 SavedStateHandleViewModel의 생성자 인수로 사용할 수 있습니다.

Kotlin

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

자바

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

그런 다음 추가 구성 없이 ViewModel 인스턴스를 가져올 수 있습니다. 기본 ViewModel 팩토리는 ViewModel에 적절한 SavedStateHandle을 제공합니다.

Kotlin

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

자바

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...

    }

    ...
}

맞춤 ViewModelProvider.Factory 인스턴스를 제공할 때 AbstractSavedStateViewModelFactory를 확장하여 SavedStateHandle 사용을 설정할 수 있습니다.

SavedStateHandle을 사용한 작업

SavedStateHandle 클래스는 set() 메서드와 get() 메서드를 통해 저장된 상태에 데이터를 작성하고 저장된 상태에서 데이터를 검색할 수 있게 하는 키-값 맵입니다.

SavedStateHandle을 사용하면 쿼리 값이 프로세스 중단 전반에 유지되어 활동이나 프래그먼트에서 값을 수동으로 저장 및 복원하고 ViewModel에 다시 전달하지 않고도 재생성 전과 후에 동일한 필터링된 데이터 세트가 사용자에게 표시됩니다.

SavedStateHandle에는 키-값 맵과 상호작용할 때 예상되는 다른 메서드도 있습니다.

또한 관측 가능한 데이터 홀더를 사용하여 SavedStateHandle에서 값을 가져올 수 있습니다. 지원되는 유형 목록은 다음과 같습니다.

LiveData

getLiveData()를 사용하여 관측 가능한 LiveData에 래핑된 값을 SavedStateHandle에서 가져옵니다. 키의 값이 업데이트되면 LiveData는 새 값을 수신합니다. 대부분의 경우 데이터 목록을 필터링하는 쿼리를 입력하는 등의 사용자 상호작용으로 인해 값이 설정됩니다. 그런 다음 업데이트된 값을 사용하여 LiveData를 변환할 수 있습니다.

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

자바

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public UserViewModelJava(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

StateFlow

getStateFlow()를 사용하여 관측 가능한 StateFlow에 래핑된 값을 SavedStateHandle에서 가져옵니다. 키의 값을 업데이트하면 StateFlow가 새 값을 수신합니다. 대부분의 경우 데이터 목록을 필터링하는 쿼리를 입력하는 등의 사용자 상호작용으로 인해 값을 설정할 수도 있습니다. 그런 다음 다른 Flow 연산자를 사용하여 업데이트된 값을 변환할 수 있습니다.

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: StateFlow<List<String>> =
        savedStateHandle.getStateFlow<String>("query")
            .flatMapLatest { query ->
                repository.getFilteredData(query)
            }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

실험용 Compose 상태 지원

lifecycle-viewmodel-compose 아티팩트는 실험용 saveable API를 제공합니다. 이 API는 SavedStateHandle과 Compose의 Saver 사이의 상호 운용성을 허용하므로 맞춤 SaverrememberSaveable을 통해 저장할 수 있는 모든 StateSavedStateHandle을 사용하여 저장할 수 있습니다.

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    val filteredData: List<String> by savedStateHandle.saveable {
        mutableStateOf>(emptyList())
    }

    fun setQuery(query: String) {
        withMutableSnapshot {
            filteredData = query
        }
    }
}

지원 유형

SavedStateHandle 내에 보관된 데이터는 활동 또는 프래그먼트의 나머지 savedInstanceState와 함께 Bundle로 저장되고 복원됩니다.

직접 지원되는 유형

기본적으로 다음과 같이 Bundle과 동일한 데이터 유형에 관해 SavedStateHandle에서 set()get()을 호출할 수 있습니다.

유형/클래스 지원 배열 지원
double double[]
int int[]
long long[]
String String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

클래스가 위의 목록에 있는 클래스 중 하나를 확장하지 않는 경우 @Parcelize Kotlin 주석을 추가하거나 Parcelable을 직접 구현하여 parcelable 클래스로 만들 수 있습니다.

비 parcelable 클래스 저장

클래스가 Parcelable 또는 Serializable을 구현하지 않으며 인터페이스 중 하나를 구현하기 위해 수정할 수 없는 경우 이 클래스의 인스턴스를 SavedStateHandle에 직접 저장할 수 없습니다.

Lifecycle 2.3.0-alpha03부터 SavedStateHandlesetSavedStateProvider() 메서드를 사용해 객체를 Bundle로 저장하는 자체 로직을 제공하여 모든 객체를 저장할 수 있습니다. SavedStateRegistry.SavedStateProvider는 저장할 상태가 포함된 Bundle을 반환하는 단일 saveState() 메서드를 정의하는 인터페이스입니다. SavedStateHandle은 상태를 저장할 준비가 되면 saveState()를 호출하여 SavedStateProvider에서 Bundle을 검색하고 연결된 키용으로 Bundle을 저장합니다.

ACTION_IMAGE_CAPTURE 인텐트를 통해 카메라 앱에서 이미지를 요청하고 카메라가 이미지를 저장해야 하는 임시 파일에 전달하는 앱의 예를 생각해 보세요. TempFileViewModel은 임시 파일을 만드는 로직을 캡슐화합니다.

Kotlin

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

자바

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

활동의 프로세스가 중단되고 나중에 복원되는 경우 임시 파일이 손실되지 않도록 TempFileViewModelSavedStateHandle을 사용하여 데이터를 유지할 수 있습니다. TempFileViewModel이 데이터를 저장할 수 있게 하려면 SavedStateProvider를 구현하고 ViewModelSavedStateHandle에 관한 제공자로 설정하세요.

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

자바

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

사용자가 돌아올 때 File 데이터를 복원하려면 SavedStateHandle에서 temp_file Bundle을 검색합니다. 절대 경로가 포함된 saveTempFile()에서 제공하는 것과 동일한 Bundle입니다. 그런 다음 이 절대 경로를 사용하여 새 File을 인스턴스화할 수 있습니다.

Kotlin

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

자바

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}

추가 리소스

ViewModel의 저장된 상태 모듈에 관한 자세한 내용은 다음 리소스를 참고하세요.

Codelab