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