0

I am having unexpected behavior with kotlin and rxjava. I create an extension function for loading image using picasso

fun Picasso.loadBitmap(url: String) : Observable<Bitmap>
        = Observable.create<Bitmap> {
    emitter ->
    Log.d("picasso load bitmap", "me ${this}")
    try {
        val bitmap = load(url).centerCrop()
                .resize(100, 100)
                .transform(CircleTransformer())
                .get()
        emitter.onNext(bitmap)
        emitter.onComplete()
    } catch (e: IOException) {
        emitter.onError(e)
    }
}

Im calling this multiple times in a close interval(almost at the same time) like this,

picasso.loadBitmap(place.image_url)
    .subscribeOn(Schedulers.io())
    .retryWhen { error ->
        error.zipWith(Observable.range(1, 5),
                BiFunction<Throwable, Int, RetryWrapper> {
                    t1, t2 -> RetryWrapper(t2.toLong(), t1) })
                .flatMap {
                    if(it.delay < 4){
                        Log.d(TAG, "retry no. ${it.delay} for ${place.image_url}")
                        Observable.timer(it.delay * 5, TimeUnit.SECONDS)
                    } else {
                        Log.d(TAG, "DMD ${place.image_url}")
                        Observable.error { it.error }
                    }
                }

    }
    .subscribe (
        { bitmap ->
            markers.find { it.place.id == place.id }?.let {
                it.marker.icon = IconFactory.getInstance(context).fromBitmap(bitmap)
            }
        },
        {
            Log.e(TAG, "error decoding ${place.image_url}", it)
        })

I expect that every time loadBitmap will be called, it will create a new observable. But I got this in the logs

09-28 11:17:00.022 31694-32276/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.068 31694-32277/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.069 31694-31959/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.108 31694-32278/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.112 31694-32251/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.125 31694-32260/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.162 31694-31794/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.192 31694-32280/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.195 31694-32279/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:00.219 31694-32281/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:04.828 31694-32262/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:14.885 31694-31793/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26
09-28 11:17:29.928 31694-32269/? D/picasso load bitmap: me com.squareup.picasso.Picasso@c894e26

The observable is the same for all loadBitmap calls. I need them to have there own observable because if I wont, when retryWhen fails, it wont proceed to the next fail. I hope it make sense.

Putting the observable inside a defer or flatmap wont change anything.

EDIT my code

override fun render(state: MainState) {
        map?.let { map ->
            val newMarkers: MutableList<PlaceMarker> = mutableListOf()
            for(place in state.places) {
                var placeMarker = placeMarkers.find { it.place.id == place.id }
                if(placeMarker != null && map.markers.contains(placeMarker.marker)) {
                    newMarkers.add(placeMarker)
                    placeMarkers.remove(placeMarker)
                } else {
                    if(placeMarker != null) placeMarkers.remove(placeMarker)
                    val option = MarkerOptions()
                    option.position = LatLng(place.latitude, place.longitude)
                    option.snippet = place.name
                    placeMarker = PlaceMarker(place, map.addMarker(option))
                    newMarkers.add(placeMarker)

                    picasso.loadBitmap(place.image_url)
                            .subscribeOn(Schedulers.io())
                            .retryWhen { error ->
                                error.zipWith(Observable.range(1, 5),
                                        BiFunction<Throwable, Int, RetryWrapper> {
                                            t1, t2 -> RetryWrapper(t2.toLong(), t1) })
                                        .flatMap {
                                            if(it.delay < 4){
                                                Log.d(TAG, "retry no. ${it.delay} for ${place.image_url}")
                                                Observable.timer(it.delay * 5, TimeUnit.SECONDS)
                                            } else {
                                                Log.d(TAG, "DMD ${place.image_url}")
                                                Observable.error { it.error }
                                            }
                                        }
                            }
                            .subscribe (
                                    { bitmap ->
                                        placeMarkers.find { it.place.id == place.id }?.let {
                                            it.marker.icon = IconFactory.getInstance(context).fromBitmap(bitmap)
                                            bitmap.recycle()
                                        }
                                    },
                                    {
                                        Log.e(TAG, "error decoding ${place.image_url}", it)
                                    })
                }

            }
            placeMarkers.forEach { it.marker.remove() }
            placeMarkers.clear()
            placeMarkers.addAll(newMarkers)
        }
    }

Im using MVP, just for you to see a little wider. So, thats a function inside a VIEW, render will be triggered after the MODEL finished getting data from server.

Hohenheim
  • 393
  • 2
  • 16

1 Answers1

3

You have to be careful here. The keyword this in

Log.d("picasso load bitmap", "me ${this}")

doesn't targets the Observable but the receiver type. In your case Picasso. You see that in your log me com.squareup.picasso.Picasso@c894e26

The good news is, that you get a new instance of Observable for every call of loadBitmap. You can check this by:

val observable = picasso.loadBitmap(place.image_url)
Log.d("observable for picasso", "$observable")
observable.subscribeOn(Schedulers.io())...

So you see, you call the loadBitmap always on the same instance of picasso that's why you get the same output for that class. But every individual call to loadBitmap creates a new Observable

So your code is fine.

Willi Mentzel
  • 27,862
  • 20
  • 113
  • 121
guenhter
  • 11,255
  • 3
  • 35
  • 66
  • Thank you, but why is it that I can only get 1 cycles of retry and nothing more? I can only get 1 loading error, but I got plenty more urls to load. – Hohenheim Sep 28 '17 at 08:03
  • Could you add some more details about this problem. – guenhter Sep 28 '17 at 08:13
  • Sorry, I have a list of places fetched from server. This places has img_urls on it, I need to make a marker on each place and at the same time spawn an observable to load the image. What happen is that when I get retry cycles, it wont continue to load the rest of the imgs. I hope it make sense. Ill post a code. – Hohenheim Sep 28 '17 at 08:17
  • Just to narrow down the problem. can you replace your whole `retryWhen` with e.g. `.doOnError { Log.d("Error received") }`. If this helps, than you have to check the `retryWhen` – guenhter Sep 28 '17 at 09:01
  • Hi @guenhter, I tried now, I still get 1 `java.io.IOException: Failed to decode stream.`, nothing more. – Hohenheim Sep 28 '17 at 09:26
  • Hi @guenhter, I recorded the `no. of subscribe`, `no. of success` and `no. of failure`, it surprise me that it did fail only once. This makes me pull my hair, how come that not all my markers are populated with icons when there is only 1 error. subscribe: 38 success: 37 failures: 1 Thank you for your help. – Hohenheim Sep 28 '17 at 09:38