Skip to content

Instantly share code, notes, and snippets.

@Andrew0000
Last active December 21, 2023 20:19
Show Gist options
  • Select an option

  • Save Andrew0000/a99124c0f06f73ebd02c2515752c4f72 to your computer and use it in GitHub Desktop.

Select an option

Save Andrew0000/a99124c0f06f73ebd02c2515752c4f72 to your computer and use it in GitHub Desktop.
NavigationMediator + NavigationUpdater
import androidx.annotation.MainThread
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import timber.log.Timber
class NavigationMediator {
val startScreen = AppScreen.MAP
private var navRequestCount = 0
private val _requestedScreen = MutableSharedFlow<NavRequest>()
val requestedScreen: Flow<NavRequest?> = _requestedScreen
// Compose Navigation doesn't allow to pass arguments beyond query parameters
// which is not very convenient.
// Here is a possible workaround - our own arguments storage.
// TODO think about clearing or use another navigation library
private val requestedScreenArgs = mutableMapOf<AppScreen, Map<String, Any>>()
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
fun requestScreen(screen: AppScreen, args: Map<String, Any>? = null) {
scope.launch {
args?.let {
requestedScreenArgs[screen] = it
}
Timber.d("[Navigation] request screen: $screen")
_requestedScreen.emit(screen.toNavRequest())
}
}
@MainThread
fun getArgs(screen: AppScreen): Map<String, Any>? =
requestedScreenArgs[screen]
private fun AppScreen.toNavRequest() =
NavRequest(this, uid = navRequestCount++)
}
data class NavRequest(
val target: AppScreen,
val uid: Int,
)
object NavArg {
const val MAP_POINT = "map_point"
const val NEW_LAT_LNG = "new_lat_lng"
}
enum class AppScreen(val id: String) {
MAP("map"),
LIST("list"),
DETAILS("details"),
}
@Composable
private fun RowScope.NavItem(
target: AppScreen,
@DrawableRes imageId: Int,
navController : NavHostController,
) {
val currentBackStack = navController.currentBackStack.collectAsState()
val currentBackStackLast = currentBackStack.value.lastOrNull()
NavigationBarItem(
selected = currentBackStackLast?.destination?.route == target.id,
icon = {
Icon(
painterResource(imageId),
contentDescription = null,
)
},
onClick = {
navigationMediator.requestScreen(target)
}
)
}
@Composable
private fun AppNavigationHost(navController: NavHostController) {
NavHost(
modifier = Modifier
.fillMaxSize()
.padding(bottom = Dimensions.navBarHeight),
navController = navController,
startDestination = navigationMediator.startScreen.id,
) {
composable(
AppScreen.MAP.id,
enterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) },
popExitTransition = { slideOutHorizontally(targetOffsetX = { -it }) },
) { MapLayout() }
composable(
AppScreen.LIST.id,
enterTransition = { slideInHorizontally(initialOffsetX = { it }) },
exitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
popEnterTransition = { slideInHorizontally(initialOffsetX = { it }) },
popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) },
) { ListLayout() }
composable(
AppScreen.DETAILS.id,
enterTransition = { slideInVertically(initialOffsetY = { it }) },
exitTransition = { slideOutVertically(targetOffsetY = { it }) },
popEnterTransition = { slideInVertically(initialOffsetY = { it }) },
popExitTransition = { slideOutVertically(targetOffsetY = { it }) },
) {
val args = navigationMediator.getArgs(AppScreen.DETAILS)
DetailsLayout(
newLatLng = args?.get(NavArg.NEW_LAT_LNG) as? LatLng,
existingPoint = args?.get(NavArg.MAP_POINT) as? MapPoint,
)
}
}
}
@Composable
private fun NavigationUpdater(
navController: NavHostController,
) {
val requestedScreen = navigationMediator.requestedScreen.collectAsState(null)
val requestedScreenValue = requestedScreen.value ?: return
LaunchedEffect(requestedScreenValue) {
Timber.d("[Navigation] requestedScreen: $requestedScreenValue")
val currentRoute = navController.currentDestination?.route
val target = requestedScreenValue.target
if (currentRoute != target.id) {
if (target.needPopBack()) {
navController.popBackStack()
}
navController.navigate(target.id)
}
}
}
private fun AppScreen.needPopBack() =
this != AppScreen.DETAILS
private fun String?.isRootRoute() =
this == AppScreen.MAP.id || this == AppScreen.LIST.id
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment