1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.ui.platform
18 
19 import android.view.View
20 import androidx.compose.ui.internal.checkPreconditionNotNull
21 import androidx.compose.ui.platform.ViewCompositionStrategy.Companion.Default
22 import androidx.customview.poolingcontainer.PoolingContainerListener
23 import androidx.customview.poolingcontainer.addPoolingContainerListener
24 import androidx.customview.poolingcontainer.isPoolingContainer
25 import androidx.customview.poolingcontainer.isWithinPoolingContainer
26 import androidx.customview.poolingcontainer.removePoolingContainerListener
27 import androidx.lifecycle.Lifecycle
28 import androidx.lifecycle.LifecycleEventObserver
29 import androidx.lifecycle.LifecycleOwner
30 import androidx.lifecycle.findViewTreeLifecycleOwner
31 
32 /**
33  * A strategy for managing the underlying Composition of Compose UI [View]s such as [ComposeView]
34  * and [AbstractComposeView]. See [AbstractComposeView.setViewCompositionStrategy].
35  *
36  * Compose views involve ongoing work and registering the composition with external event sources.
37  * These registrations can cause the composition to remain live and ineligible for garbage
38  * collection for long after the host View may have been abandoned. These resources and
39  * registrations can be released manually at any time by calling
40  * [AbstractComposeView.disposeComposition] and a new composition will be created automatically when
41  * needed. A [ViewCompositionStrategy] defines a strategy for disposing the composition
42  * automatically at an appropriate time.
43  *
44  * By default, Compose UI views are configured to [Default].
45  */
46 interface ViewCompositionStrategy {
47 
48     /**
49      * Install this strategy for [view] and return a function that will uninstall it later. This
50      * function should not be called directly; it is called by
51      * [AbstractComposeView.setViewCompositionStrategy] after uninstalling the previous strategy.
52      */
53     fun installFor(view: AbstractComposeView): () -> Unit
54 
55     /**
56      * This companion object may be used to define extension factory functions for other strategies
57      * to aid in discovery via autocomplete. e.g.: `fun
58      * ViewCompositionStrategy.Companion.MyStrategy(): MyStrategy`
59      */
60     companion object {
61         /**
62          * The default strategy for [AbstractComposeView] and [ComposeView].
63          *
64          * Currently, this is [DisposeOnDetachedFromWindowOrReleasedFromPool], though this
65          * implementation detail may change.
66          */
67         // WARNING: the implementation of the default strategy is installed with a reference to
68         // `this` on a not-fully-constructed object in AbstractComposeView.
69         // Be careful not to do anything that would break that.
70         val Default: ViewCompositionStrategy
71             get() = DisposeOnDetachedFromWindowOrReleasedFromPool
72     }
73 
74     /**
75      * The composition will be disposed automatically when the view is detached from a window,
76      * unless it is part of a [pooling container][isPoolingContainer], such as `RecyclerView`.
77      *
78      * When not within a pooling container, this behaves exactly the same as
79      * [DisposeOnDetachedFromWindow].
80      */
81     // WARNING: the implementation of the default strategy is installed with a reference to
82     // `this` on a not-fully-constructed object in AbstractComposeView.
83     // Be careful not to do anything that would break that.
84     object DisposeOnDetachedFromWindowOrReleasedFromPool : ViewCompositionStrategy {
85         override fun installFor(view: AbstractComposeView): () -> Unit {
86             val listener =
87                 object : View.OnAttachStateChangeListener {
88                     override fun onViewAttachedToWindow(v: View) {}
89 
90                     override fun onViewDetachedFromWindow(v: View) {
91                         if (!view.isWithinPoolingContainer) {
92                             view.disposeComposition()
93                         }
94                     }
95                 }
96             view.addOnAttachStateChangeListener(listener)
97 
98             val poolingContainerListener = PoolingContainerListener { view.disposeComposition() }
99             view.addPoolingContainerListener(poolingContainerListener)
100 
101             return {
102                 view.removeOnAttachStateChangeListener(listener)
103                 view.removePoolingContainerListener(poolingContainerListener)
104             }
105         }
106     }
107 
108     /**
109      * [ViewCompositionStrategy] that disposes the composition whenever the view becomes detached
110      * from a window. If the user of a Compose UI view never explicitly calls
111      * [AbstractComposeView.createComposition], this strategy is always safe and will always clean
112      * up composition resources with no explicit action required - just use the view like any other
113      * View and let garbage collection do the rest. (If [AbstractComposeView.createComposition] is
114      * called while the view is detached from a window, [AbstractComposeView.disposeComposition]
115      * must be called manually if the view is not later attached to a window.)
116      */
117     object DisposeOnDetachedFromWindow : ViewCompositionStrategy {
118         override fun installFor(view: AbstractComposeView): () -> Unit {
119             val listener =
120                 object : View.OnAttachStateChangeListener {
121                     override fun onViewAttachedToWindow(v: View) {}
122 
123                     override fun onViewDetachedFromWindow(v: View) {
124                         view.disposeComposition()
125                     }
126                 }
127             view.addOnAttachStateChangeListener(listener)
128             return { view.removeOnAttachStateChangeListener(listener) }
129         }
130     }
131 
132     /**
133      * [ViewCompositionStrategy] that disposes the composition when [lifecycle] is
134      * [destroyed][Lifecycle.Event.ON_DESTROY]. This strategy is appropriate for Compose UI views
135      * that share a 1-1 relationship with a known [LifecycleOwner].
136      */
137     class DisposeOnLifecycleDestroyed(private val lifecycle: Lifecycle) : ViewCompositionStrategy {
138         constructor(lifecycleOwner: LifecycleOwner) : this(lifecycleOwner.lifecycle)
139 
140         override fun installFor(view: AbstractComposeView): () -> Unit =
141             installForLifecycle(view, lifecycle)
142     }
143 
144     /**
145      * [ViewCompositionStrategy] that disposes the composition when the [LifecycleOwner] returned by
146      * [findViewTreeLifecycleOwner] of the next window the view is attached to is
147      * [destroyed][Lifecycle.Event.ON_DESTROY]. This strategy is appropriate for Compose UI views
148      * that share a 1-1 relationship with their closest [LifecycleOwner], such as a Fragment view.
149      */
150     object DisposeOnViewTreeLifecycleDestroyed : ViewCompositionStrategy {
151         override fun installFor(view: AbstractComposeView): () -> Unit {
152             if (view.isAttachedToWindow) {
153                 val lco =
154                     checkPreconditionNotNull(view.findViewTreeLifecycleOwner()) {
155                         "View tree for $view has no ViewTreeLifecycleOwner"
156                     }
157                 return installForLifecycle(view, lco.lifecycle)
158             } else {
159                 // We change this reference after we successfully attach
160                 var disposer: () -> Unit
161                 val listener =
162                     object : View.OnAttachStateChangeListener {
163                         override fun onViewAttachedToWindow(v: View) {
164                             val lco =
165                                 checkPreconditionNotNull(view.findViewTreeLifecycleOwner()) {
166                                     "View tree for $view has no ViewTreeLifecycleOwner"
167                                 }
168                             disposer = installForLifecycle(view, lco.lifecycle)
169 
170                             // Ensure this runs only once
171                             view.removeOnAttachStateChangeListener(this)
172                         }
173 
174                         override fun onViewDetachedFromWindow(v: View) {}
175                     }
176                 view.addOnAttachStateChangeListener(listener)
177                 disposer = { view.removeOnAttachStateChangeListener(listener) }
178                 return { disposer() }
179             }
180         }
181     }
182 }
183 
installForLifecyclenull184 private fun installForLifecycle(view: AbstractComposeView, lifecycle: Lifecycle): () -> Unit {
185     check(lifecycle.currentState > Lifecycle.State.DESTROYED) {
186         "Cannot configure $view to disposeComposition at Lifecycle ON_DESTROY: $lifecycle" +
187             "is already destroyed"
188     }
189     val observer = LifecycleEventObserver { _, event ->
190         if (event == Lifecycle.Event.ON_DESTROY) {
191             view.disposeComposition()
192         }
193     }
194     lifecycle.addObserver(observer)
195     return { lifecycle.removeObserver(observer) }
196 }
197