Instantly share code, notes, and snippets.
Last active
June 22, 2020 15:56
-
Star
(0)
0
You must be signed in to star a gist -
Fork
(0)
0
You must be signed in to fork a gist
-
Save thegarlynch/5821b1fc46b2bf87054d6fc885e5d5d8 to your computer and use it in GitHub Desktop.
Navigation Gateway in navigation component
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
import android.os.Bundle | |
import androidx.annotation.IdRes | |
import androidx.navigation.* | |
/** | |
* | |
* Act as gateway between current fragment to it's destination | |
* | |
* Each [NavigationGateway] may have only one gatewayId (either a NavGraph or Destination) | |
* | |
* @sample | |
* | |
* class AuthenticatorNavigationGateway @Inject constructor( | |
* private val fragment : Fragment, | |
* private val sessionStorage : SessionStorage | |
* ) : NavigationGateway(fragment.findNavController()){ | |
* | |
* override fun getGatewayId() = R.id.auth_nav_graph | |
* override fun canProceed() = sessionStorage.currentSession != null | |
* | |
* } | |
* | |
* class LoginFragment { | |
* | |
* val gateway by gateway() | |
* | |
* override fun onViewCreated(view : View?){ | |
* if(LoginSuccess){ | |
* gateway.proceed() | |
* } | |
* } | |
* | |
* } | |
* | |
*/ | |
abstract class NavigationGateway constructor( | |
private val navController: NavController | |
) { | |
@IdRes | |
abstract fun getGatewayId() : Int | |
abstract fun canProceed() : Boolean | |
private fun requireGateway(){ | |
navController.graph.findNode(getGatewayId()) | |
?: throw IllegalArgumentException("Destination with id : ${getGatewayId()} cannot be found in this graph") | |
} | |
private fun requireAllowedOptions(options: NavOptions){ | |
if(options.popUpTo == -1) return | |
val gatewayDestination = navController.graph.findNode(getGatewayId())!! | |
if(gatewayDestination is NavGraph){ | |
gatewayDestination.forEach { | |
if(it.id == options.popUpTo){ | |
throw IllegalArgumentException("You cannot popUpTo any destination in this navGraph : ${options.popUpTo}") | |
} | |
} | |
}else{ | |
if(options.popUpTo == getGatewayId()){ | |
throw IllegalArgumentException("You cannot popUpTo Gateway : ${options.popUpTo}") | |
} | |
} | |
} | |
private fun initializeSavedState(destinationId: Int, args: Bundle?, options: NavOptions){ | |
navController.currentBackStackEntry!!.savedStateHandle.apply { | |
set(DESTINATION_ARGUMENTS, args) | |
set(DESTINATION_ID, destinationId) | |
set(POP_UP_INCLUSIVE, options.isPopUpToInclusive) | |
set(POP_UP_TO, options.popUpTo) | |
set(POPUP_ENTER_ANIM, options.popEnterAnim) | |
set(POPUP_EXIT_ANIM, options.popExitAnim) | |
set(ENTER_ANIM, options.enterAnim) | |
set(EXIT_ANIM, options.exitAnim) | |
set(LAUNCH_SINGLE_TOP, options.shouldLaunchSingleTop()) | |
set(GATEWAY_ID, getGatewayId()) | |
} | |
} | |
private fun requireAction(direction: NavDirections) : NavAction{ | |
return navController.currentDestination!!.getAction(direction.actionId) | |
?: navController.graph.getAction(direction.actionId) | |
?: throw IllegalArgumentException("direction must be in graph $direction") | |
} | |
fun navigate( | |
direction : NavDirections, | |
gatewayArgs: Bundle? = null | |
){ | |
requireGateway() | |
val action = requireAction(direction) | |
if(action.navOptions != null){ | |
requireAllowedOptions(action.navOptions!!) | |
} | |
if(canProceed()){ | |
navController.navigate(direction) | |
return | |
} | |
val newArgs = (gatewayArgs ?: Bundle()).apply { putInt(SAVED_STATE_HOLDER_ID, navController.currentDestination!!.id) } | |
val navOptions = action.navOptions ?: navOptions { } | |
initializeSavedState(action.destinationId, direction.arguments ,navOptions) | |
navController.navigate(getGatewayId(), newArgs, defaultGatewayOptions(inherit = navOptions)) | |
} | |
fun navigate( | |
@IdRes destinationId : Int, | |
args : Bundle?, | |
navOptions : NavOptions = navOptions { }, | |
gatewayArgs : Bundle? = null | |
){ | |
requireGateway() | |
requireAllowedOptions(navOptions) | |
if(canProceed()){ | |
navController.navigate(destinationId, args, navOptions) | |
return | |
} | |
val newArgs = (gatewayArgs ?: Bundle()).apply { putInt(SAVED_STATE_HOLDER_ID, navController.currentDestination!!.id) } | |
initializeSavedState(destinationId, args, navOptions) | |
navController.navigate(getGatewayId(), newArgs, defaultGatewayOptions(inherit = navOptions)) | |
} | |
companion object { | |
private const val PREFIX = "com.ibima.elearning.helper.navigation.gateway.NavigationGateway" | |
internal const val DESTINATION_ARGUMENTS = "${PREFIX}.destination_arguments" | |
internal const val DESTINATION_ID = "${PREFIX}.destinationId" | |
internal const val POP_UP_INCLUSIVE = "${PREFIX}.popUpInclusive" | |
internal const val POP_UP_TO = "${PREFIX}.popUpTo" | |
internal const val LAUNCH_SINGLE_TOP = "${PREFIX}.launchSingleTop" | |
internal const val ENTER_ANIM = "${PREFIX}.enterAnim" | |
internal const val EXIT_ANIM = "${PREFIX}.exitAnim" | |
internal const val POPUP_ENTER_ANIM = "${PREFIX}.popUpEnterAnim" | |
internal const val POPUP_EXIT_ANIM = "${PREFIX}.popUpExitAnim" | |
internal const val SAVED_STATE_HOLDER_ID = "${PREFIX}.savedStateHolderId" | |
internal const val GATEWAY_ID = "${PREFIX}.gatewayId" | |
} | |
} | |
private fun defaultGatewayOptions(inherit : NavOptions) = navOptions { | |
this.launchSingleTop = true | |
this.anim { | |
enter = inherit.enterAnim | |
popEnter = inherit.popEnterAnim | |
exit = inherit.exitAnim | |
popExit = inherit.popExitAnim | |
} | |
} |
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
import android.os.Bundle | |
import androidx.fragment.app.Fragment | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.LifecycleObserver | |
import androidx.lifecycle.OnLifecycleEvent | |
import androidx.lifecycle.SavedStateHandle | |
import androidx.navigation.NavController | |
import androidx.navigation.fragment.findNavController | |
import androidx.navigation.navOptions | |
class GatewayAction internal constructor( | |
private val navController: NavController, | |
private val handle : SavedStateHandle | |
) : LifecycleObserver { | |
private val destinationId : Int = handle.get<Int>(NavigationGateway.DESTINATION_ID) | |
?: throw IllegalStateException("You can only proceed if gateway is initialized") | |
private val destinationArgs = handle.get<Bundle?>(NavigationGateway.DESTINATION_ARGUMENTS) | |
private val navOptions = navOptions { | |
launchSingleTop = handle.get<Boolean>(NavigationGateway.LAUNCH_SINGLE_TOP)!! | |
val whatToPopUpTo = handle.get<Int>(NavigationGateway.POP_UP_TO)!! | |
if(whatToPopUpTo == -1){ | |
// if there is no destination to popUpTo -> remove Gateway | |
popUpTo(handle.get<Int>(NavigationGateway.GATEWAY_ID)!!){ inclusive = true } | |
}else { | |
popUpTo(handle.get<Int>(NavigationGateway.POP_UP_TO)!!) { | |
inclusive = handle.get<Boolean>(NavigationGateway.POP_UP_INCLUSIVE)!! | |
} | |
} | |
anim { | |
enter = handle.get<Int>(NavigationGateway.ENTER_ANIM)!! | |
exit = handle.get<Int>(NavigationGateway.EXIT_ANIM)!! | |
popEnter = handle.get<Int>(NavigationGateway.POPUP_ENTER_ANIM)!! | |
popExit = handle.get<Int>(NavigationGateway.POPUP_EXIT_ANIM)!! | |
} | |
} | |
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) | |
internal fun invalidateSavedState(){ | |
handle.remove<Int>(NavigationGateway.DESTINATION_ID) | |
handle.remove<Bundle?>(NavigationGateway.DESTINATION_ARGUMENTS) | |
handle.remove<Boolean>(NavigationGateway.LAUNCH_SINGLE_TOP) | |
handle.remove<Int>(NavigationGateway.POP_UP_TO) | |
handle.remove<Boolean>(NavigationGateway.POP_UP_INCLUSIVE) | |
handle.remove<Int>(NavigationGateway.ENTER_ANIM) | |
handle.remove<Int>(NavigationGateway.EXIT_ANIM) | |
handle.remove<Int>(NavigationGateway.POPUP_ENTER_ANIM) | |
handle.remove<Int>(NavigationGateway.POPUP_EXIT_ANIM) | |
handle.remove<Int>(NavigationGateway.GATEWAY_ID) | |
} | |
fun proceed(){ | |
navController.navigate(destinationId, destinationArgs, navOptions) | |
invalidateSavedState() | |
} | |
} | |
fun Fragment.gateway() : Lazy<GatewayAction>{ | |
return lazy { | |
val navController = findNavController() | |
val savedStateHolderId = navController.currentBackStackEntry!!.arguments!!.getInt(NavigationGateway.SAVED_STATE_HOLDER_ID, -1) | |
if(savedStateHolderId == -1){ | |
throw IllegalStateException("Argument ${NavigationGateway.SAVED_STATE_HOLDER_ID} does not exist") | |
} | |
val savedStateHolderEntry = navController.getBackStackEntry(savedStateHolderId) | |
val action = GatewayAction(navController, savedStateHolderEntry.savedStateHandle) | |
lifecycle.addObserver(action) | |
action | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment