Skip to content

Commit 6515060

Browse files
Merge pull request #1379 from square/sedwards/remove-compose-requirement
Remove Compose Dependency from Android Runtime
2 parents f1b9881 + bd09ff7 commit 6515060

File tree

11 files changed

+57
-137
lines changed

11 files changed

+57
-137
lines changed

.github/workflows/kotlin.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,10 @@ jobs:
179179
write-cache-key: main-build-artifacts
180180

181181
check:
182-
name: Check
182+
name: Unit Tests
183183
runs-on: macos-latest
184184
needs: build-all
185-
timeout-minutes: 20
185+
timeout-minutes: 40
186186
steps:
187187
- name: Checkout
188188
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.os.Bundle
66
import androidx.activity.viewModels
77
import androidx.appcompat.app.AppCompatActivity
88
import androidx.compose.material.MaterialTheme
9+
import androidx.compose.ui.platform.AndroidUiDispatcher
910
import androidx.lifecycle.SavedStateHandle
1011
import androidx.lifecycle.ViewModel
1112
import androidx.lifecycle.viewModelScope
@@ -21,6 +22,7 @@ import com.squareup.workflow1.ui.plus
2122
import com.squareup.workflow1.ui.withEnvironment
2223
import com.squareup.workflow1.ui.workflowContentView
2324
import kotlinx.coroutines.flow.StateFlow
25+
import kotlinx.coroutines.plus
2426

2527
private val viewEnvironment =
2628
(ViewEnvironment.EMPTY + ViewRegistry(HelloBinding))
@@ -47,7 +49,7 @@ class HelloBindingActivity : AppCompatActivity() {
4749
val renderings: StateFlow<Screen> by lazy {
4850
renderWorkflowIn(
4951
workflow = HelloWorkflow.mapRendering { it.withEnvironment(viewEnvironment) },
50-
scope = viewModelScope,
52+
scope = viewModelScope + AndroidUiDispatcher.Main,
5153
savedStateHandle = savedState,
5254
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
5355
)

samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflowActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package com.squareup.sample.compose.hellocomposeworkflow
55
import android.os.Bundle
66
import androidx.activity.viewModels
77
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.compose.ui.platform.AndroidUiDispatcher
89
import androidx.lifecycle.SavedStateHandle
910
import androidx.lifecycle.ViewModel
1011
import androidx.lifecycle.viewModelScope
@@ -18,6 +19,7 @@ import com.squareup.workflow1.ui.compose.withComposeInteropSupport
1819
import com.squareup.workflow1.ui.withEnvironment
1920
import com.squareup.workflow1.ui.workflowContentView
2021
import kotlinx.coroutines.flow.StateFlow
22+
import kotlinx.coroutines.plus
2123

2224
class HelloComposeWorkflowActivity : AppCompatActivity() {
2325
override fun onCreate(savedInstanceState: Bundle?) {
@@ -32,7 +34,7 @@ class HelloComposeWorkflowActivity : AppCompatActivity() {
3234
workflow = HelloWorkflow.mapRendering {
3335
it.withEnvironment(ViewEnvironment.EMPTY.withComposeInteropSupport())
3436
},
35-
scope = viewModelScope,
37+
scope = viewModelScope + AndroidUiDispatcher.Main,
3638
savedStateHandle = savedState,
3739
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
3840
)

samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package com.squareup.sample.compose.inlinerendering
55
import android.os.Bundle
66
import androidx.activity.viewModels
77
import androidx.appcompat.app.AppCompatActivity
8+
import androidx.compose.ui.platform.AndroidUiDispatcher
89
import androidx.lifecycle.SavedStateHandle
910
import androidx.lifecycle.ViewModel
1011
import androidx.lifecycle.viewModelScope
@@ -18,6 +19,7 @@ import com.squareup.workflow1.ui.compose.withComposeInteropSupport
1819
import com.squareup.workflow1.ui.withEnvironment
1920
import com.squareup.workflow1.ui.workflowContentView
2021
import kotlinx.coroutines.flow.StateFlow
22+
import kotlinx.coroutines.plus
2123

2224
/**
2325
* A workflow that returns an anonymous
@@ -37,7 +39,7 @@ class InlineRenderingActivity : AppCompatActivity() {
3739
workflow = InlineRenderingWorkflow.mapRendering {
3840
it.withEnvironment(ViewEnvironment.EMPTY.withComposeInteropSupport())
3941
},
40-
scope = viewModelScope,
42+
scope = viewModelScope + AndroidUiDispatcher.Main,
4143
savedStateHandle = savedState,
4244
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
4345
)

samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import androidx.activity.viewModels
77
import androidx.appcompat.app.AppCompatActivity
88
import androidx.compose.runtime.CompositionLocalProvider
99
import androidx.compose.ui.graphics.Color
10+
import androidx.compose.ui.platform.AndroidUiDispatcher
1011
import androidx.lifecycle.SavedStateHandle
1112
import androidx.lifecycle.ViewModel
1213
import androidx.lifecycle.viewModelScope
@@ -22,6 +23,7 @@ import com.squareup.workflow1.ui.plus
2223
import com.squareup.workflow1.ui.withEnvironment
2324
import com.squareup.workflow1.ui.workflowContentView
2425
import kotlinx.coroutines.flow.StateFlow
26+
import kotlinx.coroutines.plus
2527

2628
private val viewRegistry = ViewRegistry(RecursiveComposableFactory)
2729

@@ -45,7 +47,7 @@ class NestedRenderingsActivity : AppCompatActivity() {
4547
val renderings: StateFlow<Screen> by lazy {
4648
renderWorkflowIn(
4749
workflow = RecursiveWorkflow.mapRendering { it.withEnvironment(viewEnvironment) },
48-
scope = viewModelScope,
50+
scope = viewModelScope + AndroidUiDispatcher.Main,
4951
savedStateHandle = savedState,
5052
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
5153
)

workflow-runtime-android/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ dependencies {
1616
val composeBom = platform(libs.androidx.compose.bom)
1717

1818
api(project(":workflow-runtime"))
19-
api(libs.androidx.compose.ui.android)
2019
api(libs.androidx.lifecycle.viewmodel.savedstate)
2120

22-
implementation(composeBom)
2321
implementation(project(":workflow-core"))
2422

23+
androidTestImplementation(libs.androidx.compose.ui.android)
24+
androidTestImplementation(composeBom)
2525
androidTestImplementation(libs.androidx.activity.ktx)
2626
androidTestImplementation(libs.androidx.lifecycle.viewmodel.ktx)
2727
androidTestImplementation(libs.androidx.test.core)

workflow-runtime-android/dependencies/releaseRuntimeClasspath.txt

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,24 @@
1-
androidx.activity:activity-ktx:1.7.0
2-
androidx.activity:activity:1.7.0
3-
androidx.annotation:annotation-experimental:1.4.1
41
androidx.annotation:annotation-jvm:1.8.1
52
androidx.annotation:annotation:1.8.1
63
androidx.arch.core:core-common:2.2.0
74
androidx.arch.core:core-runtime:2.2.0
8-
androidx.autofill:autofill:1.0.0
9-
androidx.collection:collection-jvm:1.4.4
10-
androidx.collection:collection-ktx:1.4.4
11-
androidx.collection:collection:1.4.4
12-
androidx.compose.runtime:runtime-android:1.7.2
13-
androidx.compose.runtime:runtime-saveable-android:1.7.2
14-
androidx.compose.runtime:runtime-saveable:1.7.2
15-
androidx.compose.runtime:runtime:1.7.2
16-
androidx.compose.ui:ui-android:1.7.2
17-
androidx.compose.ui:ui-geometry-android:1.7.2
18-
androidx.compose.ui:ui-geometry:1.7.2
19-
androidx.compose.ui:ui-graphics-android:1.7.2
20-
androidx.compose.ui:ui-graphics:1.7.2
21-
androidx.compose.ui:ui-text-android:1.7.2
22-
androidx.compose.ui:ui-text:1.7.2
23-
androidx.compose.ui:ui-unit-android:1.7.2
24-
androidx.compose.ui:ui-unit:1.7.2
25-
androidx.compose.ui:ui-util-android:1.7.2
26-
androidx.compose.ui:ui-util:1.7.2
27-
androidx.compose:compose-bom:2024.09.02
5+
androidx.collection:collection:1.0.0
286
androidx.concurrent:concurrent-futures:1.1.0
29-
androidx.core:core-ktx:1.12.0
30-
androidx.core:core:1.12.0
31-
androidx.customview:customview-poolingcontainer:1.0.0
32-
androidx.emoji2:emoji2:1.2.0
33-
androidx.graphics:graphics-path:1.0.1
34-
androidx.interpolator:interpolator:1.0.0
7+
androidx.core:core-ktx:1.2.0
8+
androidx.core:core:1.2.0
359
androidx.lifecycle:lifecycle-common-jvm:2.8.7
3610
androidx.lifecycle:lifecycle-common:2.8.7
3711
androidx.lifecycle:lifecycle-livedata-core:2.8.7
38-
androidx.lifecycle:lifecycle-process:2.8.7
3912
androidx.lifecycle:lifecycle-runtime-android:2.8.7
40-
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
41-
androidx.lifecycle:lifecycle-runtime-compose:2.8.7
42-
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
43-
androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
4413
androidx.lifecycle:lifecycle-runtime:2.8.7
4514
androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
46-
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
4715
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
4816
androidx.lifecycle:lifecycle-viewmodel:2.8.7
4917
androidx.profileinstaller:profileinstaller:1.3.1
50-
androidx.savedstate:savedstate-ktx:1.2.1
5118
androidx.savedstate:savedstate:1.2.1
5219
androidx.startup:startup-runtime:1.1.1
5320
androidx.tracing:tracing:1.0.0
54-
androidx.versionedparcelable:versionedparcelable:1.1.1
21+
androidx.versionedparcelable:versionedparcelable:1.1.0
5522
com.google.guava:listenablefuture:1.0
5623
com.squareup.okio:okio-jvm:3.3.0
5724
com.squareup.okio:okio:3.3.0

workflow-runtime-android/src/main/java/com/squareup/workflow1/android/AndroidRenderWorkflow.kt

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,20 @@
33
package com.squareup.workflow1.android
44

55
import androidx.annotation.VisibleForTesting
6-
import androidx.compose.ui.platform.AndroidUiDispatcher
76
import androidx.lifecycle.SavedStateHandle
87
import com.squareup.workflow1.RuntimeConfig
98
import com.squareup.workflow1.RuntimeConfigOptions
109
import com.squareup.workflow1.Workflow
1110
import com.squareup.workflow1.WorkflowInterceptor
1211
import com.squareup.workflow1.WorkflowTracer
1312
import com.squareup.workflow1.renderWorkflowIn
14-
import kotlinx.coroutines.CoroutineDispatcher
1513
import kotlinx.coroutines.CoroutineScope
1614
import kotlinx.coroutines.flow.MutableStateFlow
1715
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
1816
import kotlinx.coroutines.flow.StateFlow
1917
import kotlinx.coroutines.flow.map
2018
import kotlinx.coroutines.flow.onEach
2119
import kotlinx.coroutines.flow.stateIn
22-
import kotlinx.coroutines.plus
2320

2421
/**
2522
* An Android `ViewModel`-friendly wrapper for [com.squareup.workflow1.renderWorkflowIn],
@@ -58,10 +55,12 @@ import kotlinx.coroutines.plus
5855
* in any workflows, after the initial render pass, will be handled by this scope, and cancelling
5956
* this scope will cancel the workflow runtime and any running workers. Note that any dispatcher
6057
* in this scope will _not_ be used to execute the very first render pass (which happens
61-
* synchronously).
62-
* Also note that if there is no [CoroutineDispatcher] in this scope then Compose UI's
63-
* [AndroidUiDispatcher.Main] will be used as this is the most performant Android UI dispatcher
64-
* for Workflow.
58+
* synchronously). We recommend using a `CoroutineDispatcher` in this scope's `CoroutineContext`
59+
* that will ensure that all dispatched coroutines are run before the next Choreographer frame.
60+
* Compose UI's `AndroidUiDispatcher.Main` provides this behavior in a performant way!
61+
* Another way to achieve that is to use an immediate (or Unconfined) dispatcher like
62+
* Dispatchers.Main.immediate. However, that dispatcher cannot take advantage of some runtime
63+
* optimizations. E.G., see [RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS].
6564
*
6665
* @param savedStateHandle
6766
* Used to restore workflow state in a new process. Typically this is the
@@ -141,10 +140,12 @@ public fun <OutputT, RenderingT> renderWorkflowIn(
141140
* in any workflows, after the initial render pass, will be handled by this scope, and cancelling
142141
* this scope will cancel the workflow runtime and any running workers. Note that any dispatcher
143142
* in this scope will _not_ be used to execute the very first render pass (which happens
144-
* synchronously).
145-
* Also note that if there is no [CoroutineDispatcher] in this scope then Compose UI's
146-
* [AndroidUiDispatcher.Main] will be used as this is the most performant Android UI dispatcher
147-
* for Workflow.
143+
* synchronously). We recommend using a `CoroutineDispatcher` in this scope's `CoroutineContext`
144+
* that will ensure that all dispatched coroutines are run before the next Choreographer frame.
145+
* Compose UI's `AndroidUiDispatcher.Main` provides this behavior in a performant way!
146+
* Another way to achieve that is to use an immediate (or Unconfined) dispatcher like
147+
* Dispatchers.Main.immediate. However, that dispatcher cannot take advantage of some runtime
148+
* optimizations. E.G., see [RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS].
148149
*
149150
* @param prop
150151
* Specifies the sole [PropsT] value to use to render the root workflow. To allow updates,
@@ -243,10 +244,12 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
243244
* in any workflows, after the initial render pass, will be handled by this scope, and cancelling
244245
* this scope will cancel the workflow runtime and any running workers. Note that any dispatcher
245246
* in this scope will _not_ be used to execute the very first render pass (which happens
246-
* synchronously).
247-
* Also note that if there is no [CoroutineDispatcher] in this scope then Compose UI's
248-
* [AndroidUiDispatcher.Main] will be used as this is the most performant Android UI dispatcher
249-
* for Workflow.
247+
* synchronously). We recommend using a `CoroutineDispatcher` in this scope's `CoroutineContext`
248+
* that will ensure that all dispatched coroutines are run before the next Choreographer frame.
249+
* Compose UI's `AndroidUiDispatcher.Main` provides this behavior in a performant way!
250+
* Another way to achieve that is to use an immediate (or Unconfined) dispatcher like
251+
* Dispatchers.Main.immediate. However, that dispatcher cannot take advantage of some runtime
252+
* optimizations. E.G., see [RuntimeConfigOptions.DRAIN_EXCLUSIVE_ACTIONS].
250253
*
251254
* @param props
252255
* Specifies the initial [PropsT] to use to render the root workflow, and will cause a re-render
@@ -278,7 +281,6 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
278281
* A [StateFlow] of [RenderingT]s that will emit any time the root workflow creates a new
279282
* rendering.
280283
*/
281-
@OptIn(ExperimentalStdlibApi::class)
282284
public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
283285
workflow: Workflow<PropsT, OutputT, RenderingT>,
284286
scope: CoroutineScope,
@@ -291,16 +293,9 @@ public fun <PropsT, OutputT, RenderingT> renderWorkflowIn(
291293
): StateFlow<RenderingT> {
292294
val restoredSnap = savedStateHandle?.get<PickledTreesnapshot>(KEY)?.snapshot
293295

294-
// Add in Compose's AndroidUiDispatcher.Main by default if none is specified.
295-
val updatedContext = if (scope.coroutineContext[CoroutineDispatcher.Key] == null) {
296-
scope.coroutineContext + AndroidUiDispatcher.Main
297-
} else {
298-
scope.coroutineContext
299-
}
300-
301296
val renderingsAndSnapshots = renderWorkflowIn(
302297
workflow,
303-
scope + updatedContext,
298+
scope,
304299
props,
305300
restoredSnap,
306301
interceptors,

workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/RenderWorkflow.kt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,22 @@ import kotlinx.coroutines.plus
5151
* available actions and do another render pass.
5252
* 1. Pass the updated rendering into the [StateFlow] returned from this method.
5353
*
54-
* Note that if this is run on the main thread we must `suspend` in order to release the thread and
55-
* let any actions that can be processed queue up. How that happens will depend on the
56-
* [CoroutineDispatcher] used in [scope].
54+
* When there is UI involved, we recommend using a `CoroutineDispatcher` in [scope]'s
55+
* `CoroutineContext` that runs the above runtime loop until it is stable before the next 'frame',
56+
* whatever frame means on your platform. Specifically, ensure that all dispatched coroutines are
57+
* run before the next 'frame', so that the Workflow runtime has done all the work it can.
5758
*
58-
* If an "immediate" dispatcher is used, then after 1 only 1 action will ever be available since
59-
* as soon as it is available it will resume and start processing the rest of the loop immediately.
59+
* One way to achieve that guarantee is with an "immediate" dispatcher on the main thread - like,
60+
* `Dispatchers.Main.immediate` - since it will continue to run until the runtime is stable before
61+
* it lets any frame get updated by the main thread.
62+
* However, if an "immediate" dispatcher is used, then only 1 action will ever be available
63+
* since as soon as it is available it will resume (step #1 above) and start processing the rest of
64+
* the loop immediately.
65+
* This means that [DRAIN_EXCLUSIVE_ACTIONS] and [CONFLATE_STALE_RENDERINGS] will have no effect.
6066
*
61-
* There is no need to try the [DRAIN_EXCLUSIVE_ACTIONS] loop after each render pass in
62-
* [CONFLATE_STALE_RENDERINGS] because they all happen synchronously so no new exclusive actions
63-
* could have been queued.
67+
* A preferred way to achieve that is to have your dispatcher drain coroutines for each frame
68+
* explicitly. On Android, for example, that can be done with Compose UI's
69+
* `AndroidUiDispatcher.Main`.
6470
*
6571
* ## Scoping
6672
*

workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,33 @@
1-
androidx.activity:activity-ktx:1.8.2
21
androidx.activity:activity:1.8.2
3-
androidx.annotation:annotation-experimental:1.4.1
2+
androidx.annotation:annotation-experimental:1.4.0
43
androidx.annotation:annotation-jvm:1.8.1
54
androidx.annotation:annotation:1.8.1
65
androidx.arch.core:core-common:2.2.0
76
androidx.arch.core:core-runtime:2.2.0
8-
androidx.autofill:autofill:1.0.0
9-
androidx.collection:collection-jvm:1.4.4
10-
androidx.collection:collection-ktx:1.4.4
11-
androidx.collection:collection:1.4.4
12-
androidx.compose.runtime:runtime-android:1.7.2
13-
androidx.compose.runtime:runtime-saveable-android:1.7.2
14-
androidx.compose.runtime:runtime-saveable:1.7.2
15-
androidx.compose.runtime:runtime:1.7.2
16-
androidx.compose.ui:ui-android:1.7.2
17-
androidx.compose.ui:ui-geometry-android:1.7.2
18-
androidx.compose.ui:ui-geometry:1.7.2
19-
androidx.compose.ui:ui-graphics-android:1.7.2
20-
androidx.compose.ui:ui-graphics:1.7.2
21-
androidx.compose.ui:ui-text-android:1.7.2
22-
androidx.compose.ui:ui-text:1.7.2
23-
androidx.compose.ui:ui-unit-android:1.7.2
24-
androidx.compose.ui:ui-unit:1.7.2
25-
androidx.compose.ui:ui-util-android:1.7.2
26-
androidx.compose.ui:ui-util:1.7.2
27-
androidx.compose:compose-bom:2024.09.02
7+
androidx.collection:collection:1.1.0
288
androidx.concurrent:concurrent-futures:1.1.0
299
androidx.core:core-ktx:1.13.1
3010
androidx.core:core:1.13.1
31-
androidx.customview:customview-poolingcontainer:1.0.0
3211
androidx.documentfile:documentfile:1.0.0
3312
androidx.dynamicanimation:dynamicanimation:1.0.0
34-
androidx.emoji2:emoji2:1.2.0
35-
androidx.graphics:graphics-path:1.0.1
3613
androidx.interpolator:interpolator:1.0.0
3714
androidx.legacy:legacy-support-core-utils:1.0.0
3815
androidx.lifecycle:lifecycle-common-jvm:2.8.7
3916
androidx.lifecycle:lifecycle-common:2.8.7
4017
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.7
4118
androidx.lifecycle:lifecycle-livedata-core:2.8.7
4219
androidx.lifecycle:lifecycle-livedata:2.8.7
43-
androidx.lifecycle:lifecycle-process:2.8.7
4420
androidx.lifecycle:lifecycle-runtime-android:2.8.7
45-
androidx.lifecycle:lifecycle-runtime-compose-android:2.8.7
46-
androidx.lifecycle:lifecycle-runtime-compose:2.8.7
4721
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.7
4822
androidx.lifecycle:lifecycle-runtime-ktx:2.8.7
4923
androidx.lifecycle:lifecycle-runtime:2.8.7
5024
androidx.lifecycle:lifecycle-viewmodel-android:2.8.7
51-
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7
5225
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.7
5326
androidx.lifecycle:lifecycle-viewmodel:2.8.7
5427
androidx.loader:loader:1.0.0
5528
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
5629
androidx.print:print:1.0.0
5730
androidx.profileinstaller:profileinstaller:1.3.1
58-
androidx.savedstate:savedstate-ktx:1.2.1
5931
androidx.savedstate:savedstate:1.2.1
6032
androidx.startup:startup-runtime:1.1.1
6133
androidx.tracing:tracing:1.0.0

0 commit comments

Comments
 (0)