Skip to content

Instantly share code, notes, and snippets.

@lelandrichardson
Created November 15, 2019 17:26
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lelandrichardson/5b70430ea383600eb0dba5a62172148c to your computer and use it in GitHub Desktop.
Save lelandrichardson/5b70430ea383600eb0dba5a62172148c to your computer and use it in GitHub Desktop.
Compose Navigation
import androidx.compose.Ambient
import androidx.navigation.NavController
object Ambients {
val Navigator = Ambient.of<ComposableNavigator>()
val NavController = Ambient.of<NavController>()
}
import androidx.compose.ambient
import androidx.compose.effectOf
import com.example.reddit.Ambients
fun <T> navArg(name: String) = effectOf<T> {
val nav = +ambient(Ambients.NavController)
val entry = nav.getBackStackEntry(nav.currentDestination!!.id)
val args = entry.arguments
val arg = args?.get(name) ?: error("No argument found with name $name")
@Suppress("UNCHECKED_CAST")
arg as T
}
fun <T> optionalNavArg(name: String) = effectOf<T?> {
val nav = +ambient(Ambients.NavController)
val entry = nav.getBackStackEntry(nav.currentDestination!!.id)
val args = entry.arguments
val arg = args?.get(name)
@Suppress("UNCHECKED_CAST")
arg as? T
}
import android.os.Bundle
import androidx.compose.Composable
import androidx.compose.FrameManager
import androidx.compose.Model
import androidx.navigation.*
import java.util.*
private typealias ComposableUnitLambda = @Composable () -> Unit
private val EmptyRoute: ComposableUnitLambda = {}
@Model
class ContentHolder(var value: ComposableUnitLambda = EmptyRoute)
@Navigator.Name("compose")
class ComposableNavigator : Navigator<Destination>() {
private val stack = Stack<ComposableUnitLambda>()
private var content = ContentHolder()
val current: ComposableUnitLambda
get() = content.value
override fun createDestination(): Destination {
return Destination(this)
}
override fun popBackStack(): Boolean {
if (stack.empty()) {
return false
}
content.value = stack.pop()
return true
}
override fun navigate(
destination: Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Extras?
): NavDestination? {
FrameManager.ensureStarted()
if (content.value !== EmptyRoute) {
stack.push(content.value)
}
content.value = destination.content
return destination
}
}
@NavDestination.ClassType(Destination::class)
class Destination(navigator: ComposableNavigator) : NavDestination(navigator) {
var content: ComposableUnitLambda = EmptyRoute
}
fun NavGraphBuilder.route(
id: Int,
content: @Composable () -> Unit) {
addDestination(
DestinationBuilder(provider[ComposableNavigator::class], id, content)
.build())
}
fun NavGraphBuilder.route(
id: Int,
content: @Composable () -> Unit,
builder: DestinationBuilder.() -> Unit) {
addDestination(
DestinationBuilder(provider[ComposableNavigator::class], id, content)
.apply(builder)
.build()
)
}
class DestinationBuilder(
navigator: ComposableNavigator,
id: Int,
val content: @Composable () -> Unit)
: NavDestinationBuilder<Destination>(navigator, id) {
override fun build(): Destination {
return super.build().also { it.content = content }
}
}
import android.app.Activity
import android.os.Bundle
import android.util.Log
import androidx.compose.Composable
import androidx.compose.disposeComposition
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.createGraph
import androidx.navigation.plusAssign
import androidx.ui.core.setContent
import com.example.reddit.Ambients
abstract class ComposeActivity : Activity() {
@Composable
abstract fun content(content: @Composable () -> Unit)
abstract fun NavGraphBuilder.graph()
abstract val initialRoute: Int
private val graphId = 100
private var navController: NavController? = null
private val navigator = ComposableNavigator()
override fun onBackPressed() {
if (navController?.popBackStack() == true) {
Log.v("Navigation", "Successfully navigated back")
} else {
super.onBackPressed()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val controller = NavController(this).also {
it.navigatorProvider += navigator
it.graph = it.createGraph(graphId, initialRoute) {
graph()
}
}
navController = controller
setContent {
Ambients.NavController.Provider(controller) {
Ambients.Navigator.Provider(navigator) {
content(navigator.current)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
navController = null
disposeComposition()
}
}
// example usage
class MainActivity : ComposeActivity() {
override val initialRoute = R.id.screen_one
override fun NavGraphBuilder.graph() {
route(R.id.screen_one) {
val arg1 = +optionalNavArg<String>("someArg")
ScreenOne(arg1)
}
route(R.id.screen_two) {
val id = +navArg<String>("id")
ScreenTwo(id)
}
route(R.id.screen_three) {
ScreenThree()
}
}
@Composable
override fun content(content: @Composable () -> Unit) {
AppTheme {
Scaffold {
content()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment