NavigatorProvider.kt
/*
* Copyright (C) 2017 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.navigation
import android.annotation.SuppressLint
import androidx.annotation.CallSuper
import androidx.annotation.RestrictTo
import kotlin.reflect.KClass
/**
* A NavigationProvider stores a set of [Navigator]s that are valid ways to navigate
* to a destination.
*/
@SuppressLint("TypeParameterUnusedInFormals")
public open class NavigatorProvider {
private val _navigators: MutableMap<String, Navigator<out NavDestination>> = mutableMapOf()
@get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public val navigators: Map<String, Navigator<out NavDestination>>
get() = _navigators.toMap()
/**
* Retrieves a registered [Navigator] using the name provided by the
* [Navigator.Name annotation][Navigator.Name].
*
* @param navigatorClass class of the navigator to return
* @return the registered navigator with the given [Navigator.Name]
*
* @throws IllegalArgumentException if the Navigator does not have a
* [Navigator.Name annotation][Navigator.Name]
* @throws IllegalStateException if the Navigator has not been added
*
* @see NavigatorProvider.addNavigator
*/
public fun <T : Navigator<*>> getNavigator(navigatorClass: Class<T>): T {
val name = getNameForNavigator(navigatorClass)
return getNavigator(name)
}
/**
* Retrieves a registered [Navigator] by name.
*
* @param name name of the navigator to return
* @return the registered navigator with the given name
*
* @throws IllegalStateException if the Navigator has not been added
*
* @see NavigatorProvider.addNavigator
*/
@Suppress("UNCHECKED_CAST")
@CallSuper
public open fun <T : Navigator<*>> getNavigator(name: String): T {
require(validateName(name)) { "navigator name cannot be an empty string" }
val navigator = _navigators[name]
?: throw IllegalStateException(
"Could not find Navigator with name \"$name\". You must call " +
"NavController.addNavigator() for each navigation type."
)
return navigator as T
}
/**
* Register a navigator using the name provided by the
* [Navigator.Name annotation][Navigator.Name]. [destinations][NavDestination] may
* refer to any registered navigator by name for inflation. If a navigator by this name is
* already registered, this new navigator will replace it.
*
* @param navigator navigator to add
* @return the previously added Navigator for the name provided by the
* [Navigator.Name annotation][Navigator.Name], if any
*/
public fun addNavigator(
navigator: Navigator<out NavDestination>
): Navigator<out NavDestination>? {
return addNavigator(getNameForNavigator(navigator.javaClass), navigator)
}
/**
* Register a navigator by name. [destinations][NavDestination] may refer to any
* registered navigator by name for inflation. If a navigator by this name is already
* registered, this new navigator will replace it.
*
* @param name name for this navigator
* @param navigator navigator to add
* @return the previously added Navigator for the given name, if any
*/
@CallSuper
public open fun addNavigator(
name: String,
navigator: Navigator<out NavDestination>
): Navigator<out NavDestination>? {
require(validateName(name)) { "navigator name cannot be an empty string" }
val previousNavigator = _navigators[name]
if (previousNavigator == navigator) {
return navigator
}
check(previousNavigator?.isAttached != true) {
"Navigator $navigator is replacing an already attached $previousNavigator"
}
check(!navigator.isAttached) {
"Navigator $navigator is already attached to another NavController"
}
return _navigators.put(name, navigator)
}
internal companion object {
private val annotationNames = mutableMapOf<Class<*>, String?>()
internal fun validateName(name: String?): Boolean {
return name != null && name.isNotEmpty()
}
@JvmStatic
internal fun getNameForNavigator(navigatorClass: Class<out Navigator<*>>): String {
var name = annotationNames[navigatorClass]
if (name == null) {
val annotation = navigatorClass.getAnnotation(
Navigator.Name::class.java
)
name = annotation?.value
require(validateName(name)) {
"No @Navigator.Name annotation found for ${navigatorClass.simpleName}"
}
annotationNames[navigatorClass] = name
}
return name!!
}
}
}
/**
* Retrieves a registered [Navigator] by name.
*
* @throws IllegalStateException if the Navigator has not been added
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : Navigator<out NavDestination>> NavigatorProvider.get(
name: String
): T = getNavigator(name)
/**
* Retrieves a registered [Navigator] using the name provided by the
* [Navigator.Name annotation][Navigator.Name].
*
* @throws IllegalStateException if the Navigator has not been added
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : Navigator<out NavDestination>> NavigatorProvider.get(
clazz: KClass<T>
): T = getNavigator(clazz.java)
/**
* Register a [Navigator] by name. If a navigator by this name is already
* registered, this new navigator will replace it.
*
* @return the previously added [Navigator] for the given name, if any
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun NavigatorProvider.set(
name: String,
navigator: Navigator<out NavDestination>
): Navigator<out NavDestination>? = addNavigator(name, navigator)
/**
* Register a navigator using the name provided by the
* [Navigator.Name annotation][Navigator.Name].
*/
@Suppress("NOTHING_TO_INLINE")
public inline operator fun NavigatorProvider.plusAssign(navigator: Navigator<out NavDestination>) {
addNavigator(navigator)
}