Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding duplicate parameters with Retrofit v1.9.0

There's a similar question asked here, but my case is a bit different.

I'm trying to make a request similar to the following:

http://www.example.com/abc?foo=def&foo=ghi&foo=jkl&bar=xyz

I have two issues that are making things difficult. First, the repeated parameter (setting values for "foo" multiple times) is preventing the use of QueryMap (I don't have the option to pass the values in the query string differently, like as an array). Second, the query parameters I'm using are sort of dynamic, so I can't really use Query and supply it a list of values for a given parameter name since I won't know the parameter names until I am making the request.

The code I'm trying to upgrade from is using an older version of Retrofit, but somehow it has a concept of a QueryList which took a List of NameValuePairs to pass in query parameters as the name (and their values as the value) and allowing duplicate parameters. I don't see a reference to retrofit.http.QueryList anywhere in Retrofit's source code history or on the web, so I'm not sure if this was a custom addition at the time. In any case, I'm trying to find the best way to replicate that functionality in the latest version so any help would be appreciated!

like image 745
Niraj Avatar asked Feb 28 '15 00:02

Niraj


People also ask

How do you send parameters in retrofit?

You can pass parameter by @QueryMap Retrofit uses annotations to translate defined keys and values into appropriate format. Using the @Query("key") String value annotation will add a query parameter with name key and the respective string value to the request url .

What is @query in retrofit?

Retrofit uses @Query annotation to define query parameters for requests. Query parameters are defined before method parameters. In annotation, we pass the query parameter name which will be appended in the URL.

How to define query parameters in @retrofit?

Retrofit uses @Query annotation to define query parameters for requests. Query parameters are defined before method parameters. In annotation, we pass the query parameter name which will be appended in the URL. 1.1. Single or multiple query parameters Use as many @Query annotations as many parameters we want to send.

Does retrofit ignore null parameters?

Retrofit skips null parameters and ignores them while assembling the request. Keep in mind, that you can’t pass null for primitive data types like int, float, long, etc. Instead, use Integer, Float, Long, etc and the compiler won’t be grumpy.

What are @path parameters in retrofit?

They also come before method parameters. They are named replacement in a URL path segment. Path parameters may not be null. Values passed in @Path annotations modified and made URL encoded before full API path resolution. 2.1. Simple path parameter An example to use @Path parameters in Retrofit 2.

What is @querymap in retrofit?

Retrofit’s @QueryMap annotation is a convenient way to add multiple query parameters to a request without pre-defining their actual names. That gives you more freedom for requests by just passing the map with key-value-pairs to the service method, but doesn’t show you which options are available.


2 Answers

To follow-up, I was able to resolve this by using QueryMap and a bit of a hack. Basically I turn my List of NameValuePairs into a HashMap where I check if I already have the key, and if I do I append the new value for the same key to the old value. So essentially (key, value) would turn into (key, value&key=value2). This way when the query string is constructed, I'll have key=value&key=value2 as desired. In order for that to work, I need to handle the value encoding myself so that the additional ampersands and equal signs that I'm including in the value don't get encoded.

So the HashMap is constructed from the List like this:

public static HashMap<String, String> getPathMap(List<NameValuePair> params) {
    HashMap<String, String> paramMap = new HashMap<>();
    String paramValue;

    for (NameValuePair paramPair : params) {
        if (!TextUtils.isEmpty(paramPair.getName())) {
            try {
                if (paramMap.containsKey(paramPair.getName())) {
                    // Add the duplicate key and new value onto the previous value
                    // so (key, value) will now look like (key, value&key=value2)
                    // which is a hack to work with Retrofit's QueryMap
                    paramValue = paramMap.get(paramPair.getName());
                    paramValue += "&" + paramPair.getName() + "=" + URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                } else {
                    // This is the first value, so directly map it
                    paramValue = URLEncoder.encode(String.valueOf(paramPair.getValue()), "UTF-8");
                }
                paramMap.put(paramPair.getName(), paramValue);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }

    return paramMap;
}

Then my request looks like this:

@GET("/api/")
List<Repo> listRepos(@QueryMap(encodeValues=false) Map<String, String> params);

And my service call looks like this:

// Get the list of params for the service call
ArrayList<NameValuePair> paramList = getParams();

// Convert the list into a map and make the call with it
Map<String, String> params = getPathMap(paramList);
List<Repo> repos = service.listRepos(params);

I originally tried a solution using Path where I tried to manually construct the query string, but replacement blocks are not allowed in the query string so I went with this QueryMap solution. Hopefully this helps anyone else that runs into the same issue!

like image 55
Niraj Avatar answered Oct 03 '22 15:10

Niraj


Interface:

@GET()
Call<List<CustomDto>> getCustomData(@Url String url);

Request data:

 String params = "custom?";
 for (Integer id : ids) {
      params += "id=" + id.toString() + "&";
 }
 params = params.substring(0, params.length() - 1);
 api.getCustomData(params).enqueue(callback);
like image 43
Pablo Cegarra Avatar answered Oct 03 '22 14:10

Pablo Cegarra