googlemaps / android-maps-rx

RxJava bindings for the Maps and Places SDKs for Android
Apache License 2.0
21 stars 12 forks source link

Support shared observables instead of enforcing same single listener requirement as underlying googleMap instance #116

Open kmayoral opened 6 months ago

kmayoral commented 6 months ago

Thank you for this library!

A humble request to this update would be to provide a method to share a single observable instance across multiple callers for every map event type. This could be done by providing a wrapper around the googleMap instance that tracks the inner observable instance state or a static weakreference keyed-by-googlemap-instance type of cache that tracks any previously created observable instances to share out.

In this way, the caller can have multiple streams referencing the same underlying observables without worrying about that streams which were subscribed to earlier no longer receiving emissions.

wangela commented 6 months ago

If you would like to upvote the priority of this issue, please comment below or react on the original post above with :+1: so we can see what is popular when we triage.

@kmayoral Thank you for opening this issue. 🙏 Please check out these other resources that might help you get to a resolution in the meantime:

This is an automated message, feel free to ignore.

kmayoral commented 6 months ago

Here could be an example implementation (untested):

package com.google.maps.android.rx

import com.google.android.gms.maps.GoogleMap
import com.google.maps.android.rx.shared.MainThreadObservable
import io.reactivex.rxjava3.core.Observable
import java.util.WeakHashMap

/**
 * Utility class used to create shared observable instances that will be returned to callers for
 * as long as the stream is active and the associated google map is still loaded in memory.
 *
 * @param initialCapacity should be set to a value greater than the max number of GoogleMap
 * instances supported by the application. Defaults to 16 which should be more than enough for most
 * use cases.
 */
public class SharedObservableCache<T : MainThreadObservable<R>, R : Any>(
    initialCapacity: Int = 16,
) {
    private val streamCache = WeakHashMap<GoogleMap, Observable<R>>(initialCapacity)

    /**
     * Returns an existing observable instance stored in the observable cache, if it exists or
     * creates a new instance set to be removed from cache whenever the created observable is
     * disposed, and then makes it shareable before placing it into the cache to be referenced by
     * future callers.
     *
     * @param map The GoogleMap instance that we want to request event updates from
     * @param creator The function to invoke to create a new event listening observable if none is
     * present in the cache
     * @return The shareable version of the created or cache referenced observable that is safe to
     * reference across multiple callers
     */
    public fun <T : Observable<R>> getOrCreate(map: GoogleMap, creator: (GoogleMap) -> T): Observable<R> {
        return streamCache[map]
            ?: creator.invoke(map)
                .doOnDispose { synchronized(streamCache) { streamCache.remove(map) } }
                .share()
                .also {
                    synchronized(streamCache) {
                        streamCache[map] = it
                    }
                } as Observable<R>
    }
}

And an example of how it could be referenced in an existing implementation such as within GoogleMapCameraIdleObservable.kt:


// This is a static cache
private val sharedStreamCache by lazy {
    SharedObservableCache<GoogleMapCameraIdleObservable, Unit>()
}

/**
 * Creates an [Observable] that emits whenever the camera on this [GoogleMap] instance goes idle.
 *
 * The created [Observable] uses [GoogleMap.setOnCameraIdleListener] to listen to camera idle
 * events. Since only one listener at a time is allowed, only one Observable at a time can be used.
 */
public fun GoogleMap.cameraIdleEvents(): Observable<Unit> =
    GoogleMapCameraIdleObservable(this)

/**
 * Retrieves a shared [Observable] that emits whenever the camera on this [GoogleMap] instance
 * goes idle.
 *
 * The returned [Observable] will be an instance of [GoogleMapCameraIdleObservable] that has had
 * {@link io.reactivex.rxjava3.core.Observable#share()} called on it in order to allow multiple
 * concurrent rx streams to reference the same camera idle events.
 */
public fun GoogleMap.cameraIdleEventsShared(): Observable<Unit> =
    sharedStreamCache.getOrCreate(this, GoogleMap::cameraIdleEvents)

private class GoogleMapCameraIdleObservable(
    private val googleMap: GoogleMap
) : MainThreadObservable<Unit>() {
....