0

I'm building an Android app which uses AWS Amplify to list and download files from S3.

The sample code shows downloading is asynchronous:

Amplify.Storage.downloadFile()
    "ExampleKey",
    new File(getApplicationContext().getFilesDir() + "/download.txt"),
    result -> Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName()),
    error -> Log.e("MyAmplifyApp",  "Download Failure", error)
);

I wish to download the (possibly many) files in a background thread, and notify the main thread after all files have been downloaded (or an error occurred). Questions:

What is the best way to achieve this functionality?

P.S. I've tried the RxAmplify, which exposes RxJava Observables on which I can call blockingSubscribe(). However, the bindings are very new, and I've encountered some app-crashing uncaught exceptions using it.

bavaza
  • 10,319
  • 10
  • 64
  • 103

1 Answers1

0

With Vanilla Amplify

downloadFile() will perform its work on a background thread. Just use one of the standard approaches to go back onto the main thread, from the callback:

Handler handler = new Handler(context.getMainLooper());
File file = new File(context.getFilesDir() + "/download.txt");

Amplify.Storage.downloadFile(
    "ExampleKey", file,
    result -> {
        handler.post(() -> {
            Log.i("MyAmplifyApp", "Successfully downloaded: " + result.getFile().getName());
        });
    },
    error -> Log.e("MyAmplifyApp",  "Download Failure", error)
);

With Rx Bindings

But personally, I would use the Rx Bindings. The official documentation includes snippets for the Rx API. Here's a more tailored example:

File file = new File(context.getFilesDir() + "/download.txt");
RxAmplify.Storage.downloadFile("ExampleKey", file)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(result -> {
        Log.i("RxExample", "Download OK.");
    }, failure -> {
        Log.e("RxExample", "Failed.", failure);
    });

Running Many Downloads In Parallel

Build a collection of Singles by calling RxAmplify.Storage.downloadFile("key", local). Then, use Single.mergeArray(...) to combine them all. Subscribe to that, in the same way as above.

RxStorageCategoryBehavior storage = RxAmplify.Storage;
Single
    .mergeArray(
        storage.downloadFile("one", localOne)
            .observeResult(),
        storage.downloadFile("two", localTwo)
            .observeResult()
    )
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(/* args ... */);

Reporting Bugs

You mention that you encountered an unexpected exception. If so, please file a bug here, and I will fix it.

Jameson
  • 6,400
  • 6
  • 32
  • 53
  • Thanks. Using your example as-is, I got and error for `mergeArray()`: "reason: no instance(s) of type variable(s) T exist so that RxProgressAwareSingleOperation conforms to SingleSource extends T>". I guess I should add `observeResult()` to each `downloadFile()`? – bavaza Sep 29 '20 at 07:04
  • + I wonder how I can observe both result and progress in this case... – bavaza Sep 29 '20 at 07:06
  • Ah true, had to add `.observeResult()` on the calls inside the `mergeArray(...)`. Updated the post. To observe the progress as well, create a collection of the operations. Then, form an `Observable` by merging the various `.observeProgress()`. – Jameson Sep 29 '20 at 07:10
  • Thanks @Jameson. How can I merge an unknown number of singles? Namely, the result of a previous `list()` operation of the bucket? – bavaza Sep 29 '20 at 07:13
  • @bavaza Here's a starting place for that. You may want to create your own `Download` model that keeps the key, file, and operation all in one place. Otherwise, you can't tell which download the progress is for. https://gist.github.com/jamesonwilliams/bf66c47e46a1588daed4efa980acf7e6 – Jameson Sep 29 '20 at 07:39
  • Thanks. I get a build error for the `flatMapSingle`: "error: incompatible types: cannot infer type-variable(s) R.flatMapSingle(item -> RxAmplify.Storage.downloadFile(item, new File(path, item))); (argument mismatch; bad return type in lambda expression RxProgressAwareSingleOperation cannot be converted to SingleSource extends R>) where R,T are type-variables: R extends Object declared in method flatMapSingle(Function super T,? extends SingleSource extends R>>) T extends Object declared in class Observable". – bavaza Sep 29 '20 at 08:00
  • @bavaza oh yea use flatMap instead of flatMapSingle. – Jameson Sep 29 '20 at 08:02