Skip to content

Commit ca97025

Browse files
authored
feat(collections,clustering): complete Kotlin migration for core modules and apply optimizations (#1766)
1 parent e160a04 commit ca97025

35 files changed

Lines changed: 2014 additions & 951 deletions

build-logic/convention/src/main/kotlin/BomPublishingConventionPlugin.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ class BomPublishingConventionPlugin : Plugin<Project> {
2727

2828
extensions.configure<MavenPublishBaseExtension> {
2929
publishToMavenCentral()
30-
signAllPublications()
30+
if (findProperty("signing.keyId")?.toString()?.isNotBlank() == true ||
31+
findProperty("signing.secretKeyRingFile")?.toString()?.isNotBlank() == true ||
32+
findProperty("signingInMemoryKey")?.toString()?.isNotBlank() == true
33+
) {
34+
signAllPublications()
35+
}
3136

3237
coordinates(
3338
artifactId = "maps-utils-bom",

build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,12 @@ class PublishingConventionPlugin : Plugin<Project> {
8383
)
8484

8585
publishToMavenCentral()
86-
signAllPublications()
86+
if (findProperty("signing.keyId")?.toString()?.isNotBlank() == true ||
87+
findProperty("signing.secretKeyRingFile")?.toString()?.isNotBlank() == true ||
88+
findProperty("signingInMemoryKey")?.toString()?.isNotBlank() == true
89+
) {
90+
signAllPublications()
91+
}
8792

8893
val artifactIdName = when (project.name) {
8994
"maps-utils" -> "android-maps-utils"

clustering/src/main/java/com/google/maps/android/clustering/ClusterManager.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ open class ClusterManager<T : ClusterItem>
299299
/**
300300
* Might re-cluster.
301301
*/
302-
open override fun onCameraIdle() {
302+
override fun onCameraIdle() {
303303
if (mRenderer is OnCameraIdleListener) {
304304
(mRenderer as OnCameraIdleListener).onCameraIdle()
305305
}
@@ -317,9 +317,9 @@ open class ClusterManager<T : ClusterItem>
317317
}
318318
}
319319

320-
open override fun onMarkerClick(marker: Marker): Boolean = markerManager.onMarkerClick(marker)
320+
override fun onMarkerClick(marker: Marker): Boolean = markerManager.onMarkerClick(marker)
321321

322-
open override fun onInfoWindowClick(marker: Marker) {
322+
override fun onInfoWindowClick(marker: Marker) {
323323
markerManager.onInfoWindowClick(marker)
324324
}
325325

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* Copyright 2026 Google LLC
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 com.google.maps.android.clustering
18+
19+
import android.content.Context
20+
import com.google.android.gms.maps.GoogleMap
21+
import com.google.android.gms.maps.model.CameraPosition
22+
import com.google.android.gms.maps.model.LatLng
23+
import com.google.android.gms.maps.model.Marker
24+
import com.google.common.truth.Truth.assertThat
25+
import com.google.maps.android.clustering.algo.GridBasedAlgorithm
26+
import com.google.maps.android.clustering.algo.ScreenBasedAlgorithmAdapter
27+
import com.google.maps.android.clustering.view.ClusterRenderer
28+
import com.google.maps.android.collections.MarkerManager
29+
import io.mockk.every
30+
import io.mockk.mockk
31+
import io.mockk.verify
32+
import org.junit.Before
33+
import org.junit.Test
34+
import org.junit.runner.RunWith
35+
import org.robolectric.RobolectricTestRunner
36+
import org.robolectric.RuntimeEnvironment
37+
38+
/**
39+
* Unit tests for [ClusterManager].
40+
*/
41+
@RunWith(RobolectricTestRunner::class)
42+
class ClusterManagerTest {
43+
44+
private class TestItem(lat: Double, lng: Double) : ClusterItem {
45+
override val position: LatLng = LatLng(lat, lng)
46+
override val title: String? = null
47+
override val snippet: String? = null
48+
override val zIndex: Float? = null
49+
}
50+
51+
private lateinit var context: Context
52+
private lateinit var map: GoogleMap
53+
private lateinit var markerManager: MarkerManager
54+
private lateinit var clusterManager: ClusterManager<TestItem>
55+
56+
@Before
57+
fun setUp() {
58+
context = RuntimeEnvironment.getApplication()
59+
map = mockk(relaxed = true)
60+
markerManager = MarkerManager(map)
61+
clusterManager = ClusterManager(context, map, markerManager)
62+
}
63+
64+
@Test
65+
fun testItemLifecycle() {
66+
val item1 = TestItem(10.0, 10.0)
67+
val item2 = TestItem(20.0, 20.0)
68+
69+
assertThat(clusterManager.addItem(item1)).isTrue()
70+
assertThat(clusterManager.addItems(listOf(item2))).isTrue()
71+
72+
assertThat(clusterManager.updateItem(item1)).isTrue()
73+
74+
assertThat(clusterManager.removeItem(item1)).isTrue()
75+
assertThat(clusterManager.removeItems(listOf(item2))).isTrue()
76+
77+
clusterManager.addItem(item1)
78+
clusterManager.clearItems()
79+
}
80+
81+
@Test
82+
fun testAlgorithmAndRendererCustomization() {
83+
val customScreenAlgo = ScreenBasedAlgorithmAdapter(GridBasedAlgorithm<TestItem>())
84+
clusterManager.setAlgorithm(customScreenAlgo)
85+
assertThat(clusterManager.algorithm).isEqualTo(customScreenAlgo)
86+
87+
val customBaseAlgo = GridBasedAlgorithm<TestItem>()
88+
clusterManager.algorithm = customBaseAlgo
89+
assertThat(clusterManager.algorithm).isInstanceOf(ScreenBasedAlgorithmAdapter::class.java)
90+
91+
val customRenderer = mockk<ClusterRenderer<TestItem>>(relaxed = true)
92+
clusterManager.renderer = customRenderer
93+
assertThat(clusterManager.renderer).isEqualTo(customRenderer)
94+
verify { customRenderer.onAdd() }
95+
96+
clusterManager.setAnimation(true)
97+
}
98+
99+
@Test
100+
fun testDelegatedMapEvents() {
101+
val mockMarker = mockk<Marker>(relaxed = true)
102+
103+
clusterManager.onCameraIdle()
104+
clusterManager.onMarkerClick(mockMarker)
105+
clusterManager.onInfoWindowClick(mockMarker)
106+
}
107+
108+
@Test
109+
fun testListenerSetters() {
110+
val clusterClickListener = ClusterManager.OnClusterClickListener<TestItem> { true }
111+
val itemClickListener = ClusterManager.OnClusterItemClickListener<TestItem> { true }
112+
val clusterInfoClickListener = ClusterManager.OnClusterInfoWindowClickListener<TestItem> {}
113+
val itemInfoClickListener = ClusterManager.OnClusterItemInfoWindowClickListener<TestItem> {}
114+
115+
clusterManager.setOnClusterClickListener(clusterClickListener)
116+
clusterManager.setOnClusterItemClickListener(itemClickListener)
117+
clusterManager.setOnClusterInfoWindowClickListener(clusterInfoClickListener)
118+
clusterManager.setOnClusterItemInfoWindowClickListener(itemInfoClickListener)
119+
}
120+
}

clustering/src/test/java/com/google/maps/android/clustering/StaticClusterTest.java

Lines changed: 0 additions & 45 deletions
This file was deleted.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2026 Google LLC
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 com.google.maps.android.clustering
18+
19+
import com.google.android.gms.maps.model.LatLng
20+
import com.google.common.truth.Truth.assertThat
21+
import com.google.maps.android.clustering.algo.StaticCluster
22+
import org.junit.Test
23+
24+
/**
25+
* Unit tests for [StaticCluster].
26+
*/
27+
class StaticClusterTest {
28+
29+
private class TestItem(lat: Double, lng: Double) : ClusterItem {
30+
override val position: LatLng = LatLng(lat, lng)
31+
override val title: String? = null
32+
override val snippet: String? = null
33+
override val zIndex: Float? = null
34+
}
35+
36+
@Test
37+
fun testEquality() {
38+
val cluster1 = StaticCluster<ClusterItem>(LatLng(0.1, 0.5))
39+
val cluster2 = StaticCluster<ClusterItem>(LatLng(0.1, 0.5))
40+
41+
assertThat(cluster1).isEqualTo(cluster2)
42+
assertThat(cluster1).isNotSameInstanceAs(cluster2)
43+
assertThat(cluster1.hashCode()).isEqualTo(cluster2.hashCode())
44+
}
45+
46+
@Test
47+
fun testUnequality() {
48+
val cluster1 = StaticCluster<ClusterItem>(LatLng(0.1, 0.5))
49+
val cluster2 = StaticCluster<ClusterItem>(LatLng(0.2, 0.3))
50+
51+
assertThat(cluster1).isNotEqualTo(cluster2)
52+
assertThat(cluster1.hashCode()).isNotEqualTo(cluster2.hashCode())
53+
assertThat(cluster1).isNotEqualTo(null)
54+
assertThat(cluster1).isNotEqualTo("not a cluster")
55+
}
56+
57+
@Test
58+
fun testItemOperationsAndProperties() {
59+
val center = LatLng(10.0, 20.0)
60+
val cluster = StaticCluster<ClusterItem>(center)
61+
62+
assertThat(cluster.position).isEqualTo(center)
63+
assertThat(cluster.size).isEqualTo(0)
64+
assertThat(cluster.items).isEmpty()
65+
66+
val item = TestItem(10.0, 20.0)
67+
cluster.add(item)
68+
assertThat(cluster.size).isEqualTo(1)
69+
assertThat(cluster.items).containsExactly(item)
70+
assertThat(cluster.toString()).contains("StaticCluster")
71+
72+
cluster.remove(item)
73+
assertThat(cluster.size).isEqualTo(0)
74+
assertThat(cluster.items).isEmpty()
75+
}
76+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2026 Google LLC
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 com.google.maps.android.clustering.algo
18+
19+
import com.google.android.gms.maps.model.LatLng
20+
import com.google.common.truth.Truth.assertThat
21+
import com.google.maps.android.clustering.Cluster
22+
import com.google.maps.android.clustering.ClusterItem
23+
import org.junit.Test
24+
25+
/**
26+
* Unit tests for [AbstractAlgorithm].
27+
*/
28+
class AbstractAlgorithmTest {
29+
30+
private class TestItem : ClusterItem {
31+
override val position: LatLng = LatLng(0.0, 0.0)
32+
override val title: String? = null
33+
override val snippet: String? = null
34+
override val zIndex: Float? = null
35+
}
36+
37+
private class ConcreteAlgorithm : AbstractAlgorithm<TestItem>() {
38+
override fun addItem(item: TestItem): Boolean = true
39+
override fun addItems(items: Collection<TestItem>): Boolean = true
40+
override fun clearItems() {}
41+
override fun removeItem(item: TestItem): Boolean = true
42+
override fun removeItems(items: Collection<TestItem>): Boolean = true
43+
override fun updateItem(item: TestItem): Boolean = true
44+
override fun getClusters(zoom: Float): Set<Cluster<TestItem>> = emptySet()
45+
override val items: Collection<TestItem> get() = emptyList()
46+
override var maxDistanceBetweenClusteredItems: Int = 100
47+
}
48+
49+
@Test
50+
fun testLockOperations() {
51+
val algorithm = ConcreteAlgorithm()
52+
var lockExecuted = false
53+
algorithm.lock()
54+
try {
55+
lockExecuted = true
56+
} finally {
57+
algorithm.unlock()
58+
}
59+
assertThat(lockExecuted).isTrue()
60+
}
61+
}

0 commit comments

Comments
 (0)