Search code examples
androidfoursquareretrofit

Retrofit: How to specify comma-separated parameters in request?


I'm trying to refactor my code to use Retrofit (from Volley) for some Foursquare API calls but haven't found a proper example that shows how to specify a query parameter that has 2 values separated by a comma.

My base url is the following:

public static final String VENUES_BASE_URL = "https://api.foursquare.com/v2/venues";

The rest of my url is like this:

"?ll=40.7,50.2&limit=50&radius=25000&v=20140909&venuePhotos=1&oauth_token=xxyyxx";

1st implementation for my interface:

public interface Fourquare {
    @GET("/explore?ll={p1},{p2}&limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx")
    Response getVenues(@Path("p1") String param1,
                   @Path("p2") String param2);

}

And then made the request like this:

RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint(ConfigConstants.VENUES_BASE_URL)
            .build();

    Fourquare fourquare = restAdapter.create(Fourquare.class);
    Response myResponse = fourquare.getVenues("50", "75");

However, the above gave me the following error:

retrofit.RetrofitError: Fourquare.getVenues: URL query string "ll={p1},{p2}&limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx" must not have replace block.

2nd implementation (After looking at some of the SO responses that use Query parameters. NOTE: Once I figure out the ll? parameter call I will have the token as a parameter):

@GET("/explore&limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx")
void getVenues(@Query("ll") String ll,
                      Callback<String> cb);

With the actual call like this:

fourquare.getVenues("50,75", new Callback<String>() {
        @Override
        public void success(String s, Response response) {
            Log.d(TAG, "Successful run!");
        }

        @Override
        public void failure(RetrofitError error) {
            Log.d(TAG, "Failed run!");
        }
    });

With the above implementation the failure() method is always getting called, so there is still something wrong with my code. Can someone please give some suggestions as to the proper way to implement this call? I'm most certain the issue is with the "ll?" parameter.

Update: After turning logging this is the final url I'm getting from Retrofit: https://api.foursquare.com/v2/venues/explore&limit=50&radius=25000&v=20140909&venuePhotos=1&oauth_token=xxyyxx?ll=30.26%2C-97.74

It looks like the Foursquare server does not like the ?ll parameter at the end of the url and it must be explicitly placed right after ../v2/venues/explore as that is working fine when a place the request through the browser.

Any solutions to get around this limitation from the API?

3rd implementation (9/17/14) With colriot suggestion I was able to resolve the 400 response code I was getting with my previous implementation. I'm still having a speed issue with GSON so looking for suggestions on how to fix that. Specifically, my Retrofit implementation is taking longer to display my results compared to Volley so I'm wondering if there is a better way to implement the callback.

Foursquare interface

public interface Fourquare {   
    @GET("/explore?limit=50&radius=25000&v=20140909&venuePhotos=1&oauth_token=xxyyxx")
    void getVenues(@Query("ll") String ll,
                Callback<Object> cb);
}

RestAdapter call

RestAdapter restAdapter = new RestAdapter.Builder()
            .setEndpoint(ConfigConstants.VENUES_BASE_URL)
            .build();

    Foursquare foursquare = restAdapter.create(Foursquare.class);

foursquare.getVenues("30.26,-97.74", new Callback<Object>() {
        @Override
        public void success(Object o, Response response) {
            Log.d(TAG, "Success!");
            // Parse response
            GsonBuilder gsonBuilder = new GsonBuilder();
            Gson gson = gsonBuilder.create();
            JsonParser parser = new JsonParser();
            String response2 = gson.toJson(o);
            JsonObject data = parser.parse(response2).getAsJsonObject();

            // Populate data model
            MetaResponse metaResponse = gson.fromJson(data.get("meta"), MetaResponse.class);
            VenuesExploreResponse myResponse = gson.fromJson(data.get("response"), VenuesExploreResponse.class);                

            // Store results from myResponse in List
        }

        @Override
        public void failure(RetrofitError error) {
            Log.d(TAG, "Failures!");
        }
    });

The current issue with the above callback implementation is that it is taking longer (about 1 second) than with Volley to parse and show the results. The GsonBuilder/Gson/JsonParser block is exactly the same as in my Volley onResponse(String response) method except for that intermediate "response2" object, so most definitely this intermediate/extra step is the bottleneck. I'm looking for suggestions on how to better implement the Gson parsing. If this might fit better as a new/separate question I'll do so.


Solution

  • So, as we already found out the problem was in ? -> & typo. But one more thing to be mentioned is that Retrofit can accept complex objects as call parameters. Then String.valueOf(object) will be called to convert object into Query/Path param.

    In your case you could define custom class like that:

    class LatLng {
      private double lat;
      private double lng;
    
      ...
    
      @Override public String toString() {
        return String.format("%.1f,%.1f", lat, lng);
      }
    }
    

    And refactor your endpoint method like that:

    @GET("/explore?limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx")
    void getVenues(@Query("ll") LatLng ll, Callback<String> cb);
    

    About parsing the answer:

    • Never create Gson objects right in callback. It's simply too heavyweight. Use the one you provide to RestAdapter.
    • Why do you mix JsonParser & Gson? They are different tools for basically the same problem.
    • Make use of Retrofit's built-in converter mechanism ;)

    From words to code:

    public class FoursquareResponse<T> {
      private MetaResponse meta;
      private T response;
      // getters
    }
    

    Altogether:

    @GET("/explore?limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx")
    void getVenues(@Query("ll") LatLng ll, Callback<FoursquareResponse<VenuesExploreResponse>> cb);
    
    ...
    
    foursquare.getVenues(LatLng.valueOf(30.26, -97.74), new Callback<FoursquareResponse<VenuesExploreResponse>>() {
        @Override 
        public void success(FoursquareResponse<VenuesExploreResponse> r, Response response) {
            MetaResponse metaResponse = r.getMeta;
            VenuesExploreResponse myResponse = r.getResponse();
    
            // Store results from myResponse in List
        }
    
        @Override
        public void failure(RetrofitError error) {
            Log.d(TAG, "Failures!");
        }
    });