Documentation

$cache

A process-wide key-value store, for the times a screen already has the data the next screen is about to fetch.

Reading and writing

Assign to a key, read it back with a type:

$cache('shows') = shows.associateBy { it.id }

val cached: Map<String, Show>? = $cache('shows')

Reads are nullable, because the cache is memory that the process can lose. Treat a miss as normal and fall back to loading.

$cache.remove('shows')
$cache.clear()

The pattern it exists for

A list screen fetches shows. Tapping one opens a detail screen that needs the same show. The detail loader can start from what the list already has, then fill in the rest:

// list +screen.load.wh
suspend fun load(): List<Show> {
  val shows: List<Show> = $fetch.get(url = "…/shows")
  $cache('shows') = shows.associateBy { it.id }
  return shows
}
// detail +screen.load.wh
suspend fun load(): ShowDetail {
  val cached: Map<String, Show>? = $cache('shows')
  val preview = cached?.get($screen.params.id)
  return ShowDetail(preview, $fetch.get(url = "…/shows/${$screen.params.id}"))
}
$cache is memory only. It does not survive the process being killed, and it is not a place to keep anything the app needs on next launch. For that, use $fetch's disk cache, or write it somewhere yourself with $appContext.

The other three ways to move data between screens

$cache is the loosest of four, and usually not the first one to reach for:

MechanismScopeReach for it when
Navigation stateOne navigationThe next screen needs a title and image immediately, so it can render before its fetch lands
Route cachePer route + paramsAutomatic. Revisiting a screen is instant until $invalidate()
@storeWhole appThe data is a real concern with its own logic — a session, a cart
$cacheWhole appPlain reuse, no logic, and losing it is harmless

Navigation state, for comparison

Handing a couple of fields forward is often all you need, and it does not need a cache at all:

$navigate("/show/${id}", state = { name: show.name, poster: show.poster })

// in the destination's loader
val previewName = $route.state?.name

$defer

The related trick for making a screen appear sooner: mark the slow half of a loader deferred and it does not block the render.

suspend fun load(): ShowData {
  val show = $fetch.get(url = "…")                            // blocks
  val episodes = $defer { $fetch.get(url = "…/episodes") }   // does not
  return ShowData(show, episodes)
}

The screen renders with episodes null and recomposes when it arrives. See $data / $layoutData.

See Also