Skip to content

CircuitX

CircuitX is a suite of extension artifacts for Circuit. These artifacts are intended to be batteries-included implementations of common use cases, such as out-of-the-box Overlay types or Android navigation interop.

These packages differ from Circuit’s core artifacts in a few ways:

  • Their APIs may change more frequently during Circuit’s development.
  • These artifacts won’t ship with their own baseline profiles.
  • These artifacts are under the com.slack.circuitx package prefix.
  • These artifacts may be platform-specific where appropriate.

Android

The circuitx-android artifact contains Android-specific extensions for Circuit.

dependencies {
  implementation("com.slack.circuit:circuitx-android:<version>")
}

It can be important for Circuit to be able to navigate to Android targets, such as other activities or custom tabs. To support this, decorate your existing Navigator instance with rememberAndroidScreenAwareNavigator().

class MainActivity : Activity {
  override fun onCreate(savedInstanceState: Bundle?) {
    setContent {
      val backStack = rememberSaveableBackStack(root = HomeScreen)
      val navigator = rememberAndroidScreenAwareNavigator(
        rememberCircuitNavigator(backstack), // Decorated navigator
        this@MainActivity
      )
      CircuitCompositionLocals(circuit) {
        NavigableCircuitContent(navigator, backstack)
      }
    }
  }
}

rememberAndroidScreenAwareNavigator() has two overloads - one that accepts a Context and one that accepts an AndroidScreenStarter. The former is just a shorthand for the latter that only supports IntentScreen. You can also implement your own starter that supports other screen types.

AndroidScreen is the base Screen type that this navigator and AndroidScreenStarter interact with. There is a built-in IntentScreen implementation that wraps an Intent and an options Bundle to pass to startActivity(). Custom AndroidScreens can be implemented separately and route through here, but you should be sure to implement your own AndroidScreenStarter to handle them accordingly.

Effects

CircuitX provides some effects for use with logging/analytics. These effects are typically used in Circuit presenters for tracking impressions and will run only once until forgotten based on the current circuit-retained strategy.

dependencies {
  implementation("com.slack.circuit:circuitx-effects:<version>")
}

ImpressionEffect

ImpressionEffect is a simple single fire side effect useful for logging or analytics. This impression will run only once until it is forgotten based on the current RetainedStateRegistry.

ImpressionEffect {
  // Impression 
}

LaunchedImpressionEffect

This is useful for async single fire side effects like logging or analytics. This effect will run a suspendable impression once until it is forgotten based on the RetainedStateRegistry.

LaunchedImpressionEffect {
  // Impression 
}

RememberImpressionNavigator

A LaunchedImpressionEffect that is useful for async single fire side effects like logging or analytics that need to be navigation aware. This will run the impression again if it re-enters the composition after a navigation event.

val navigator = rememberImpressionNavigator(
  navigator = Navigator()
) {
  // Impression
}

Gesture Navigation

CircuitX provides NavDecoration implementation which support navigation through appropriate gestures on certain platforms.

dependencies {
  implementation("com.slack.circuit:circuitx-gesture-navigation:<version>")
}

To enable gesture navigation support, you can use the use the GestureNavigationDecorationfunction:

NavigableCircuitContent(
  navigator = navigator,
  backStack = backstack,
  decoration = GestureNavigationDecoration(
    // Pop the back stack once the user has gone 'back'
    navigator::pop
  )
)

Android

On Android, this supports the Predictive back gesture which is available on Android 14 and later (API level 34+). On older platforms, Circuit’s default NavDecoration decoration is used instead.

Star sample running on an Android 14 device

iOS

On iOS, this simulates iOS’s ‘Interactive Pop Gesture’ in Compose UI, allowing the user to swipe Circuit UIs away. As this is a simulation of the native behavior, it does not match the native functionality perfectly. However, it is a good approximation.

Tivi app running on iPhone

Other platforms

On other platforms we defer to Circuit’s default NavDecoration decoration.

Overlays

CircuitX provides a few out-of-the-box Overlay implementations that you can use to build common UIs.

dependencies {
  implementation("com.slack.circuit:circuitx-overlays:<version>")
}

BottomSheetOverlay

BottomSheetOverlay is an overlay that shows a bottom sheet with a strongly-typed API for the input model to the sheet content and result type. This allows you to easily use a bottom sheet to prompt for user input and suspend the underlying Circuit content until that result is returned.

/** A hypothetical bottom sheet of available actions when long-pressing a list item. */
suspend fun OverlayHost.showLongPressActionsSheet(): Action {
  return show(
    BottomSheetOverlay(
      model = listOfActions()
    ) { actions, overlayNavigator ->
      ActionsSheet(
        actions,
        overlayNavigator::finish // Finish the overlay with the clicked Action
      )
    }
  )
}

@Composable
fun ActionsSheet(actions: List<Action>, onActionClicked: (Action) -> Unit) {
  Column {
    actions.forEach { action ->
      TextButton(onClick = { onActionClicked(action) }) {
        Text(action.title)
      }
    }
  }
}

Dialog Overlays

alertDialogOverlay is function that returns an Overlay that shows a simple confirmation dialog with configurable inputs. This acts more or less as an Overlay shim over the Material 3 AlertDialog API.

/** A hypothetical confirmation dialog. */
suspend fun OverlayHost.showConfirmationDialog(): Action {
  return show(
    alertDialogOverlay(
      titleText = { Text("Are you sure?") },
      confirmButton = { onClick -> Button(onClick = onClick) { Text("Yes") } },
      dismissButton = { onClick -> Button(onClick = onClick) { Text("No") } },
    )
  )
}

There are also more generic BasicAlertDialog and BasicDialog implementations that allow more customization.

FullScreenOverlay

Sometimes it’s useful to have a full-screen overlay that can be used to show a screen in full above the current content. This API is fairly simple to use and just takes a Screen input of what content you want to show in the overlay.

overlayHost.showFullScreenOverlay(
  ImageViewerScreen(id = url, url = url, placeholderKey = name)
)

When to use FullScreenOverlay vs navigating to a Screen?

While they achieve similar results, the key difference is that FullScreenOverlay is inherently an ephemeral UI that is controlled by an underlying primary UI. It cannot navigate elsewhere and it does not participate in the backstack.