| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.compose |
| |
| import android.app.Activity |
| import android.content.Context |
| import android.view.View |
| import android.view.ViewGroup |
| import android.widget.FrameLayout |
| import androidx.annotation.MainThread |
| import org.jetbrains.annotations.TestOnly |
| import java.util.WeakHashMap |
| |
| // TODO(lmr): consider moving this to the ViewComposer directly |
| /** |
| * A global namespace to hold some Compose utility methods, such as [Compose.composeInto] and |
| * [Compose.disposeComposition]. |
| */ |
| object Compose { |
| |
| private class Root : Component() { |
| @Suppress("DEPRECATION") |
| fun update() = recomposeSync() |
| lateinit var composable: @Composable() () -> Unit |
| @Suppress("PLUGIN_ERROR") |
| override fun compose() { |
| val cc = currentComposerNonNull |
| cc.startGroup(0) |
| composable() |
| cc.endGroup() |
| } |
| } |
| |
| private val TAG_ROOT_COMPONENT = "composeRootComponent".hashCode() |
| private val EMITTABLE_ROOT_COMPONENT = WeakHashMap<Emittable, Component>() |
| |
| private fun getRootComponent(view: View): Component? { |
| return view.getTag(TAG_ROOT_COMPONENT) as? Component |
| } |
| |
| // TODO(lmr): used by tests only. consider ways to remove. |
| internal fun findRoot(view: View): Component? { |
| var node: View? = view |
| while (node != null) { |
| val cc = node.getTag(TAG_ROOT_COMPONENT) as? Component |
| if (cc != null) return cc |
| node = node.parent as? View |
| } |
| return null |
| } |
| |
| internal fun setRoot(view: View, component: Component) { |
| view.setTag(TAG_ROOT_COMPONENT, component) |
| } |
| |
| private fun getRootComponent(emittable: Emittable): Component? { |
| return EMITTABLE_ROOT_COMPONENT[emittable] |
| } |
| |
| private fun setRoot(emittable: Emittable, component: Component) { |
| EMITTABLE_ROOT_COMPONENT[emittable] = component |
| } |
| |
| /** |
| * @suppress |
| */ |
| @TestOnly |
| fun createCompositionContext( |
| context: Context, |
| group: Any, |
| component: Component, |
| reference: CompositionReference? |
| ): CompositionContext = CompositionContext.create( |
| context, |
| group, |
| component, |
| reference |
| ).also { |
| when (group) { |
| is ViewGroup -> setRoot(group, component) |
| is Emittable -> setRoot(group, component) |
| } |
| } |
| |
| /** |
| * This method is the way to initiate a composition. The [composable] passed in will be executed |
| * to compose the children of the passed in [container]. Optionally, a [parent] |
| * [CompositionReference] can be provided to make the composition behave as a sub-composition of |
| * the parent. The children of [container] will be updated and maintained by the time this |
| * method returns. |
| * |
| * It is important to call [disposeComposition] whenever this view is no longer needed in order |
| * to release resources. |
| * |
| * @param container The view whose children is being composed. |
| * @param parent The parent composition reference, if applicable. Default is null. |
| * @param composable The composable function intended to compose the children of [container]. |
| * |
| * @see Compose.disposeComposition |
| * @see Composable |
| */ |
| // TODO(lmr): rename to compose? |
| @MainThread |
| fun composeInto( |
| container: ViewGroup, |
| parent: CompositionReference? = null, |
| composable: @Composable() () -> Unit |
| ): CompositionContext? { |
| var root = getRootComponent(container) as? Root |
| if (root == null) { |
| container.removeAllViews() |
| root = Root() |
| root.composable = composable |
| setRoot(container, root) |
| val cc = CompositionContext.create( |
| container.context, |
| container, |
| root, |
| parent |
| ) |
| cc.recompose() |
| return cc |
| } else { |
| root.composable = composable |
| root.recomposeCallback?.invoke(true) |
| } |
| return null |
| } |
| |
| /** |
| * Disposes any composition previously run with [container] as the root. This will |
| * release any resources that have been built around the composition, including all [onDispose] |
| * callbacks that have been registered with [CompositionLifecycleObserver] objects. |
| * |
| * It is important to call this for any [Compose.composeInto] call that is made, or else you may have |
| * memory leaks in your application. |
| * |
| * @param container The view that was passed into [Compose.composeInto] as the root container of the composition |
| * @param parent The parent composition reference, if applicable. |
| * |
| * @see Compose.composeInto |
| * @see CompositionLifecycleObserver |
| */ |
| @MainThread |
| fun disposeComposition(container: ViewGroup, parent: CompositionReference? = null) { |
| // temporary easy way to call correct lifecycles on everything |
| composeInto(container, parent) { } |
| container.setTag(TAG_ROOT_COMPONENT, null) |
| } |
| |
| /** |
| * This method is the way to initiate a composition. The [composable] passed in will be executed |
| * to compose the children of the passed in [container]. Optionally, a [parent] |
| * [CompositionReference] can be provided to make the composition behave as a sub-composition of |
| * the parent. The children of [container] will be updated and maintained by the time this |
| * method returns. |
| * |
| * It is important to call [Compose.disposeComposition] whenever this view is no longer needed in order |
| * to release resources. |
| * |
| * @param container The emittable whose children is being composed. |
| * @param context The android [Context] to associate with this composition. |
| * @param parent The parent composition reference, if applicable. Default is null. |
| * @param composable The composable function intended to compose the children of [container]. |
| * |
| * @see Compose.disposeComposition |
| * @see Composable |
| */ |
| // TODO(lmr): rename to compose? |
| @MainThread |
| fun composeInto( |
| container: Emittable, |
| context: Context, |
| parent: CompositionReference? = null, |
| composable: @Composable() () -> Unit |
| ) { |
| var root = getRootComponent(container) as? Root |
| if (root == null) { |
| root = Root() |
| root.composable = composable |
| setRoot(container, root) |
| val cc = CompositionContext.create(context, container, root, parent) |
| cc.recompose() |
| } else { |
| root.composable = composable |
| root.recomposeCallback?.invoke(true) |
| } |
| } |
| |
| /** |
| * Disposes any composition previously run with [container] as the root. This will |
| * release any resources that have been built around the composition, including all [onDispose] |
| * callbacks that have been registered with [CompositionLifecycleObserver] objects. |
| * |
| * It is important to call this for any [Compose.composeInto] call that is made, or else you may have |
| * memory leaks in your application. |
| * |
| * @param container The view that was passed into [Compose.composeInto] as the root container of the composition |
| * @param context The android [Context] associated with the composition |
| * @param parent The parent composition reference, if applicable. |
| * |
| * @see Compose.composeInto |
| * @see CompositionLifecycleObserver |
| */ |
| @MainThread |
| fun disposeComposition( |
| container: Emittable, |
| context: Context, |
| parent: CompositionReference? = null |
| ) { |
| // temporary easy way to call correct lifecycles on everything |
| composeInto(container, context, parent) {} |
| EMITTABLE_ROOT_COMPONENT.remove(container) |
| } |
| } |
| |
| /** |
| * Sets the contentView of an activity to a FrameLayout, and composes the contents of the layout |
| * with the passed in [composable]. This is a convenience method around [Compose.composeInto]. |
| * |
| * @see Compose.composeInto |
| * @see Activity.setContentView |
| */ |
| fun Activity.setContent(composable: @Composable() () -> Unit) = |
| setContentView(FrameLayout(this).apply { compose(composable) }) |
| |
| /** |
| * Disposes of a composition that was started using [setContent]. This is a convenience method |
| * around [Compose.disposeComposition]. |
| * |
| * @see setContent |
| * @see Compose.disposeComposition |
| */ |
| fun Activity.disposeComposition() { |
| val view = window |
| .decorView |
| .findViewById<ViewGroup>(android.R.id.content) |
| .getChildAt(0) as? ViewGroup |
| ?: error("No root view found") |
| Compose.disposeComposition(view, null) |
| } |
| |
| /** |
| * Composes the children of the view with the passed in [composable]. This is a convenience method |
| * around [Compose.composeInto]. |
| * |
| * @see Compose.composeInto |
| * @see disposeComposition |
| */ |
| fun ViewGroup.compose(composable: @Composable() () -> Unit) = |
| Compose.composeInto(this, null, composable) |
| |
| /** |
| * Disposes of a composition of the children of this view. This is a convenience method around |
| * [Compose.disposeComposition]. |
| * |
| * @see Compose.disposeComposition |
| * @see compose |
| */ |
| fun ViewGroup.disposeComposition() = Compose.disposeComposition(this, null) |