-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added `instruction` property directly to `NavigationContext`, to provide easy access to the instruction * Added extensions `getViewModel` and `requireViewModel` to `NavigationContext` to access `ViewModels` directly from a context reference * Added extensions for `findContext` and `findActiveContext` to `NavigationContext` to allow for finding other NavigationContexts from a context reference * Updated `NavigationContainer` to add `getChildContext` which allows finding specific Active/ActivePushed/ActivePresented/Specific contexts from a container reference * Added `instruction` property to `NavigationContext`, and marked `NavigationContext` as `@AdvancedEnroApi`
- Loading branch information
Showing
19 changed files
with
1,280 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
enro-core/src/main/java/dev/enro/core/NavigationContext.findActiveContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package dev.enro.core | ||
|
||
import dev.enro.core.container.NavigationContainer | ||
import kotlin.reflect.KClass | ||
|
||
|
||
/** | ||
* Finds a NavigationContext that matches the predicate. This will search the hierarchy of active NavigationContexts starting | ||
* at the context it is invoked on (not the root). Only active child contexts are considered (e.g. if there are | ||
* two containers visible, only the active container will be searched). | ||
* | ||
* If you want to search the entire hierarchy, including the root, you should call this function on the root NavigationContext, | ||
* which can be accessed from any NavigationContext by using the [rootContext] function. | ||
*/ | ||
public fun NavigationContext<*>.findActiveContext(predicate: (NavigationContext<*>) -> Boolean): NavigationContext<*>? { | ||
val contexts = mutableListOf(this) | ||
while (contexts.isNotEmpty()) { | ||
val context = contexts.removeAt(0) | ||
if (predicate(context)) { | ||
return context | ||
} | ||
val children = context.containerManager.activeContainer?.let { | ||
setOfNotNull( | ||
it.getChildContext(NavigationContainer.ContextFilter.ActivePushed), | ||
it.getChildContext(NavigationContainer.ContextFilter.ActivePresented), | ||
) | ||
}.orEmpty() | ||
contexts.addAll(children) | ||
} | ||
return null | ||
} | ||
|
||
/** | ||
* Requires an active NavigationContext that matches the predicate. A wrapper for [findActiveContext] that throws an exception if | ||
* no matching context is found. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public fun NavigationContext<*>.requireActiveContext(predicate: (NavigationContext<*>) -> Boolean): NavigationContext<*> { | ||
return requireNotNull(findActiveContext(predicate)) | ||
} | ||
|
||
/** | ||
* Finds an active NavigationContext that has a NavigationKey of type [keyType]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public fun NavigationContext<*>.findActiveContextWithKey(keyType: KClass<*>): NavigationContext<*>? { | ||
return findActiveContext { | ||
val key = it.instruction?.navigationKey ?: return@findActiveContext false | ||
key::class == keyType | ||
} | ||
} | ||
|
||
/** | ||
* Requires an active NavigationContext that has a NavigationKey of type [keyType]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public fun NavigationContext<*>.requireActiveContextWithKey(keyType: KClass<*>): NavigationContext<*> { | ||
return requireContext { | ||
val key = it.instruction?.navigationKey ?: return@requireContext false | ||
key::class == keyType | ||
} | ||
} | ||
|
||
/** | ||
* Finds an active NavigationContext that has a NavigationKey of type [T]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.findActiveContextWithKey(): NavigationContext<*>? { | ||
return findActiveContext { it.instruction?.navigationKey is T } | ||
} | ||
|
||
/** | ||
* Requires an active NavigationContext that has a NavigationKey of type [T]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.requireActiveContextWithKey(): NavigationContext<*> { | ||
return requireContext { it.instruction?.navigationKey is T } | ||
} | ||
|
||
/** | ||
* Finds an active NavigationContext that has a NavigationKey of matching [predicate]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.findActiveContextWithKey(crossinline predicate: (NavigationKey) -> Boolean): NavigationContext<*>? { | ||
return findActiveContext { it.instruction?.navigationKey?.let(predicate) ?: false } | ||
} | ||
|
||
/** | ||
* Requires an active NavigationContext that has a NavigationKey of matching [predicate]. | ||
* | ||
* @see [findActiveContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.requireActiveContextWithKey(crossinline predicate: (NavigationKey) -> Boolean): NavigationContext<*> { | ||
return requireContext { it.instruction?.navigationKey?.let(predicate) ?: false } | ||
} |
100 changes: 100 additions & 0 deletions
100
enro-core/src/main/java/dev/enro/core/NavigationContext.findContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package dev.enro.core | ||
|
||
import dev.enro.core.container.NavigationContainer | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* Finds a NavigationContext that matches the predicate. This will search the entire hierarchy of NavigationContexts starting | ||
* at the context it is invoked on (not the root). All child contexts are considered, including contexts which are not in the | ||
* active NavigationContainer (e.g. if there are two containers visible, both the active and non-active container will be searched). | ||
* | ||
* If you want to search the entire hierarchy, including the root, you should call this function on the root NavigationContext, | ||
* which can be accessed from any NavigationContext by using the [rootContext] function. | ||
*/ | ||
public fun NavigationContext<*>.findContext(predicate: (NavigationContext<*>) -> Boolean): NavigationContext<*>? { | ||
val contexts = mutableListOf(this) | ||
while (contexts.isNotEmpty()) { | ||
val context = contexts.removeAt(0) | ||
if (predicate(context)) { | ||
return context | ||
} | ||
val children = context.containerManager.containers.flatMap { | ||
setOfNotNull( | ||
it.getChildContext(NavigationContainer.ContextFilter.ActivePushed), | ||
it.getChildContext(NavigationContainer.ContextFilter.ActivePresented), | ||
) | ||
} | ||
contexts.addAll(children) | ||
} | ||
return null | ||
} | ||
|
||
/** | ||
* Requires a NavigationContext that matches the predicate. A wrapper for [findContext] that throws an exception if | ||
* no matching context is found. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public fun NavigationContext<*>.requireContext(predicate: (NavigationContext<*>) -> Boolean): NavigationContext<*> { | ||
return requireNotNull(findContext(predicate)) | ||
} | ||
|
||
/** | ||
* Finds a NavigationContext that has a NavigationKey of type [keyType]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public fun NavigationContext<*>.findContextWithKey(keyType: KClass<*>): NavigationContext<*>? { | ||
return findContext { | ||
val key = it.instruction?.navigationKey ?: return@findContext false | ||
key::class == keyType | ||
} | ||
} | ||
|
||
/** | ||
* Requires a NavigationContext that has a NavigationKey of type [keyType]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public fun NavigationContext<*>.requireContextWithKey(keyType: KClass<*>): NavigationContext<*> { | ||
return requireContext { | ||
val key = it.instruction?.navigationKey ?: return@requireContext false | ||
key::class == keyType | ||
} | ||
} | ||
|
||
/** | ||
* Finds a NavigationContext that has a NavigationKey of type [T]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.findContextWithKey(): NavigationContext<*>? { | ||
return findContext { it.instruction?.navigationKey is T } | ||
} | ||
|
||
/** | ||
* Requires a NavigationContext that has a NavigationKey of type [T]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.requireContextWithKey(): NavigationContext<*> { | ||
return requireContext { it.instruction?.navigationKey is T } | ||
} | ||
|
||
/** | ||
* Finds a NavigationContext that has a NavigationKey of matching [predicate]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.findContextWithKey(crossinline predicate: (NavigationKey) -> Boolean): NavigationContext<*>? { | ||
return findContext { it.instruction?.navigationKey?.let(predicate) ?: false } | ||
} | ||
|
||
/** | ||
* Requires a NavigationContext that has a NavigationKey of matching [predicate]. | ||
* | ||
* @see [findContext] | ||
*/ | ||
public inline fun <reified T> NavigationContext<*>.requireContextWithKey(crossinline predicate: (NavigationKey) -> Boolean): NavigationContext<*> { | ||
return requireContext { it.instruction?.navigationKey?.let(predicate) ?: false } | ||
} |
115 changes: 115 additions & 0 deletions
115
enro-core/src/main/java/dev/enro/core/NavigationContext.getViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package dev.enro.core | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.ViewModelProvider | ||
import androidx.lifecycle.viewmodel.CreationExtras | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* When attempting to find a ViewModel in a NavigationContext, we don't want to create a new ViewModel, rather we want to | ||
* get an existing instance of that ViewModel, if it exists, so this ViewModelProvider.Factory always throws an exception | ||
* if it is ever asked to actually create a ViewModel. | ||
*/ | ||
private class NavigationContextViewModelFactory( | ||
private val context: NavigationContext<*>, | ||
) : ViewModelProvider.Factory { | ||
override fun <T : ViewModel> create(modelClass: Class<T>): T { | ||
viewModelNotFoundError(context, modelClass) | ||
} | ||
|
||
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T { | ||
viewModelNotFoundError(context, modelClass) | ||
} | ||
} | ||
|
||
private fun viewModelNotFoundError(context: NavigationContext<*>, modelClass: Class<*>): Nothing { | ||
val key = context.instruction?.navigationKey | ||
error("ViewModel ${modelClass.simpleName} was not found in NavigationContext with navigation key $key") | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested, or null if the ViewModel does not exist in the NavigationContext's ViewModelStore | ||
*/ | ||
public fun <T : ViewModel> NavigationContext<*>.getViewModel( | ||
cls: Class<T>, | ||
key: String? = null, | ||
): T? { | ||
val provider = ViewModelProvider( | ||
store = viewModelStoreOwner.viewModelStore, | ||
factory = NavigationContextViewModelFactory(this) | ||
) | ||
val result = kotlin.runCatching { | ||
when (key) { | ||
null -> provider[cls] | ||
else -> provider[key, cls] | ||
} | ||
} | ||
return result.getOrNull() | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested | ||
* | ||
* @throws IllegalStateException if the ViewModel does not already exist in the NavigationContext | ||
*/ | ||
public fun <T : ViewModel> NavigationContext<*>.requireViewModel( | ||
cls: Class<T>, | ||
key: String? = null, | ||
): T { | ||
return getViewModel(cls, key) | ||
?: viewModelNotFoundError(this, cls) | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested, or null if the ViewModel does not exist in the NavigationContext's ViewModelStore | ||
*/ | ||
public fun <T : ViewModel> NavigationContext<*>.getViewModel( | ||
cls: KClass<T>, | ||
key: String? = null, | ||
): T? { | ||
return getViewModel(cls.java, key) | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested | ||
* | ||
* @throws IllegalStateException if the ViewModel does not already exist in the NavigationContext | ||
*/ | ||
public fun <T : ViewModel> NavigationContext<*>.requireViewModel( | ||
cls: KClass<T>, | ||
key: String? = null, | ||
): T { | ||
return requireViewModel(cls.java, key) | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested, or null if the ViewModel does not exist in the NavigationContext's ViewModelStore | ||
*/ | ||
public inline fun <reified T : ViewModel> NavigationContext<*>.getViewModel( | ||
key: String? = null, | ||
): T? { | ||
return getViewModel(T::class.java, key) | ||
} | ||
|
||
/** | ||
* Attempt to get a ViewModel of a certain type from a NavigationContext. | ||
* | ||
* @return The ViewModel requested | ||
* | ||
* @throws IllegalStateException if the ViewModel does not already exist in the NavigationContext | ||
*/ | ||
public inline fun <reified T : ViewModel> NavigationContext<*>.requireViewModel( | ||
key: String? = null, | ||
): T { | ||
return requireViewModel(T::class.java, key) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.