SavedStateViewModelFactory.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.
 */
@file:RestrictTo(RestrictTo.Scope.LIBRARY)

package androidx.lifecycle

import android.annotation.SuppressLint
import android.app.Application
import android.os.Bundle
import androidx.annotation.RestrictTo
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.getInstance
import androidx.lifecycle.ViewModelProvider.NewInstanceFactory.Companion.instance
import androidx.lifecycle.viewmodel.CreationExtras
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryOwner
import java.lang.reflect.Constructor
import java.lang.reflect.InvocationTargetException

/**
 * [androidx.lifecycle.ViewModelProvider.Factory] that can create ViewModels accessing and
 * contributing to a saved state via [SavedStateHandle] received in a constructor.
 * If `defaultArgs` bundle was passed into the constructor, it will provide default
 * values in `SavedStateHandle`.
 *
 * If ViewModel is instance of [androidx.lifecycle.AndroidViewModel], it looks for a
 * constructor that receives an [Application] and [SavedStateHandle] (in this order),
 * otherwise it looks for a constructor that receives [SavedStateHandle] only.
 * [androidx.lifecycle.AndroidViewModel] is only supported if you pass a non-null
 * [Application] instance.
 */
class SavedStateViewModelFactory : ViewModelProvider.OnRequeryFactory, ViewModelProvider.Factory {
    private var application: Application? = null
    private val factory: ViewModelProvider.Factory
    private var defaultArgs: Bundle? = null
    private var lifecycle: Lifecycle? = null
    private var savedStateRegistry: SavedStateRegistry? = null

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

    /**
     * Creates [SavedStateViewModelFactory].
     *
     * [androidx.lifecycle.ViewModel] created with this factory can access to saved state
     * scoped to the given `activity`.
     *
     * @param application an application.  If null, [AndroidViewModel] instances will not be
     * supported.
     * @param owner       [SavedStateRegistryOwner] that will provide restored state for created
     * [ViewModels][androidx.lifecycle.ViewModel]
     */
    constructor(
        application: Application?,
        owner: SavedStateRegistryOwner
    ) : this(application, owner, null)

    /**
     * Creates [SavedStateViewModelFactory].
     *
     * [androidx.lifecycle.ViewModel] created with this factory can access to saved state
     * scoped to the given `activity`.
     *
     * When a factory is constructed this way, if you add any [CreationExtras] those arguments will
     * be used instead of the state passed in here. It is not possible to mix the arguments
     * received here with the [CreationExtras].
     *
     * @param application an application. If null, [AndroidViewModel] instances will not be
     * supported.
     * @param owner       [SavedStateRegistryOwner] that will provide restored state for created
     * [ViewModels][androidx.lifecycle.ViewModel]
     * @param defaultArgs values from this `Bundle` will be used as defaults by [SavedStateHandle]
     * if there is no previously saved state or previously saved state misses a value by such key.
     */
    @SuppressLint("LambdaLast")
    constructor(application: Application?, owner: SavedStateRegistryOwner, defaultArgs: Bundle?) {
        savedStateRegistry = owner.savedStateRegistry
        lifecycle = owner.lifecycle
        this.defaultArgs = defaultArgs
        this.application = application
        factory = if (application != null) getInstance(application)
            else ViewModelProvider.AndroidViewModelFactory()
    }

    /**
     * {@inheritDoc}
     *
     * @throws IllegalStateException if the provided extras do not provide a
     * [ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
     */
    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"
            )

        return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
            extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {
            val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
            val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
            val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
                findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
            } else {
                findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
            }
            // doesn't need SavedStateHandle
            if (constructor == null) {
                return factory.create(modelClass, extras)
            }
            val viewModel = if (isAndroidViewModel && application != null) {
                newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
            } else {
                newInstance(modelClass, constructor, extras.createSavedStateHandle())
            }
            viewModel
        } else {
            val viewModel = if (lifecycle != null) {
                create(key, modelClass)
            } else {
                throw IllegalStateException("SAVED_STATE_REGISTRY_OWNER_KEY and" +
                    "VIEW_MODEL_STORE_OWNER_KEY must be provided in the creation extras to" +
                    "successfully create a ViewModel.")
            }
            viewModel
        }
    }

    /**
     * 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
     * @return a newly created ViewModel
     *
     * @throws UnsupportedOperationException if the there is no lifecycle
     */
    fun <T : ViewModel> create(key: String, modelClass: Class<T>): T {
        // empty constructor was called.
        if (lifecycle == null) {
            throw UnsupportedOperationException(
                "SavedStateViewModelFactory constructed with empty constructor supports only " +
                    "calls to create(modelClass: Class<T>, extras: CreationExtras)."
            )
        }
        val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
        val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
            findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
        } else {
            findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
        }
        // doesn't need SavedStateHandle
        if (constructor == null) {
            // If you are using a stateful constructor and no application is available, we
            // use an instance factory instead.
            return if (application != null) factory.create(modelClass)
                else instance.create(modelClass)
        }
        val controller = LegacySavedStateHandleController.create(
            savedStateRegistry, lifecycle, key, defaultArgs
        )
        val viewModel: T = if (isAndroidViewModel && application != null) {
            newInstance(modelClass, constructor, application!!, controller.handle)
        } else {
            newInstance(modelClass, constructor, controller.handle)
        }
        viewModel.setTagIfAbsent(
            AbstractSavedStateViewModelFactory.TAG_SAVED_STATE_HANDLE_CONTROLLER, controller
        )
        return viewModel
    }

    /**
     * {@inheritDoc}
     *
     * @throws IllegalArgumentException if the given modelClass does not have a classname
     */
    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")
        return create(canonicalName, modelClass)
    }

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

internal fun <T : ViewModel?> newInstance(
    modelClass: Class<T>,
    constructor: Constructor<T>,
    vararg params: Any
): T {
    return try {
        constructor.newInstance(*params)
    } catch (e: IllegalAccessException) {
        throw RuntimeException("Failed to access $modelClass", e)
    } catch (e: InstantiationException) {
        throw RuntimeException("A $modelClass cannot be instantiated.", e)
    } catch (e: InvocationTargetException) {
        throw RuntimeException(
            "An exception happened in constructor of $modelClass", e.cause
        )
    }
}

private val ANDROID_VIEWMODEL_SIGNATURE = listOf<Class<*>>(
    Application::class.java,
    SavedStateHandle::class.java
)
private val VIEWMODEL_SIGNATURE = listOf<Class<*>>(SavedStateHandle::class.java)

// it is done instead of getConstructor(), because getConstructor() throws an exception
// if there is no such constructor, which is expensive
internal fun <T> findMatchingConstructor(
    modelClass: Class<T>,
    signature: List<Class<*>>
): Constructor<T>? {
    for (constructor in modelClass.constructors) {
        val parameterTypes = constructor.parameterTypes.toList()
        if (signature == parameterTypes) {
            @Suppress("UNCHECKED_CAST")
            return constructor as Constructor<T>
        }
        if (signature.size == parameterTypes.size && parameterTypes.containsAll(signature)) {
            throw UnsupportedOperationException(
                "Class ${modelClass.simpleName} must have parameters in the proper " +
                    "order: $signature"
            )
        }
    }
    return null
}