Cleanup
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
index 9d2e1eb..d231f59 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
@@ -2,10 +2,12 @@
import androidx.compose.animation.core.AnimationConstants
import androidx.compose.foundation.MutatorMutex
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import com.airbnb.lottie.LottieComposition
@@ -17,14 +19,42 @@
import kotlin.coroutines.coroutineContext
/**
+ * Use this to create a [LottieAnimatable] in a composable.
+ *
+ * @see LottieAnimatable
+ */
+@Composable
+fun rememberLottieAnimatable(): LottieAnimatable = remember { LottieAnimatable() }
+
+/**
+ * Use this to create a [LottieAnimatable] outside of a composable such as a hoisted state class.
+ *
+ * @see rememberLottieAnimatable
* @see LottieAnimatable
*/
fun LottieAnimatable(): LottieAnimatable = LottieAnimatableImpl()
/**
- * [LottieAnimatable] is an extension of [LottieAnimationState] that contains imperative
+ * Reset the animation back to the minimum progress and first iteration.
+ */
+suspend fun LottieAnimatable.resetToBeginning() {
+ snapTo(
+ progress = defaultProgress(composition, clipSpec, speed),
+ iteration = 1,
+ )
+}
+
+/**
+ * [rememberLottieAnimatable] is an extension of [LottieAnimationState] that contains imperative
* suspend functions to initiate animations.
*
+ * To create one, call:
+ * ```
+ * val animatable = remember { LottieAnimatable() }
+ * ```
+ *
+ * This is the imperative version of [animateLottieComposition].
+ *
* [LottieAnimationState] ensures *mutual exclusiveness* on its animations. To
* achieve this, when a new animation is started via [animate] or [snapTo], any ongoing
* animation will be canceled via a [CancellationException]. Because of this, it is possible
@@ -40,11 +70,10 @@
*
* @see animate
* @see snapTo
+ * @see animateLottieComposition
*/
@Stable
interface LottieAnimatable : LottieAnimationState {
- suspend fun resetToBeginning()
-
/**
* Snap to a specific point in an animation. This can be used to update the progress
* or iteration count of an ongoing animation. It will cancel any ongoing animations
@@ -58,7 +87,7 @@
* @param iteration Updates the current iteration count. This can be used to "rewind" or
* "fast-forward" an ongoing animation to a past/future iteration count.
* Defaults to [LottieAnimatable.iteration]
- * @param resetLastFrameNanos [LottieAnimatable] keeps track of the frame time of the most
+ * @param resetLastFrameNanos [rememberLottieAnimatable] keeps track of the frame time of the most
* recent animation. When [animate] is called with continueFromPreviousAnimate
* set to true, a delta will be calculated from the most recent [animate] call
* to ensure that the original progress is unaffected by [snapTo] calls in the
@@ -78,17 +107,18 @@
*
* @param composition The [LottieComposition] that should be rendered.
* @param continueFromPreviousAnimate When set to true, this animation will be considered continuous from any
- * previous animate calls. When set to true 1) parameters will carry over from
- * their previous value instead of being set to their defaults 2) instead of
+ * previous animate calls. When set to true, instead of
* starting at the minimum progress, the initial progress will be advanced in
* accordance to the amount of time that has passed since the last frame
* was rendered.
- * @param iteration The iteration to start the animation at. Defaults to 1 and starts at 1.
+ * @param iteration The iteration to start the animation at. Defaults to 1 and carries over from previous animates.
* @param iterations The number of iterations to continue running for. Set to 1 to play one time
* set to [LottieConstants.IterateForever] to iterate forever. Can be set to arbitrary
- * numbers.
- * @param speed The speed at which the composition should be animated. Can be negative. Defaults to 1.
+ * numbers. Defaults to 1 and carries over from previous animates.
+ * @param speed The speed at which the composition should be animated. Can be negative. Defaults to 1 and
+ * carries over from previous animates.
* @param clipSpec An optional [LottieClipSpec] to trim the playback of the composition between two values.
+ * Defaults to null and carries over from previous animates.
* @param initialProgress An optional progress value that the animation should start at. Defaults to the
* starting progress as defined by the clipSpec and speed. Can be used to resume
* animations from arbitrary points.
@@ -99,10 +129,10 @@
*/
suspend fun animate(
composition: LottieComposition?,
- iteration: Int = 1,
- iterations: Int = 1,
- speed: Float = 1f,
- clipSpec: LottieClipSpec? = null,
+ iteration: Int = this.iteration,
+ iterations: Int = this.iterations,
+ speed: Float = this.speed,
+ clipSpec: LottieClipSpec? = this.clipSpec,
initialProgress: Float = defaultProgress(composition, clipSpec, speed),
continueFromPreviousAnimate: Boolean = false,
cancellationBehavior: LottieCancellationBehavior = LottieCancellationBehavior.Immediately,
@@ -151,13 +181,6 @@
private val mutex = MutatorMutex()
- override suspend fun resetToBeginning() {
- snapTo(
- progress = defaultProgress(composition, clipSpec, speed),
- iteration = 1,
- )
- }
-
override suspend fun snapTo(
composition: LottieComposition?,
progress: Float,
@@ -187,7 +210,7 @@
) {
mutex.mutate {
require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }
- require(!(iterations == LottieConstants.IterateForever && cancellationBehavior == LottieCancellationBehavior.OnFinish)) {
+ require(!(iterations == LottieConstants.IterateForever && cancellationBehavior == LottieCancellationBehavior.OnIterationFinish)) {
"You cannot use IterateForever with LottieCancellationBehavior.OnFinish because it will never finish."
}
this.iteration = iteration
@@ -205,7 +228,7 @@
isPlaying = true
try {
val context = when (cancellationBehavior) {
- LottieCancellationBehavior.OnFinish -> NonCancellable
+ LottieCancellationBehavior.OnIterationFinish -> NonCancellable
LottieCancellationBehavior.Immediately -> EmptyCoroutineContext
}
withContext(context) {
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCancellationBehavior.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCancellationBehavior.kt
index f9b23ef..18c3ef5 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCancellationBehavior.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCancellationBehavior.kt
@@ -3,18 +3,16 @@
/**
* Determines how the animation should behave if the calling CoroutineScope is cancelled.
*
- * @see LottieAnimatable
+ * @see rememberLottieAnimatable
*/
enum class LottieCancellationBehavior {
/**
* Stop animation immediately and return early.
- *
- * @see lottieTransition
*/
Immediately,
/**
- * Delay cancellations until the animation has fully completed.
+ * Delay cancellations until the current iteration has fully completed.
* This can be useful in state based transitions where you want one animation to finish its
* animation before continuing to the next.
*
@@ -22,5 +20,5 @@
* This CANNOT be used with [LottieConstants.IterateForever] because that would prevent the animation
* from ever completing.
*/
- OnFinish,
+ OnIterationFinish,
}
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieClipSpec.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieClipSpec.kt
index 395141b..0aecbb1 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieClipSpec.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieClipSpec.kt
@@ -6,7 +6,7 @@
* Use subclasses of [LottieClipSpec] to set min/max bounds on the animation playback.
*
* @see LottieAnimation
- * @see LottieAnimatable
+ * @see rememberLottieAnimatable
* @see animateLottieComposition
*/
sealed class LottieClipSpec {
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
index d36be70..58977f4 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/animateLottieComposition.kt
@@ -9,17 +9,9 @@
import com.airbnb.lottie.LottieComposition
/**
- * Returns a [LottieAnimatable] representing the progress of an animation.
+ * Returns a [LottieAnimationState] representing the progress of an animation.
*
- * Because the state is mutable, you can modify its value and the internal animation
- * will continue animating from the value you set. The progress will snap to the value you
- * set without changing the repeat count.
- *
- * There is also a suspending version of this that takes progress as a MutableState<Float>
- * as a required second parameter.
- *
- * You do not have to use this to animate a Lottie composition. You may create your own animation
- * and pass its progress to [LottieComposition].
+ * This is the declarative version of [rememberLottieAnimatable].
*
* @param composition The composition to render. This should be retrieved with [rememberLottieComposition].
* @param isPlaying Whether or not the animation is currently playing. Note that the internal
@@ -47,7 +39,7 @@
require(iterations > 0) { "Iterations must be a positive number ($iterations)." }
require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
var wasPlaying by remember { mutableStateOf(isPlaying) }
LaunchedEffect(
diff --git a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
index 412cb04..3b907a9 100644
--- a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
+++ b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
@@ -168,7 +168,7 @@
assertFrame(300, progress = 0.5f, iterations = 2)
val clipSpec = LottieClipSpec.MinProgress(0.25f)
launch {
- anim.animate(composition, clipSpec = clipSpec, initialProgress = anim.progress, continueFromPreviousAnimate = true, iterations = 2)
+ anim.animate(composition, clipSpec = clipSpec, initialProgress = anim.progress, continueFromPreviousAnimate = true)
}
assertFrame(598, progress = 0.998f, clipSpec = clipSpec, iterations = 2)
assertFrame(599, progress = 0.25f, clipSpec = clipSpec, iteration = 2, iterations = 2)
@@ -254,7 +254,7 @@
assertFrame(1796, progress = 0.998f, iteration = 3, iterations = 3)
launch {
anim.snapTo(iteration = 1)
- anim.animate(composition, initialProgress = anim.progress, continueFromPreviousAnimate = true, iterations = 3)
+ anim.animate(composition, initialProgress = anim.progress, continueFromPreviousAnimate = true)
}
assertFrame(1796, progress = 0.998f, iteration = 1, iterations = 3)
assertFrame(1797, progress = 0f, iteration = 2, iterations = 3)
@@ -280,7 +280,7 @@
@Test
fun testNonCancellable() = runTest {
val job = launch {
- anim.animate(composition, cancellationBehavior = LottieCancellationBehavior.OnFinish)
+ anim.animate(composition, cancellationBehavior = LottieCancellationBehavior.OnIterationFinish)
}
assertFrame(0, progress = 0f)
job.cancel()
@@ -293,7 +293,7 @@
launch {
anim.animate(
composition,
- cancellationBehavior = LottieCancellationBehavior.OnFinish,
+ cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
iterations = LottieConstants.IterateForever,
)
}
diff --git a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultTest.kt b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultImplTest.kt
similarity index 84%
rename from lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultTest.kt
rename to lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultImplTest.kt
index 622198c..1192fe9 100644
--- a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultTest.kt
+++ b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieCompositionResultImplTest.kt
@@ -9,7 +9,7 @@
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
-class LottieCompositionResultTest {
+class LottieCompositionResultImplTest {
private lateinit var composition: LottieComposition
@@ -20,13 +20,13 @@
@Test
fun testLoading() {
- val result = LottieCompositionResult()
+ val result = LottieCompositionResultImpl()
assertTrue(result.isLoading)
}
@Test
fun testFail() {
- val result = LottieCompositionResult()
+ val result = LottieCompositionResultImpl()
val e = IllegalStateException("Fail")
result.completeExceptionally(e)
assertFalse(result.isSuccess)
@@ -37,7 +37,7 @@
@Test
fun testCompleted() {
- val result = LottieCompositionResult()
+ val result = LottieCompositionResultImpl()
result.complete(composition)
assertFalse(result.isFailure)
assertTrue(result.isSuccess)
@@ -46,7 +46,7 @@
@Test
fun testCompletedThenFail() {
- val result = LottieCompositionResult()
+ val result = LottieCompositionResultImpl()
result.complete(composition)
result.completeExceptionally(IllegalStateException("Fail"))
assertFalse(result.isFailure)
@@ -56,7 +56,7 @@
@Test
fun testErrorThenCompleted() {
- val result = LottieCompositionResult()
+ val result = LottieCompositionResultImpl()
val e = IllegalStateException("Fail")
result.completeExceptionally(e)
result.complete(composition)
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
index 816fce9..b4e5022 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
@@ -19,7 +19,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.compose.LottieAnimatable
+import com.airbnb.lottie.compose.rememberLottieAnimatable
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
@@ -49,7 +49,7 @@
@Composable
private fun Example1() {
- val anim = remember { LottieAnimatable() }
+ val anim = rememberLottieAnimatable()
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
LaunchedEffect(composition) {
anim.animate(
@@ -62,7 +62,7 @@
@Composable
private fun Example2() {
- val anim = remember { LottieAnimatable() }
+ val anim = rememberLottieAnimatable()
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
var sliderGestureProgress: Float? by remember { mutableStateOf(null) }
LaunchedEffect(composition, sliderGestureProgress) {
@@ -91,7 +91,7 @@
@Composable
private fun Example3() {
- val anim = remember { LottieAnimatable() }
+ val anim = rememberLottieAnimatable()
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
var speed by remember { mutableStateOf(1f) }
LaunchedEffect(composition, speed) {
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CoroutinesExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CoroutinesExamplesPage.kt
index c046322..a5d8f9c 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CoroutinesExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/CoroutinesExamplesPage.kt
@@ -15,7 +15,7 @@
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.compose.LottieAnimatable
+import com.airbnb.lottie.compose.rememberLottieAnimatable
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
@@ -51,7 +51,7 @@
@Composable
private fun Example1() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
LaunchedEffect(composition, animatable) {
animatable.animate(
@@ -66,7 +66,7 @@
private fun Example2() {
var nonce by remember { mutableStateOf(1) }
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
LaunchedEffect(composition, nonce) {
composition ?: return@LaunchedEffect
@@ -86,7 +86,7 @@
@Composable
private fun Example3() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
LaunchedEffect(composition, animatable) {
composition ?: return@LaunchedEffect
@@ -102,7 +102,7 @@
private fun Example4() {
var shouldPlay by remember { mutableStateOf(true) }
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
LaunchedEffect(composition, shouldPlay) {
if (composition == null || !shouldPlay) return@LaunchedEffect
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
index 4ebf592..da1c9e3 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
@@ -16,7 +16,7 @@
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
-import com.airbnb.lottie.compose.LottieAnimatable
+import com.airbnb.lottie.compose.rememberLottieAnimatable
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCancellationBehavior
import com.airbnb.lottie.compose.LottieClipSpec
@@ -62,7 +62,7 @@
@Composable
fun SingleCompositionTransition(section: TransitionSection) {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
val state by rememberUpdatedState(section)
LaunchedEffect(composition, animatable) {
@@ -77,7 +77,7 @@
animatable.animate(
composition,
clipSpec = clipSpec,
- cancellationBehavior = LottieCancellationBehavior.OnFinish,
+ cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
)
} while (s == TransitionSection.LoopMiddle)
}
@@ -90,7 +90,7 @@
val introComposition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar_1))
val loopMiddleComposition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar_2))
val outroComposition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar_3))
- val animatable = remember { LottieAnimatable() }
+ val animatable = rememberLottieAnimatable()
val state by rememberUpdatedState(section)
LaunchedEffect(animatable) {
@@ -104,7 +104,7 @@
animatable.animate(
composition,
initialProgress = 0f,
- cancellationBehavior = LottieCancellationBehavior.OnFinish,
+ cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
)
} while (s == TransitionSection.LoopMiddle)
}
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
index b2da502..e27eb88 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/lottiefiles/LottieFilesSearchPage.kt
@@ -1,6 +1,5 @@
package com.airbnb.lottie.sample.compose.lottiefiles
-import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@@ -87,7 +86,6 @@
if (state.currentPage >= state.lastPage) return@withState
fetchJob = viewModelScope.launch {
val response = try {
- Log.d("Gabe", "Fetching page ${state.currentPage + 1}")
api.search(state.query, state.currentPage + 1)
} catch (e: Exception) {
setState { copy(fetchException = true) }
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
index 01f7d3d..8e877dc 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
@@ -1,6 +1,5 @@
package com.airbnb.lottie.sample.compose.player
-import android.util.Log
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
@@ -53,7 +52,6 @@
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.rememberVectorPainter
-import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
@@ -71,6 +69,7 @@
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.resetToBeginning
import com.airbnb.lottie.sample.compose.BuildConfig
import com.airbnb.lottie.sample.compose.R
import com.airbnb.lottie.sample.compose.composables.DebouncedCircularProgressIndicator
@@ -245,7 +244,6 @@
.weight(1f)
.maybeBackground(state.backgroundColor)
.fillMaxWidth()
- .onSizeChanged { Log.d("Gabe", "onSizeChanged $it") }
) {
PlayerPageLottieAnimation(
composition,