1

So to comply with separation of responsibilities principle, I'm trying to have my retrofit method (listNearestAirports) below return a list to a presenter (in a different package). But I've noticed that is hard with retrofit, starting from having to declare the list outside the scope of my method, to the fact that I can iterate through the list within the onResponse method only (when I try to return the list, I get a null pointer exception). See my code below :

public class NearestAirports {

     List<GeoSearchResult> airportsList;

        public List<GeoSearchResult> listNearestAirports(String lat, String lng) {
            String reuquestBody = "{Escaped_JSON_request}";
            AirportApi.Factory.getInstance().getAirports(requestBody).enqueue(new Callback<AirportCodes>() {

                public void onResponse(Call<AirportCodes> call, Response<AirportCodes> response) {
                    airportsList = response.body().getGeoSearchRS().getGeoSearchResults().getGeoSearchResult();
                    //Here the list is populated successfully every time and I can iterate through it
                    System.out.println(airportsList.iterator().next().getCity());
                }

                public void onFailure(Call<AirportCodes> call, Throwable t) {
                    System.out.println("failed");
                    if (t instanceof IOException) {
                        System.out.println("actual network failure");

                    }
                    else {
                        System.out.println("conversion issue);
                    }
                }
            });
//Should return list, but at this point the list is null
            return airportsList;
        }
}

I want to return a list to main() , so I can do the following:

        NearestAirports nearest = new NearestAirports();
        airportsList = nearest.listNearestAirports(lat, lng);
        System.out.println(airportsList.iterator().next().getCity());

        for (GeoSearchResult airport : airportsList) {
            System.out.print(airport.getId() + ", ");
            System.out.println(airport.getName());
        } 

What's the easiest way to return the response from retrofit? Do I need to use sterilization? or what's the best way to separate getting the response from retrofit from processing and presenting the response?

==========================*Update*=========================

The problem seems to be related to accessing inner classes I've tried to declare a variable outside the inner class, hoping that I can assign that variable the Response I'm getting from retrofit. This failed because: 1. I get an error message : variable accessed from an inner class must be declared final. Fine so 2. I declare it final, but then I get another error message : this variable cannot be assigned because it's declared final. There's a trick discussed here, that did not work for my specific situation: Local variable access to inner class needs to be declared final

I find Trying to separate the logic and retrofit response away from Activities difficult, because eventually I want to display the response in an activity/fragment and that's heavily dependant a view object, doing things in the UI thread etc.

How do you resolve this for a simple app without resorting to Rx java and Android's dataBinding ViewModel etc.?

Tlink
  • 875
  • 2
  • 14
  • 30
  • The code line `return airportsList;` returns a null object because the server request isn't finished yet. Java will jump right to the return line after starting the request. The solution would be: after the server responds and the conversion is done, it'll jump into the `onResponse()` callback. If you put the `return airportsList;` in the `onResponse()` callback, you can return the list of airports. – peitek Dec 02 '17 at 07:23
  • Thanks @peitek for the answer. I've tried moving the return statement into the the onResponse(), but that prompts the IDE to ask me to declare a return type for "onRespone()" – Tlink Dec 02 '17 at 16:14

1 Answers1

2

the problem is you're trying to implement synchronous behavior with asynchronous code. one could argue that you'd be better off picking one approach and using it consistently.

to be more specific, either:

Option 1 Refactor your service API (Retrofit) for synchronous behavior

instead of using Retrofit with callbacks, perform the network calls synchronously instead so you can immediately handle the response:

public class AirportApi {
    public Call<AirportCodes> getAirports(@Body...);
}

public class NearestAirports {
    public List<GeoSearchResult> listNearestAirports(String lat, String lng) {
        final Response<AirportCodes> airportCodes = AirportApi.Factory.getInstance()
            .getAirports(requestBody)
            .execute();

        // interrogate response and transform AirportCodes into List<GeoSearchResult>
        // don't forget to account for failures
}

Option 2 - Refactor your consumer code to work with asynchronous data types (ie Future, Single, etc)

(as an example...) change the API to return an RxJava Single. then call the API from the point you need the data and subscribe to the results of the call invocation.

public class AirportApi {
    public Single<AirportCodes> getAirports(@Body...);
}

public class AirportDisplayActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AirportApi.Factory.getInstance()
            .getAirports(requestBody)
            .subscribe(
                onSuccess = {...},
                onError = {...}
            );
    }
}
homerman
  • 3,369
  • 1
  • 16
  • 32