Skip to content

Commit d38f4e7

Browse files
peterInTownclaude
andauthored
Fix LocationMarkerLayout bitmap rendering crash (#2615)
* Fix LocationMarkerLayout to handle zero-valued spacing and border tokens - Add defensive fallback values for BpkSpacing.Base (16.dp) - Add defensive fallback values for BpkBorderSize.Lg (2.dp) - Prevent IllegalArgumentException when bitmap dimensions are 0 - Handles cases where theme tokens are not initialized in isolated composition Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * Fix detekt linting issues in BpkLocationMapMarker Remove unnecessary whitespace and add proper spacing before closing braces in takeIf expressions. This fixes the following detekt violations: - NoMultipleSpaces on lines 72 and 75 - SpacingAroundCurly on lines 72 and 75 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com> * [DON-Nojira] update new fix Co-authored-by: Claude <noreply@anthropic.com> * [DON-Nojira] update retry logic Co-authored-by: Claude <noreply@anthropic.com> * [DON-] update condition for google map marker Co-authored-by: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5a80c22 commit d38f4e7

File tree

8 files changed

+123
-90
lines changed

8 files changed

+123
-90
lines changed

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkHotelMapMarker.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,17 @@ fun BpkHotelMapMarker(
6565
HotelMarkerLayout(status = status, icon = icon)
6666
}
6767

68-
MarkerInfoWindow(
69-
state = state,
70-
tag = tag,
71-
title = contentDescription,
72-
visible = visible,
73-
zIndex = if (status == BpkHotelMarkerStatus.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
74-
icon = iconBitmap,
75-
onClick = onClick,
76-
) {}
68+
iconBitmap?.let {
69+
MarkerInfoWindow(
70+
state = state,
71+
tag = tag,
72+
title = contentDescription,
73+
visible = visible,
74+
zIndex = if (status == BpkHotelMarkerStatus.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
75+
icon = iconBitmap,
76+
onClick = onClick,
77+
) {}
78+
}
7779
}
7880

7981
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkIconMapMarker.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ fun BpkIconMapMarker(
6565
val iconBitmap = rememberCapturedComposeBitmapDescriptor(icon, status) {
6666
IconMarkerLayout(status = status, icon = icon)
6767
}
68-
69-
MarkerInfoWindow(
70-
state = state,
71-
tag = tag,
72-
title = contentDescription,
73-
visible = visible,
74-
zIndex = if (status == BpkIconMarkerStatus.Focused && zIndex == null) 1.0f else zIndex ?: 0.0f,
75-
icon = iconBitmap,
76-
onClick = onClick,
77-
) {}
68+
iconBitmap?.let {
69+
MarkerInfoWindow(
70+
state = state,
71+
tag = tag,
72+
title = contentDescription,
73+
visible = visible,
74+
zIndex = if (status == BpkIconMarkerStatus.Focused && zIndex == null) 1.0f else zIndex ?: 0.0f,
75+
icon = iconBitmap,
76+
onClick = onClick,
77+
) {}
78+
}
7879
}
7980

8081
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkLocationMapMarker.kt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,18 @@ fun BpkLocationMapMarker(
5050
LocationMarkerLayout()
5151
}
5252

53-
MarkerInfoWindow(
54-
state = state,
55-
tag = tag,
56-
title = title,
57-
visible = visible,
58-
zIndex = zIndex,
59-
icon = iconBitmap,
60-
onClick = onClick,
61-
) {}
53+
// Only show marker if bitmap is ready to avoid stale/invalid icons
54+
iconBitmap?.let {
55+
MarkerInfoWindow(
56+
state = state,
57+
tag = tag,
58+
title = title,
59+
visible = visible,
60+
zIndex = zIndex,
61+
icon = it,
62+
onClick = onClick,
63+
) {}
64+
}
6265
}
6366

6467
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkPoiMapMarker.kt

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,17 @@ fun BpkPoiMapMarker(
6363
val iconBitmap = rememberCapturedComposeBitmapDescriptor(icon, status) {
6464
PoiMarkerLayout(status = status, icon = icon)
6565
}
66-
67-
MarkerInfoWindow(
68-
state = state,
69-
tag = tag,
70-
title = contentDescription,
71-
visible = visible,
72-
zIndex = if (status == BpkPoiMarkerStatus.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
73-
icon = iconBitmap,
74-
onClick = onClick,
75-
) {}
66+
iconBitmap?.let {
67+
MarkerInfoWindow(
68+
state = state,
69+
tag = tag,
70+
title = contentDescription,
71+
visible = visible,
72+
zIndex = if (status == BpkPoiMarkerStatus.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
73+
icon = iconBitmap,
74+
onClick = onClick,
75+
) {}
76+
}
7677
}
7778

7879
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkPointerMapMarker.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,20 @@ fun BpkPointerMapMarker(
5353
PointerMarkerLayout()
5454
}
5555

56-
MarkerInfoWindow(
57-
state = state,
58-
tag = tag,
59-
title = title,
60-
anchor = Offset(0.5f, 0.5f),
61-
visible = visible,
62-
zIndex = zIndex,
63-
icon = iconBitmap,
64-
onClick = onClick,
65-
onInfoWindowClick = onInfoWindowClick,
66-
) {
67-
PriceMarkerLayout(title = title, status = BpkPriceMarkerStatus.Focused)
56+
iconBitmap?.let {
57+
MarkerInfoWindow(
58+
state = state,
59+
tag = tag,
60+
title = title,
61+
anchor = Offset(0.5f, 0.5f),
62+
visible = visible,
63+
zIndex = zIndex,
64+
icon = iconBitmap,
65+
onClick = onClick,
66+
onInfoWindowClick = onInfoWindowClick,
67+
) {
68+
PriceMarkerLayout(title = title, status = BpkPriceMarkerStatus.Focused)
69+
}
6870
}
6971
}
7072

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkPriceMapMarker.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ fun BpkPriceMapMarker(
6666
PriceMarkerLayout(title = title, status = status)
6767
}
6868

69-
MarkerInfoWindow(
70-
state = state,
71-
tag = tag,
72-
title = title,
73-
visible = visible,
74-
zIndex = if (status == BpkPriceMarkerStatus.Focused && zIndex == null) 1.0f else zIndex ?: 0.0f,
75-
icon = icon,
76-
onClick = onClick,
77-
) {}
69+
icon?.let {
70+
MarkerInfoWindow(
71+
state = state,
72+
tag = tag,
73+
title = title,
74+
visible = visible,
75+
zIndex = if (status == BpkPriceMarkerStatus.Focused && zIndex == null) 1.0f else zIndex ?: 0.0f,
76+
icon = icon,
77+
onClick = onClick,
78+
) {}
79+
}
7880
}
7981

8082
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/map/BpkPriceMapMarkerV2.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,17 @@ fun BpkPriceMapMarkerV2(
6666
PriceMarkerV2Layout(title = title, status = status, prefixIcon = prefixIcon)
6767
}
6868

69-
MarkerInfoWindow(
70-
state = state,
71-
tag = tag,
72-
title = title,
73-
visible = visible,
74-
zIndex = if (status == BpkPriceMarkerV2Status.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
75-
icon = icon,
76-
onClick = onClick,
77-
) {}
69+
icon?.let {
70+
MarkerInfoWindow(
71+
state = state,
72+
tag = tag,
73+
title = title,
74+
visible = visible,
75+
zIndex = if (status == BpkPriceMarkerV2Status.Selected && zIndex == null) 1.0f else zIndex ?: 0.0f,
76+
icon = icon,
77+
onClick = onClick,
78+
) {}
79+
}
7880
}
7981

8082
@Composable

backpack-compose/src/main/kotlin/net/skyscanner/backpack/compose/utils/ComposeToBitmap.kt

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import android.view.View
2323
import android.view.ViewGroup
2424
import androidx.compose.runtime.Composable
2525
import androidx.compose.runtime.CompositionContext
26+
import androidx.compose.runtime.LaunchedEffect
2627
import androidx.compose.runtime.getValue
2728
import androidx.compose.runtime.mutableStateOf
2829
import androidx.compose.runtime.remember
@@ -35,60 +36,79 @@ import androidx.core.graphics.applyCanvas
3536
import androidx.core.graphics.createBitmap
3637
import com.google.android.gms.maps.model.BitmapDescriptor
3738
import com.google.android.gms.maps.model.BitmapDescriptorFactory
39+
import kotlinx.coroutines.delay
3840

3941
@Composable
4042
internal fun rememberCapturedComposeBitmapDescriptor(
4143
vararg keys: Any?,
4244
content: @Composable () -> Unit,
43-
): BitmapDescriptor {
45+
): BitmapDescriptor? {
4446
val bitmap = rememberCapturedComposeBitmap(*keys, content = content)
45-
return remember(bitmap) { BitmapDescriptorFactory.fromBitmap(bitmap) }
47+
return remember(bitmap) { bitmap?.let { BitmapDescriptorFactory.fromBitmap(it) } }
4648
}
4749

4850
@Composable
4951
internal fun rememberCapturedComposeBitmap(
5052
vararg keys: Any?,
5153
content: @Composable () -> Unit,
52-
): Bitmap {
54+
): Bitmap? {
5355
val parent = LocalView.current as ViewGroup
5456
val currentContext = rememberCompositionContext()
5557
val currentContent by rememberUpdatedState(content)
5658
var cachedBitmap by remember { mutableStateOf<Bitmap?>(null) }
5759

58-
val newBitmap = remember(parent, currentContext, currentContent, *keys) {
59-
renderComposeToBitmap(parent, currentContext, currentContent)
60+
LaunchedEffect(parent, currentContext, currentContent, *keys) {
61+
var retries = 0
62+
val maxRetries = 5
63+
64+
while (retries < maxRetries) {
65+
val bitmap = renderComposeToBitmap(parent, currentContext, currentContent)
66+
if (bitmap != null) {
67+
cachedBitmap = bitmap
68+
return@LaunchedEffect
69+
}
70+
retries++
71+
if (retries < maxRetries) {
72+
delay(16) // Wait ~1 frame (~16ms at 60fps)
73+
}
74+
}
6075
}
61-
cachedBitmap = newBitmap
62-
return cachedBitmap ?: newBitmap
76+
77+
return cachedBitmap
6378
}
6479

6580
private fun renderComposeToBitmap(
6681
parent: ViewGroup,
6782
compositionContext: CompositionContext,
6883
content: @Composable () -> Unit,
69-
): Bitmap {
70-
84+
): Bitmap? {
7185
val composeView = ComposeView(parent.context)
7286
composeView.setParentCompositionContext(compositionContext)
73-
7487
composeView.setContent(content)
7588

76-
parent.addView(composeView)
89+
return try {
90+
parent.addView(composeView)
7791

78-
composeView.measure(
79-
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
80-
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
81-
)
92+
composeView.measure(
93+
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
94+
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
95+
)
8296

83-
composeView.layout(0, 0, composeView.measuredWidth, composeView.measuredHeight)
97+
composeView.layout(0, 0, composeView.measuredWidth, composeView.measuredHeight)
8498

85-
val bitmap = createBitmap(composeView.measuredWidth, composeView.measuredHeight)
99+
// If measurements are not ready yet (0x0), return null to retry on next frame
100+
if (composeView.measuredWidth <= 0 || composeView.measuredHeight <= 0) {
101+
return null
102+
}
86103

87-
bitmap.applyCanvas {
88-
composeView.draw(this)
89-
}
104+
val bitmap = createBitmap(composeView.measuredWidth, composeView.measuredHeight)
90105

91-
parent.removeView(composeView)
106+
bitmap.applyCanvas {
107+
composeView.draw(this)
108+
}
92109

93-
return bitmap
110+
bitmap
111+
} finally {
112+
parent.removeView(composeView)
113+
}
94114
}

0 commit comments

Comments
 (0)