Skip to content

Commit

Permalink
chore: Add internal utility for rendering a ComposeView (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
DSteve595 authored Feb 13, 2023
1 parent 6e0ce5a commit 0464505
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
package com.google.maps.android.compose

import android.view.View
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.ui.platform.ComposeView
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
Expand All @@ -42,20 +40,17 @@ internal class ComposeInfoWindowAdapter(
private val markerNodeFinder: (Marker) -> MarkerNode?
) : GoogleMap.InfoWindowAdapter {

private val infoWindowView: ComposeView
get() = ComposeView(mapView.context).apply {
mapView.addView(this)
}

override fun getInfoContents(marker: Marker): View? {
val markerNode = markerNodeFinder(marker) ?: return null
val content = markerNode.infoContent
if (content == null) {
return null
}
return infoWindowView.applyAndRemove(markerNode.compositionContext) {
content(marker)
val view = ComposeView(mapView.context).apply {
setContent { content(marker) }
}
mapView.renderComposeView(view, parentContext = markerNode.compositionContext)
return view
}

override fun getInfoWindow(marker: Marker): View? {
Expand All @@ -64,20 +59,11 @@ internal class ComposeInfoWindowAdapter(
if (infoWindow == null) {
return null
}
return infoWindowView.applyAndRemove(markerNode.compositionContext) {
infoWindow(marker)
val view = ComposeView(mapView.context).apply {
setContent { infoWindow(marker) }
}
mapView.renderComposeView(view, parentContext = markerNode.compositionContext)
return view
}

private fun ComposeView.applyAndRemove(
parentContext: CompositionContext,
content: @Composable () -> Unit
): ComposeView {
val result = this.apply {
setParentCompositionContext(parentContext)
setContent(content)
}
(this.parent as? MapView)?.removeView(this)
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ private object MapNodeRoot : MapNode

internal class MapApplier(
val map: GoogleMap,
private val mapView: MapView,
internal val mapView: MapView,
) : AbstractApplier<MapNode>(MapNodeRoot) {

private val decorations = mutableListOf<MapNode>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.google.maps.android.compose

import android.view.View
import androidx.annotation.RestrictTo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionContext
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.ui.UiComposable
import androidx.compose.ui.platform.ComposeView
import com.google.android.gms.maps.MapView

/**
* Prepares [view] for a single render by temporarily attaching it as a child of this [MapView].
* This is a trick to enable [ComposeView] to start its composition, as it requires being attached
* to a window. [onAddedToWindow] is called in place, and then [view] is removed from the window
* before returning.
*/
internal fun MapView.renderComposeView(
view: ComposeView,
onAddedToWindow: ((View) -> Unit)? = null,
parentContext: CompositionContext,
) {
addView(view)
view.apply {
setParentCompositionContext(parentContext)
}
onAddedToWindow?.invoke(view)
removeView(view)
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@Composable
public fun rememberComposeUiViewRenderer(): ComposeUiViewRenderer {
val mapView = (currentComposer.applier as MapApplier).mapView
val compositionContext = rememberCompositionContext()

return remember(compositionContext) {
object : ComposeUiViewRenderer {

override fun renderView(
onAddedToWindow: ((View) -> Unit)?,
content: @[UiComposable Composable] () -> Unit,
): ComposeView {
val view = ComposeView(mapView.context)
mapView.renderComposeView(
view = view.apply {
setContent(content)
},
onAddedToWindow = onAddedToWindow,
parentContext = compositionContext,
)
return view
}

override fun renderView(
view: ComposeView,
onAddedToWindow: (() -> Unit)?
) {
mapView.renderComposeView(
view = view,
onAddedToWindow = onAddedToWindow?.let { { it() } },
parentContext = compositionContext,
)
}

}
}
}

/** @see MapView.renderComposeView */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public interface ComposeUiViewRenderer {

/**
* Creates a [ComposeView] and prepares it for a single render by temporarily attaching it as a
* child of the [MapView].
* [content] is composed, [onAddedToWindow] is called in place, and then the view is removed
* from the window before returning.
*/
public fun renderView(
onAddedToWindow: ((View) -> Unit)?,
content: @[UiComposable Composable] () -> Unit
): ComposeView

/**
* Prepares [view] for a single render by temporarily attaching it as a child of the [MapView].
* Its composition will start. [onAddedToWindow] is called in place, and then [view] is removed
* from the window before returning.
*/
public fun renderView(
view: ComposeView,
onAddedToWindow: (() -> Unit)?
)

}

0 comments on commit 0464505

Please sign in to comment.