AbstractSavedStateViewModelFactory.kt

/*
 * Copyright 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.lifecycle

import android.os.Bundle
import androidx.annotation.RestrictTo
import androidx.lifecycle.LegacySavedStateHandleController.attachHandleIfNeeded
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner

/**
 * Skeleton of androidx.lifecycle.ViewModelProvider.KeyedFactory
 * that creates [SavedStateHandle] for every requested [ViewModel].
 * The subclasses implement [create] to actually instantiate
 * `androidx.lifecycle.ViewModel`s.
 */
public abstract class AbstractSavedStateViewModelFactory :
    ViewModelProvider.OnRequeryFactory,
    ViewModelProvider.Factory {

    private var savedStateRegistry: SavedStateRegistry? = null
    private var lifecycle: Lifecycle? = null
    private var defaultArgs: Bundle? = null

    /**
     * Constructs this factory.
     *
     * When a factory is constructed this way, a component for which [SavedStateHandle] is
     * scoped must have called
     * [SavedStateHandleSupport.enableSavedStateHandles].
     * See [CreationExtras.createSavedStateHandle] docs for more
     * details.
     */
    constructor() {}

    /**
     * Constructs this factory.
     *
     * @param owner [SavedStateRegistryOwner] that will provide restored state for created
     * [ViewModels][ViewModel]
     * @param defaultArgs values from this `Bundle` will be used as defaults by
     * [SavedStateHandle] passed in [ViewModels][ViewModel] if there is no
     * previously saved state or previously saved state misses a value by such key
     */
    constructor(
        owner: SavedStateRegistryOwner,
        defaultArgs: Bundle?
    ) {
        savedStateRegistry = owner.savedStateRegistry
        lifecycle = owner.lifecycle
        this.defaultArgs = defaultArgs
    }

    /**
     * Creates a new instance of the given `Class`.
     *
     * @param modelClass a `Class` whose instance is requested
     * @param extras an additional information for this creation request
     *
     * @return a newly created ViewModel
     *
     * @throws IllegalStateException if no VIEW_MODEL_KEY provided by ViewModelProvider
     */
    public override fun <T : ViewModel> create(
        modelClass: Class<T>,
        extras: CreationExtras
    ): T {
        val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
            ?: throw IllegalStateException(
                "VIEW_MODEL_KEY must always be provided by ViewModelProvider"
            )
        // if a factory constructed in the old way use the old infra to create SavedStateHandle
        return if (savedStateRegistry != null) {
            create(key, modelClass)
        } else {
            create(key, modelClass, extras.createSavedStateHandle())
        }
    }

    private fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
        val controller = LegacySavedStateHandleController
            .create(savedStateRegistry!!, lifecycle!!, key, defaultArgs)
        val viewModel = create(key, modelClass, controller.handle)
        viewModel.setTagIfAbsent(TAG_SAVED_STATE_HANDLE_CONTROLLER, controller)
        return viewModel
    }

    /**
     * Creates a new instance of the given `Class`.
     *
     * @param modelClass a `Class` whose instance is requested
     *
     * @return a newly created ViewModel
     *
     * @throws IllegalArgumentException if the given [modelClass] is local or anonymous class.
     * @throws UnsupportedOperationException if AbstractSavedStateViewModelFactory constructed
     * with empty constructor, therefore no [SavedStateRegistryOwner] available for lifecycle
     */
    public override fun <T : ViewModel> create(modelClass: Class<T>): T {
        // ViewModelProvider calls correct create that support same modelClass with different keys
        // If a developer manually calls this method, there is no "key" in picture, so factory
        // simply uses classname internally as as key.
        val canonicalName = modelClass.canonicalName
            ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
        if (lifecycle == null) {
            throw UnsupportedOperationException(
                "AbstractSavedStateViewModelFactory constructed " +
                    "with empty constructor supports only calls to " +
                    "create(modelClass: Class<T>, extras: CreationExtras)."
            )
        }
        return create(canonicalName, modelClass)
    }

    /**
     * Creates a new instance of the given `Class`.
     *
     * @param key a key associated with the requested ViewModel
     * @param modelClass a `Class` whose instance is requested
     * @param handle a handle to saved state associated with the requested ViewModel
     *
     * @return the newly created ViewModel
    </T> */
    protected abstract fun <T : ViewModel> create(
        key: String,
        modelClass: Class<T>,
        handle: SavedStateHandle
    ): T

    /**
     * @hide
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    override fun onRequery(viewModel: ViewModel) {
        // is need only for legacy path
        if (savedStateRegistry != null) {
            attachHandleIfNeeded(viewModel, savedStateRegistry!!, lifecycle!!)
        }
    }

    internal companion object {
        internal const val TAG_SAVED_STATE_HANDLE_CONTROLLER =
            "androidx.lifecycle.savedstate.vm.tag"
    }
}