12

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.

Community
  • 1
  • 1
cavega
  • 446
  • 3
  • 11
  • 23

5 Answers5

11

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!");
    }
});
colriot
  • 1,805
  • 1
  • 14
  • 21
  • Thanks for the suggestion. I'll definitely refactor that. Any suggestion on the Gson parsing part? Should I submit a new question for that? – cavega Sep 18 '14 at 14:17
  • @cavega sorry, I just haven't noticed 3rd note at the first time. – colriot Sep 18 '14 at 14:29
  • Thanks for the detailed explanation on using Retrofit's built-in Gson parsing capabilities. I took the whole JsonParser/Gson code from an answer to a Gson-specific question I made a while back. I'm definitely doing a deep dive on Retrofit's code. – cavega Sep 18 '14 at 16:16
  • 1
    @cavega welcome! Also if you want to customize parsing parameters, for example API gives you underscored field names but you use camelCase in your code, then just configure custom `Gson` instance and pass it to `RestAdapter.Builder`. – colriot Sep 18 '14 at 16:24
9

If you are using kotlin and you have var items: List<String> that should go as a query param.

Use this method to form up string:

fun itemsString(items: List<String>) = items.joinToString(separator = ",")

Your url query should be like :

@Query(value = "items", encoded = true) String items
Maksym V.
  • 2,491
  • 13
  • 24
0

Just urlencode your comma separated arguments, comma = %2

Robert Moskal
  • 19,576
  • 6
  • 57
  • 76
  • @JakeWharton I wonder if the issue could be the order in which Retrofit is ordering the parameters when making the actual http request. Does Retrofit insert the Query parameters right after the base url? Or at the end of the full URL? I'm currently getting a 400 on the code I posted. – cavega Sep 10 '14 at 03:56
  • 1
    It will append dynamic ones to the end of the static query parameters. You can turn on logging to see the full outgoing URL. – Jake Wharton Sep 10 '14 at 05:59
  • @JakeWharton I updated my question as per your logging suggestion. The server does not like the ?ll paramter at the end of the URL. Any recommendations on how to get around this limitation? – cavega Sep 10 '14 at 14:01
  • 1
    @cavega replace `&` after `/explore` with `?` – colriot Sep 17 '14 at 20:51
  • @colriot Can't believe I missed that typo. That fixed the 400 response issue. Thanks! Writing an update on my post with one remaining issue in case you want credit for the answer. – cavega Sep 18 '14 at 13:16
  • Not %2, but %2C — there are always two hexadecimal digits after percent mark. – Miha_x64 Jan 27 '17 at 09:52
0

Try to add all Query params (fixed and variable) in valid order. I mean

@GET("/explore")
Response getVenues(@Query("ll") ll, @Query("limit") limit, @Query("radius") radius, @Query("v") v, @Query("venuePhotos") venuePhotos, @Query("oauth_token") oauth_token);

and wrap call with function that have fixed params as constant

avgx
  • 644
  • 5
  • 8
  • I was able to re-solve my 400 code with @colriot suggestion, but yes, I was planning to refactor the interface by putting most if not all of the parameters as Query parameters. Thanks for the feedback. – cavega Sep 18 '14 at 13:52
0

You can use @EncodedQuery:

@GET("/explore&limit=50&radius=25000&v=20140905&venuePhotos=1&oauth_token=xxyyxx")
void getVenues(@EncodedQuery("ll") LatLng ll, Callback<String> cb);
Tomasz
  • 1,346
  • 1
  • 17
  • 26