xref: /aosp_15_r20/external/lottie/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieCompositionResult.kt (revision bb5273fecd5c61b9ace70f9ff4fcd88f0e12e3f7)
1 package com.airbnb.lottie.compose
2 
3 import androidx.compose.runtime.Stable
4 import androidx.compose.runtime.State
5 import androidx.compose.runtime.derivedStateOf
6 import androidx.compose.runtime.getValue
7 import androidx.compose.runtime.mutableStateOf
8 import androidx.compose.runtime.setValue
9 import com.airbnb.lottie.LottieComposition
10 import kotlinx.coroutines.CompletableDeferred
11 
12 /**
13  * A [LottieCompositionResult] subclass is returned from [rememberLottieComposition].
14  * It can be completed with a result or exception only one time.
15  *
16  * This class implements State<LottieComposition> so you either use it like:
17  * ```
18  * val compositionResult = rememberLottieComposition(...)
19  * // Or
20  * val composition by rememberLottieComposition(...)
21  * ```
22  *
23  * Use the former if you need to explicitly differentiate between loading and error states
24  * or if you need to call [await] or [awaitOrNull] in a coroutine such as [androidx.compose.runtime.LaunchedEffect].
25  *
26  * @see rememberLottieComposition
27  * @see LottieAnimation
28  */
29 @Stable
30 interface LottieCompositionResult : State<LottieComposition?> {
31     /**
32      * The composition or null if it hasn't yet loaded or failed to load.
33      */
34     override val value: LottieComposition?
35 
36     /**
37      * The exception that was thrown while trying to load and parse the composition.
38      */
39     val error: Throwable?
40 
41     /**
42      * Whether or not the composition is still being loaded and parsed.
43      */
44     val isLoading: Boolean
45 
46     /**
47      * Whether or not the composition is in the process of being loaded or parsed.
48      */
49     val isComplete: Boolean
50 
51     /**
52      * Whether or not the composition failed to load. This is terminal. It only occurs after
53      * returning false from [rememberLottieComposition]'s onRetry lambda.
54      */
55     val isFailure: Boolean
56 
57     /**
58      * Whether or not the composition has succeeded yet.
59      */
60     val isSuccess: Boolean
61 
62     /**
63      * Suspend until the composition has finished parsing.
64      *
65      * This can throw if the [LottieComposition] fails to load.
66      *
67      * These animations should never fail given a valid input:
68      * * [LottieCompositionSpec.RawRes]
69      * * [LottieCompositionSpec.Asset]
70      * * [LottieCompositionSpec.JsonString]
71      *
72      * These animations may fail:
73      * * [LottieCompositionSpec.Url]
74      * * [LottieCompositionSpec.File]
75      */
awaitnull76     suspend fun await(): LottieComposition
77 }
78 
79 /**
80  * Like [LottieCompositionResult.await] but returns null instead of throwing an exception if the animation fails
81  * to load.
82  */
83 suspend fun LottieCompositionResult.awaitOrNull(): LottieComposition? {
84     return try {
85         await()
86     } catch (e: Throwable) {
87         null
88     }
89 }
90 
91 @Stable
92 internal class LottieCompositionResultImpl : LottieCompositionResult {
93     private val compositionDeferred = CompletableDeferred<LottieComposition>()
94 
95     override var value: LottieComposition? by mutableStateOf(null)
96         private set
97 
98     override var error by mutableStateOf<Throwable?>(null)
99         private set
100 
<lambda>null101     override val isLoading by derivedStateOf { value == null && error == null }
102 
<lambda>null103     override val isComplete by derivedStateOf { value != null || error != null }
104 
<lambda>null105     override val isFailure by derivedStateOf { error != null }
106 
<lambda>null107     override val isSuccess by derivedStateOf { value != null }
108 
awaitnull109     override suspend fun await(): LottieComposition {
110         return compositionDeferred.await()
111     }
112 
113     @Synchronized
completenull114     internal fun complete(composition: LottieComposition) {
115         if (isComplete) return
116 
117         this.value = composition
118         compositionDeferred.complete(composition)
119     }
120 
121     @Synchronized
completeExceptionallynull122     internal fun completeExceptionally(error: Throwable) {
123         if (isComplete) return
124 
125         this.error = error
126         compositionDeferred.completeExceptionally(error)
127     }
128 }