Awesome
DEPRECATED
This project is now deprecated. No more development will be taking place here.
The successor of this project is <img src="https://user-images.githubusercontent.com/238198/177164121-3aa4d19d-7714-4f2e-af12-7d3335b43f9c.png" width="25" /> Appyx. Check it out!
compose-router
What's this?
Routing functionality for Jetpack Compose with back stack:
- Helps to map your whole app structure using Compose — not just the UI parts
- Supports a single-Activity approach — no Fragments, no Navigation component needed
- Simply branch on current routing and compose any other @Composable
- Back stack saves the history of routing
- Can be integrated with automatic back press handling to go back in screen history
- Can be integrated with automatic scoped
savedInstanceState
persistence - Supports routing based on deep links (POC impl)
Compatible with Compose version 1.0
Sample apps
-
Sample module #1 - app-lifelike — Displays a registration flow + logged in content with back stack
-
Sample module #2 - app-nested-containers — Displays nested screen history on generated levels.
-
Jetnews - fork — Built with
compose-router
, adding proper screen history functionality. -
Pokedex - compose-pokedex — Using
compose-router
for app structure.
Download
Available through jitpack.
Add the maven repo to your root build.gradle
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
Add the dependency:
implementation 'com.github.zsoltk:compose-router:{latest-version}'
How to use
On any level where routing functionality is needed, create a sealed class to represent your routing:
sealed class Routing {
object AlbumList : Routing()
data class PhotosOfAlbum(val album: Album) : Routing()
data class FullScreenPhoto(val photo: Photo) : Routing()
}
Use the Router
Composable and enjoy back stack functionality:
@Composable
fun GalleryView(defaultRouting: Routing) {
Router("GalleryView", defaultRouting) { backStack ->
// compose further based on current routing:
when (val routing = backStack.last()) {
is Routing.AlbumList -> AlbumList.Content(
onAlbumSelected = {
// add a new routing to the back stack:
backStack.push(Routing.PhotosOfAlbum(it))
})
is Routing.PhotosOfAlbum -> PhotosOfAlbum.Content(
album = routing.album,
onPhotoSelected = {
// add a new routing to the back stack:
backStack.push(Routing.FullScreenPhoto(it))
})
is Routing.FullScreenPhoto -> FullScreenPhoto.Content(
photo = routing.photo
)
}
}
}
For more usage examples see the example apps.
To go back in the back stack, you can either call the .pop()
method programmatically, or just press the back button on the device (see next section for back press integration).
Back stack operations:
- push()
- pushAndDropNested()
- pop()
- replace()
- newRoot()
Connect it to back press event
To ensure that back press automatically pops the back stack and restores history, add this to your Activity:
class MainActivity : AppCompatActivity() {
private val backPressHandler = BackPressHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Providers(
LocalBackPressHandler provides backPressHandler
) {
// Your root composable goes here
}
}
}
override fun onBackPressed() {
if (!backPressHandler.handle()) {
super.onBackPressed()
}
}
}
Connect it to savedInstanceState
Router can automatically add scoped Bundle support for your client code.
Minimal setup:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
BundleScope(savedInstanceState) {
// Your root composable goes here
}
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.saveLocal()
}
}
In client code you can now use:
@Composable
fun Content() {
var counter by persistentInt("counter", 0)
Clickable(onClick = { counter++ }) {
Text("Counter value saved/restored from bundle: $counter")
}
}
Routing from deep links
Note: this is even more of a proof-of-concept only implementation than the other parts.
Example 1
Build and install app-lifelike
on your device.
Open a console and type:
adb shell 'am start -a "android.intent.action.VIEW" -d "app-lifelike://go-to-profile?name=fake&phone=123123"'
This will open app-lifelike
with skipped registration flow and go directly to Profile screen with fake user:
Example 2
Build and install app-nested-containers
on your device.
Open a console and type:
adb shell 'am start -a "android.intent.action.VIEW" -d "app-nested://default/BGR"'
This will open app-nested-containers
with (B)lue / (G)reen / (R)ed subtrees pre-selected as routing:
See MainActivity.kt
, AndroidManifest.xml
, and DeepLink.kt
in both sample apps to see usage example.