1

I have this multiple calls to AsyncTask that I would like to convert to RxJava. The AsyncTask code works, but I would just like to explore how to do this in RxJava?

(By the way, I know the title sucks)

What it does:

  • Loop through MyImageButton (which is just a POJO, not extending ImageButton) list
  • On each MyImageButton, get its current bitmap (through getDrawable())
  • Get the icon from a URL.
  • On combine the two images BitmapUtils.combine(...);
  • Assign this newly combined bitmap to an ImageButton

How do I convert this to RxJava:

 for (MyImageButton myImageButton: myImageButtons) {
        final ImageButton imageButton = (ImageButton) findViewById(myImageButton.getImageButtonResId()); //getImageButtonResId holds a reference to some ImageButton
        final Bitmap bitmap = ((BitmapDrawable) imageButton.getDrawable()).getBitmap();

        new BindImageTask(imageButton, bitmap, myImageButton.getIconUrl()).execute();
 }

Here's the BindImageTask:

private class BindImageTask extends AsyncTask<Void, Void, Bitmap> {

    private WeakReference<Bitmap> srcBitmapWeakReference;
    private WeakReference<ImageButton> imageButtonWeakReference;
    private String dstIconUrl;

    BindImageTask(ImageButton imageButton, Bitmap srcBitmap, String dstIconUrl) {

        srcBitmapWeakReference = new WeakReference<>(srcBitmap);
        imageButtonWeakReference = new WeakReference<>(imageButton);
        this.dstIconUrl = dstIconUrl;
    }

    @Override
    protected Bitmap doInBackground(Void... params) {
        Bitmap srcBitmap = srcBitmapWeakReference.get();
        if (srcBitmap == null) return null;

        Bitmap dstBitmap = ImageLoader.getInstance().loadImageSync(dstIconUrl, new ImageSize(60, 60));
        if (dstBitmap == null) {
            return null;
        }

        return BitmapUtils.combineImage(srcBitmap, dstBitmap, PorterDuff.Mode.DST_OVER);

    }

    @Override
    protected void onPostExecute(Bitmap resultBitmap) {
        super.onPostExecute(resultBitmap);

        ImageButton imageButton = imageButtonWeakReference.get();

        if (imageButton != null && resultBitmap != null) {
            imageButton.setImageBitmap(resultBitmap);
        }

    }
}

Now I tried to use RxJava on this and, I produced this bad use of RxJava (it works, but I know there's a better way):

 Observable<MyImageButton> myImageButtonObservable = Observable.from(myImageButtons);

    myImageButtonObservable

            .map(new Func1<MyImageButton, MyImageButton>() {
                @Override
                public MyImageButton call(MyImageButton myImageButton) {
                    final ImageButton imageButton = (ImageButton) findViewById(myImageButton.getDialButtonResId());
                    final Bitmap srcBitmap = ((BitmapDrawable) imageButton.getDrawable()).getBitmap();
                    final Bitmap dstBitmap = ImageLoader.getInstance().loadImageSync(myImageButton.getIcon(), new ImageSize(60, 60));
                    final Bitmap newBitmap =  BitmapUtils.combineImage(srcBitmap, dstBitmap, PorterDuff.Mode.DST_OVER);

                    ImageLoader.getInstance().getMemoryCache().put(myImageButton.getIconUrl() + "_compound", newBitmap);
                    return myImageButton;

                }
            })
            .onErrorReturn(new Func1<Throwable, MyImageButton>() {
                @Override
                public MyImageButton call(Throwable throwable) {
                    return null;
                }
            })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Action1<MyImageButton>() {
                @Override
                public void call(MyImageButton myImageButton) {
                    if(myImageButton == null) {
                        return;
                    }
                    final ImageButton imageButton = (ImageButton) findViewById(myImageButton.getDialButtonResId());
                    imageButton.setImageBitmap(ImageLoader.getInstance().getMemoryCache().get(myImageButton.getIconUrl() + "_compound"));
                }
            });

What I wanted to happen is:

  • Not having to save the newly combined image into the ImageLoader's cache.
  • On the subscribe callback I want to have references to both this new bitmap AND reference to MyImageButton so that I can just do a simple myImageButton.setImageBitmap(newBitmap).

What I don't want:

  • To have a bitmap reference inside the MyImageButton class (since I have to serialize it later).

1 Answer 1

8

First way. Using helper class Data

Create class Data.java:

public static class Data{
    private ImageButton imageButton;
    private Bitmap srcBitmap;
    private Bitmap dstBitmap;
    private Bitmap combinedBitmap;
    private String dstIconUrl;

    public Data(ImageButton imageButton, String iconUrl) {
        this.imageButton = imageButton;
        srcBitmap = ((BitmapDrawable) imageButton.getDrawable()).getBitmap();
        dstIconUrl = iconUrl;
    }

    public ImageButton getImageButton() {
        return imageButton;
    }

    public Bitmap getSrcBitmap() {
        return srcBitmap;
    }

    public Bitmap getDstBitmap(){
        return dstBitmap;
    }

    public String getDstIconUrl() {
        return dstIconUrl;
    }

    public Bitmap getCombinedBitmap(){
        return combinedBitmap;
    }

    public Data withImageButton(ImageButton btn){
        this.imageButton = btn;
        return this;
    }

    public Data withSrcBitmap(Bitmap bitmap){
        this.srcBitmap = bitmap;
        return this;
    }

    public Data withIconUrl(String url){
        this.dstIconUrl = url;
        return this;
    }

    public Data withDstBitmap(Bitmap bitmap){
        this.dstBitmap = bitmap;
        return this;
    }

    public Data withCombinedBitmap(Bitmap bitmap){
        this.combinedBitmap = bitmap;
        return this;
    }
}

Process you buttons with class Data:

Observable.from(myImageButtons)
        .map(new Func1<MyImageButton, Data>() {
            @Override
            public Data call(MyImageButton myImageButton) {
                return new Data(myImageButton, myImageButton.getIconUrl());
            }
        })
        .map(new Func1<Data, Data>() {
            @Override
            public Data call(Data data) {
                return data.withDstBitmap(ImageLoader.getInstance().loadImageSync(data.getDstIconUrl(), new ImageSize(60, 60)));
            }
        })
        .filter(new Func1<Data, Boolean>() {
            @Override
            public Boolean call(Data data) {
                return data.getDstBitmap() != null;
            }
        })
        .map(new Func1<Data, Data>() {
            @Override
            public Data call(Data data) {
                return data.withCombinedBitmap(BitmapUtils.combineImage(data.getSrcBitmap(), data.getDstBitmap(), PorterDuff.Mode.DST_OVER));
            }
        })
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                new Action1<Data>() {
                    @Override
                    public void call(Data data) {   //onNext
                        data.getImageButton().setImageBitmap(data.getCombinedBitmap());

                        //I recomend you to recycle old bitmaps if they no needed
                        data.getSrcBitmap().recycle();
                        data.getDstBitmap().recycle();
                        data.withSrcBitmap(null).withDstBitmap(null);

                        //Complex way to remove this bitmaps from cache
                        //See http://stackoverflow.com/a/19512974/1796309
                        //MemoryCacheUtils.removeFromCache(data.getDstBitmap(), ImageLoader.getInstance().getMemoryCache());
                        //DiscCacheUtils.removeFromCache(data.getDstBitmap(), ImageLoader.getInstance().getDiscCache());
                    }
                },
                new Action1<Throwable>() {
                    @Override
                    public void call(Throwable throwable) {     //onError
                        throwable.printStackTrace();
                    }
                },
                new Action0() {
                    @Override
                    public void call() {
                        //Simple way to clear ImageLoaderCache
                        ImageLoader.getInstance().clearMemoryCache();
                    }
                }
        );

Or use Lambda expressions:

Observable.from(myImageButtons)
        .map(myImageButton -> new Data(myImageButton, myImageButton.getIconUrl()))
        .map(data -> data.withDstBitmap(ImageLoader.getInstance().loadImageSync(data.getDstIconUrl(), new ImageSize(60, 60))))
        .filter(data -> data.getDstBitmap() != null)
        .map(data -> data.withCombinedBitmap(BitmapUtils.combineImage(data.getSrcBitmap(), data.getDstBitmap(), PorterDuff.Mode.DST_OVER)))
        .subscribeOn(Schedulers.computation())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(
                data -> {   //onNext
                    data.getImageButton().setImageBitmap(data.getCombinedBitmap());

                    //I recomend you to recycle old bitmaps if they no needed
                    data.getSrcBitmap().recycle();
                    data.getDstBitmap().recycle();
                    data.withSrcBitmap(null).withDstBitmap(null);

                    //Complex way to remove this bitmaps from cache
                    //See http://stackoverflow.com/a/19512974/1796309
                    //MemoryCacheUtils.removeFromCache(data.getDstBitmap(), ImageLoader.getInstance().getMemoryCache());
                    //DiscCacheUtils.removeFromCache(data.getDstBitmap(), ImageLoader.getInstance().getDiscCache());
                },
                throwable -> {     //onError
                    throwable.printStackTrace();
                },
                () -> {
                    //Simple way to clear ImageLoaderCache
                    ImageLoader.getInstance().clearMemoryCache();
                }
        );

Second way. More reactive...

Create DST Bitmap Observable:

public Observable<Bitmap> getDstIconsFromImageButtonsByIconUrls(){
    return Observable.from(myImageButtons)
            .map(new Func1<MyImageButton, String>() {
                @Override
                public String call(MyImageButton myImageButton) {
                    return myImageButton.getIconUrl();
                }
            })
            .map(new Func1<String, Bitmap>() {
                @Override
                public Bitmap call(String s) {
                    return ImageLoader.getInstance().loadImageSync(url, new ImageSize(60, 60));
                }
            });
}

Or with Lambdas:

public Observable<Bitmap> getDstIconsFromImageButtonsByIconUrls(){
    return Observable.from(myImageButtons)
            .map(myImageButton -> myImageButton.getIconUrl())
            .map(s -> ImageLoader.getInstance().loadImageSync(url, new ImageSize(60, 60)));
}

Create SRC Bitmap Observable:

public Observable<Bitmap> getSrcIcons(){
    return Observable.from(myImageButtons)
            .map(new Func1<MyImageButton, Bitmap>() {
                @Override
                public Bitmap call(MyImageButton myImageButton) {
                    return ((BitmapDrawable) myImageButton.getDrawable()).getBitmap();
                }
            });
}

Or with Lambdas:

public Observable<Bitmap> getSrcIcons(){
    return Observable.from(myImageButtons)
            .map(myImageButton -> ((BitmapDrawable) myImageButton.getDrawable()).getBitmap());
}

Use operator zip( ) link:

    Observable
            .zip(
                    Observable.from(myImageButtons),
                    getDstIconsFromImageButtonsByIconUrls(),
                    getSrcIcons(),
                    new Func3<MyImageButton, Bitmap, Bitmap, MyImageButton>() {
                        @Override
                        public MyImageButton call(MyImageButton btn, Bitmap dstBitmap, Bitmap srcBitmap) {
                            if(dstBitmap != null){
                                btn.setImageBitmap(BitmapUtils.combineImage(srcBitmap, dstBitmap, PorterDuff.Mode.DST_OVER));

                                //recycle bitmaps if you don't need them
                                dstBitmap.recycle();
                                srcBitmap.recycle();
                            }
                            return btn;
                        }
                    })
    .subscribe(
            new Action1<MyImageButton>() {
                @Override
                public void call(MyImageButton myImageButton) {
                    //do nothing
                }
            },
            new Action1<Throwable>() {
                @Override
                public void call(Throwable throwable) {
                    throwable.printStackTrace();
                }
            },
            new Action0() {
                @Override
                public void call() {
                    ImageLoader.getInstance().clearMemoryCache();
                }
            });

Or use Lambdas:

    Observable
            .zip(
                    Observable.from(myImageButtons),
                    getDstIconsFromImageButtonsByIconUrls(),
                    getSrcIcons(),
                    (btn, dstBitmap, srcBitmap) -> {
                        if(dstBitmap != null){
                            btn.setImageBitmap(BitmapUtils.combineImage(srcBitmap, dstBitmap, PorterDuff.Mode.DST_OVER));

                            //recycle bitmaps if you don't need them
                            dstBitmap.recycle();
                            srcBitmap.recycle();
                        }
                        return btn;
                    })
    .subscribe(
            myImageButton -> {
                //do nothing
            },
            throwable -> throwable.printStackTrace(),
            () -> ImageLoader.getInstance().clearMemoryCache());

P.S.

I didn't test this examples, but they should work. If you will have any problems, let me know please.

Sign up to request clarification or add additional context in comments.

1 Comment

Awesome, thanks! Your first solution is what I was trying to implement. The 2nd solution is very new to me, but I will try that, just to get some more practice with RxJava. Accepting the answer. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.