1

I'm learning android development I'm using RXJava with retrofit to interact with an API. So i've successfully managed to GET Data from my API. The problem now is that the program continues before the data has been fetched. How can I wait for the data to be downloaded from the API and then run some function?

I have a retrofit client as shown

public class RetrofitClient {

    private static Retrofit retrofit = null;

    private RetrofitClient() {

    }

    public static Retrofit getClient(String baseUrl) {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(SimpleXmlConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .build();
        }
        return retrofit;
    }
}

I do an API call as such.

public void getData(MyFragment Fragment) {

        mAPIService.getData()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Data>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("ERROR IN GET DATA", e.toString());
                    }
                    @Override
                    public void onNext(Data response) {
                        Log.i("MY DATA", response.toString());
                        fragment.downloadData(response);
                    }

                });
}

the problem is my android application does not wait for fragment.downloadData(response) to finish but instead continues executing code and then crashes because response is null.

I have a listener on a button that when clicked gets data from the API

    button.setOnClickListener(v ->{

        APICaller.getData(this);
        Log.i("TEST", data.ToString()); //THIS IS NULL

        });

This is the downloadData function that I run from the APICaller

  public void downloadData(Data data) {
        this.data = data;
    }
Jank29
  • 31
  • 4

1 Answers1

0

You need to be waiting for your RxJava stream to emit a value (either error or response).

Firstly, for this, if you're expecting a "single" emission, success or failure, I would use a Single. At the moment it looks your mAPIService.getData() method is returning an Observable. These are meant for streams that are going to emit multiple values which in your cause I am assuming is not what is going to happen. You only have one item that is going to be emitted so I would look at returning a Single. Not part of your question but FYI.

What I like to do is to tell my UI that whatever I'm doing is "loading", normally in the doOnSubscribe. Then the UI knows to show a loading icon or to not allow user interactions or something. Something like this (nb. notice how its after the observeOn(AndroidSchedulers.mainThread). Any time you interact with UI elements, do it on the main thread. I think this will do it):

mAPIService.getData()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doOnSubscribe(new Consumer<Disposable>() {
                            @Override
                            public void accept(Disposable disposable) throws Exception {
                                fragment.loading();
                            }
                        })

Then when either the onError or onSuccess returns in your subscription, that is where you tell the UI it's ready to proceed. You then either have a valid response or can show the error to the user.

EDIT: Reactive Programming

After your comments it looks like you do not understand reactive programming. It has a bit of a steep learning curve and I still struggle with it today.

Your APICaller class, whatever it is, should return the Observable itself. You shouldn't be passing in a Fragment to this and handling it within there as you're opening yourself up to memory leaks and its a bit of a code smell. A better option is to just return the Observable returned by mAPIService.getData(). That's it. At the moment you are pushing the Observable to another thread using Schedulers.io() which says to your main thread, carry on and don't wait for me. You then come back to this thread when a response is emitted using the code .observeOn(AndroidSchedulers.mainThread())

In your fragment is then where you handle the emission of a value or an error. Your fragment code then becomes:

button.setOnClickListener(v ->{
    APICaller.getData()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Data>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("ERROR IN GET DATA", e.toString());
                    }
                    @Override
                    public void onNext(Data response) {
                        Log.i("MY DATA", response.toString());
                        this.data = response
                        Log.i("TEST: "+data.toString());
                    }

                });

});

I would like to recommend you watch some tutorials, do some reading and I'd also like to refer you back to my point about Singles at the start

StuStirling
  • 15,601
  • 23
  • 93
  • 150
  • Ok so by onSuccess I assume you mean my onComplete()? By how do i pause the UI? should i just while(gettingData) {Sleep} – Jank29 Mar 05 '20 at 11:14
  • I have updated answer with information about `doOnSubscribe`. I'm not sure what you mean by "pause". I assume you do not want user progression until this API call has finished. In which cause, when it's loading, disable all buttons that cause actions that require the response to have returned already. – StuStirling Mar 05 '20 at 11:16
  • It's not user interaction I wanted to pause but my application crashes since it tries to do work on the response from the API before the API has returned any data. So how do I keep the caller to getData to wait? I think disposable is for RXJava2 ? is there any equivalent in RXJava1? – Jank29 Mar 05 '20 at 11:23
  • Please show me where you are calling this method from in your view by updating your question. Please provide as much info as you can – StuStirling Mar 05 '20 at 11:26
  • Alright, i updated the original post. When I click a button I download data from the API and print it in the log. – Jank29 Mar 05 '20 at 11:32