From the blog

Wrapping a Legacy Library in Rx

image from Unsplash.com by Ludde Lorentz, pictures a downward view of a winding staircase

Using an AWS S3 Upload as an Example

The Rx Universe is vast and by me largely unexplored, so there is an opportunity to learn something new every day.

The AWS S3 Android SDK does not support Rx so I decided to Rx-ify it. In this example we will add a call uploading a file directly to AWS and then will post the results to a previously implemented API.

I will not go into details on how to implement Amazon’s S3 File Transfer Utility, you can find it here.

Making REST call to an endpoint is easy, if you use Retrofit2, and I suggest you do! But it gets a little more complicated if you have to deal with a 3rd party SDK that does not return an Observable.

Here’s how I wrapped it:

private Observable<String> uploadMedia(
                                 final File media, final AwsS3Config s3Config) {

    return Observable.create(new Observable.OnSubscribe<MediaUploadModel>() {
        @Override
        public void call(final Subscriber<? super MediaUploadModel> subscriber){
                …
                TransferObserver uploadObserver = 
                                  getTransferObserver(fileKey, s3Config, media);

                uploadObserver.setTransferListener(new TransferListener() {
                    @Override
                    public void onStateChanged(int id, TransferState state) {

                        // handle transfer state changes here
                        if (state == COMPLETED) {
                            subscriber.onNext(
                                new MediaUploadModel(id, fileKey));
                            subscriber.onCompleted();
                        }
                    }

                    @Override
                    public void onProgressChanged(
                                   int id, long bytesCurrent, long bytesTotal) {
                        // hook-up progress upload here
                    }

                    @Override
                    public void onError(int id, Exception ex) {
                        // handle errors here
                        subscriber.onError(ex);
                        subscriber.onCompleted();
                    }
                });
            }
        }
    })
    .map(new Func1<MediaUploadModel, String>() {
        @Override
        public String call(MediaUploadModel mediaUploadModel) {
            // convert upload results into URL;
            …
            return uploadedUrl;
        }
    });
}

In this case I am using an intermediate model to pass the results of the file transfer to downstream receivers.

class MediaUploadModel {

    final int uploadId;
    final String fileKey;

    MediaUploadModel(int id, String key) {
        uploadId = id;
        fileKey = key;
    }
}

If your existing API call implementation looked something like this before:

public Observable<Post> uploadAPost(Map<String, String> params, File media) {
    
    return getApiService()
            .createPost(params, null);
}

Now it turns into a slightly more complex, but still very readable construct:

public Observable<Post> uploadAPost(
              Map<String, String> params, File media, AwsS3Config s3Config) {

    return uploadMedia(media, s3Config)
            .flatMap(new Func1<String, Observable<Post>>() {
                @Override
                public Observable<Post> call(String s) {
                    return getRetrofitApi()
                            .createPost(params, null);
                }
            });
}

Enjoy and happy coding!

Leave a Reply

Your email address will not be published.