Home

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

Build Version License

logo

What's this?

Routing functionality for Jetpack Compose with back stack:

Compatible with Compose version 1.0

Sample apps

  1. Sample module #1 - app-lifelike — Displays a registration flow + logged in content with back stack

  2. Sample module #2 - app-nested-containers — Displays nested screen history on generated levels.

  3. Jetnews - fork — Built with compose-router, adding proper screen history functionality.

  4. 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:

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.