How to make android.net.Uri encode & between query parameters to %26 - android

I have a Android app that needs to launch a web brower with a URL containing a query string. I build my Uri like this:
Uri uri = builder.scheme("https")
.authority("ids.example.com")
.appendPath("account")
.appendPath("login")
.appendQueryParameter("client_id", "seglaren")
.appendQueryParameter("scope", "openid email name")
.build();
and pass it to the browser using:
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
This launches the browser with the following URL:
https://ids.example.com/account/login?client_id=seglaren&scope=openid%20email%20name
The problem here is that the server I am calling does not accept this URL: it requires the separator between the query parameters to be encoded to "%26" instead of just "&". So it would need to be:
https://ids.example.com/account/login?client_id=seglaren%26scope=openid%20email%20name
How do I fix this?

Instead of .appendQueryParameter() you can use .encodedQuery().
encodedQuery() will be treated as if it is already encoded, thus not encoding it again. So you may insert your own string as you wish like in the example below.
String params = "client_id=seglaren%26scope=openid%20email%20name";
Uri uri = new Uri.Builder().scheme("https")
.authority("ids.artdatabanken.se")
.appendPath("account")
.appendPath("login")
.encodedQuery(params)
.build();
You may use string concatenation or a StringBuilder to make the String params dynamic if you don't want to keep it hardcoded.
Result
"https://ids.artdatabanken.se/account/login?client_id=seglaren%26scope=openid%20email%20name"
Note that androids Uri.Builder is doing the correct thing by adding &to the parameter. So the API you're using probably has a bug if it requires %26.

Related

Android Uri building ignore parameters except if at least one parameter is present

I have a strange behaviour when I click on a link in a webview that is a PDF file link.
For example: https://my.server.com/foobar.pdf
So I have made some research and this link will start a dowload in my webview.
I have a DownloadListener and a onDownloadStart method.
In it I send the URL to Android so that PDF apps on phone can open it.
My strange behaviour is here. If the link in the webview does not have parameters I am not able to add parameters in the URL but if the URL have one parameter -> my parameters are added.
Example will be more meaningful.
Here url in link is "https://my.server.com/foobar.pdf"
val uriTest = Uri.parse(url).buildUpon()
.appendQueryParameter("key1", val1)
.appendQueryParameter("key2", val2)
.build()
val intent = Intent(Intent.ACTION_VIEW, uriTest)
startActivity(intent)
So when the PDF app called by startActivity(intent) call the URL on my server I have no parameters in URL and on my server I see a call to "https://my.server.com/foobar.pdf" not to "https://my.server.com/foobar.pdf?key1=val1&key2=val2".
BUT
If the url in link is "https://my.server.com/foobar.pdf?t=t" when my Android code is executed on my server side I can see a call to ""https://my.server.com/foobar.pdf?t=t&key1=val1&key2=val2". My parameters are added in this case.
Is it normal? Am I missing something?
Thanks for your help!
---EDIT---
I also tried to add my parameters in the string directly and it is the same -> my parameters are ignored until the URL I get has one parameter.
Example: I get "https://my.server.com/foobar.pdf" and I do:
val url1 = url + "?key1=" + val1
or
val url1 = "$url?key1=$val1"
val yourUrl = StringBuilder("https://my.server.com/foobar.pdf")
val parameters = hashMapOf<String, String>()
parameters["key1"] = "val1"
parameters["key2"] = "val2"
var count = 0
for (i in parameters.entries) {
if (count == 0)
yourUrl.append("?${i.key}=${i.value}&")
else
yourUrl.append("${i.key}=${i.value}&")
count++
}
val yourNewUrl = yourUrl.substring(0, yourUrl.length - 1)
Timber.e("URL: $yourNewUrl")
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(yourNewUrl))
startActivity(intent)
Happy coding :)

How send email Android with c++ builder?

I have seen many examples in Delphi and not one a C ++ builder. I tried to recreate the code in C ++, but it flies only exception. How to use the Intent to С++?
void __fastcall TForm1::Button1Click(TObject *Sender)
{
callEmail("russia#gmail.com", "Application");
}
//---------------------------------------------------------------------------
void TForm1::callEmail(const String address, const String Subject){
JIntent* intent;
TJIntent* intentTwo;
intent = intentTwo->Create();
intent->setAction(intentTwo->JavaClass->ACTION_SEND);
intent->setFlags(intentTwo->JavaClass->FLAG_ACTIVITY_NEW_TASK);
intent->putExtra(intentTwo->JavaClass->EXTRA_EMAIL, StringToJString(address));
intent->putExtra(intentTwo->JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString('vnd.android.cursor.dir/email'));
SharedActivity()->startActivity(intent);
}
I thnik, maybe I think maybe something needs to change in androidmanifest or user-permission?
Your code is crashing because you are not constructing the Intent object correctly.
Create() is a constructor in Delphi. intent := TJIntent.Create in Delphi would be intent = new TJIntent in C++.
Also, Embarcadero uses interfaces for its iOS/Android bridge frameworks, so you should use the provided DelphiInterface<T> typedefs, such as _di_JIntent instead of JIntent* directly.
Also, JavaClass (and OCClass in iOS) is a static class property. You do not need an object instance to access it, just the class type.
Also, C++ uses single-quotes for character literals and double-quotes for string literals, whereas Delphi uses single-quotes for both. 'vnd.android.cursor.dir/email' in C++ is not a string literal, it is a multi-byte character literal instead, which is not what you want here. Use double-quotes instead.
Also, EXTRA_EMAIL must be expressed as an array of strings.
Try something more like this:
void TForm1::callEmail(const String address, const String Subject)
{
_di_JIntent intent;
intent = new TJIntent; // or: intent = TJIntent::JavaClass->init();
intent->setAction(TJIntent::JavaClass->ACTION_SEND);
// or: intent = TJIntent::JavaClass->init(TJIntent::JavaClass->ACTION_SEND);
intent->setFlags(TJIntent::JavaClass->FLAG_ACTIVITY_NEW_TASK);
TJavaObjectArray__1<_di_JString> *Recipients = new TJavaObjectArray__1<_di_JString>(1);
Recipients->Items[0] = StringToJString(address);
intent->putExtra(TJIntent::JavaClass->EXTRA_EMAIL, Recipients);
intent->putExtra(TJIntent::JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString(L"vnd.android.cursor.dir/email"));
SharedActivity()->startActivity(intent);
}
Now, that said, you really should not be using vnd.android.cursor.dir/email as the intent's MIME type. Use message/rfc822 instead, or even plain/text. But those do not limit the intent to just email clients, other apps might also support those types. To send an email using only a true email client, use ACTION_SENDTO with a mailto: URI instead. For 1 recipient, you can put the address directly in the URI and not use EXTRA_EMAIL at all. For 2+ recipients, use a mailto: URI with no address in it and use EXTRA_EMAIL for the addresses. This is mentioned in the Android documentation:
Common Intents | Email
For example:
void TForm1::callEmail(const String address, const String Subject)
{
_di_JIntent intent;
intent = new TJIntent; // or: intent = TJIntent::JavaClass->init();
intent->setAction(TJIntent::JavaClass->ACTION_SENDTO);
intent->setData(StrToJURI(L"mailto:" + address));
// or: intent = TJIntent::JavaClass->init(TJIntent::JavaClass->ACTION_SENDTO, StrToJURI(L"mailto:" + address));
intent->setFlags(TJIntent::JavaClass->FLAG_ACTIVITY_NEW_TASK);
intent->putExtra(TJIntent::JavaClass->EXTRA_SUBJECT, StringToJString(Subject));
intent->setType(StringToJString(L"message/rfc822"));
SharedActivity()->startActivity(intent);
}

Use URI builder in Android or create URL with variables

I'm developing an Android app. I need to build a URI for my app to make an API request. Unless there's another way to put a variable in a URI, this is the easiest way I've found. I found that you need to use Uri.Builder, but I'm not quite sure how to. My url is:
http://lapi.transitchicago.com/api/1.0/ttarrivals.aspx?key=[redacted]&mapid=value
My scheme is http, authority is lapi.transitchicago.com, path is /api/1.0, path segment(s) is ttarrivals.aspx, and query string is key=[redacted]&mapid=value.
My code is below:
Intent intent = getIntent();
String value = intent.getExtras().getString("value");
Uri.Builder builder = new Uri.Builder();
builder.scheme("http")
.authority("www.lapi.transitchicago.com")
.appendPath("api")
.appendPath("1.0")
.appendPath("ttarrivals.aspx")
.appendQueryParameter("key", "[redacted]")
.appendQueryParameter("mapid", value);
I understand that I can do URI.add, but how do I integrate it into the Uri.Builder? Should I add everything like URI.add(scheme), URI.add(authority) and so on? Or is that not the way to do it? Also, is there any other easier way to add a variable to a URI/URL?
Let's say that I want to create the following URL:
https://www.myawesomesite.com/turtles/types?type=1&sort=relevance#section-name
To build this with the Uri.Builder I would do the following.
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("www.myawesomesite.com")
.appendPath("turtles")
.appendPath("types")
.appendQueryParameter("type", "1")
.appendQueryParameter("sort", "relevance")
.fragment("section-name");
String myUrl = builder.build().toString();
There is another way of using Uri and we can achieve the same goal
http://api.example.org/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=7
To build the Uri you can use this:
final String FORECAST_BASE_URL =
"http://api.example.org/data/2.5/forecast/daily?";
final String QUERY_PARAM = "q";
final String FORMAT_PARAM = "mode";
final String UNITS_PARAM = "units";
final String DAYS_PARAM = "cnt";
You can declare all this the above way or even inside the Uri.parse() and appendQueryParameter()
Uri builtUri = Uri.parse(FORECAST_BASE_URL)
.buildUpon()
.appendQueryParameter(QUERY_PARAM, params[0])
.appendQueryParameter(FORMAT_PARAM, "json")
.appendQueryParameter(UNITS_PARAM, "metric")
.appendQueryParameter(DAYS_PARAM, Integer.toString(7))
.build();
At last
URL url = new URL(builtUri.toString());
Source: Udacity Android course / Sunshine app
Excellent answer from above turned into a simple utility method.
private Uri buildURI(String url, Map<String, String> params) {
// build url with parameters.
Uri.Builder builder = Uri.parse(url).buildUpon();
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.appendQueryParameter(entry.getKey(), entry.getValue());
}
return builder.build();
}
here is a good way to explain it:
there are two forms of the URI
1 - Builder(ready to be modified, not ready to be used)
2 - Built(not ready to be modified, ready to be used )
You can create a builder by
Uri.Builder builder = new Uri.Builder();
this gonna return a Builder ready to be modified like this:-
builder.scheme("https");
builder.authority("api.github.com");
builder.appendPath("search");
builder.appendPath("repositories");
builder.appendQueryParameter(PARAMETER_QUERY,parameterValue);
but to use it you have to build it first
retrun builder.build();
or however you gonna use it.
and then you have built that is already built for you, ready to use but cannot be modified.
Uri built = Uri.parse("your URI goes here");
this is ready to use but if you want to modify it you need to buildUpon()
Uri built = Uri.parse("Your URI goes here")
.buildUpon(); //now it's ready to be modified
.buildUpon()
.appendQueryParameter(QUERY_PARAMATER, parameterValue)
//any modification you want to make goes here
.build(); // you have to build it back cause you are storing it
// as Uri not Uri.builder
now every time you want to modify it you need to buildUpon() and in the end build().
so Uri.Builder is a Builder type that store a Builder in it.
Uri is a Built type that store an already built URI in it.
new Uri.Builder(); rerurns a Builder.
Uri.parse("your URI goes here") returns a Built.
and with build() you can change it from Builder to Built.
buildUpon() you can change it from Built to Builder.
Here is what you can do
Uri.Builder builder = Uri.parse("URL").buildUpon();
// here you created a builder, made an already built URI with Uri.parse
// and then change it to builder with buildUpon();
Uri built = builder.build();
//when you want to change your URI, change Builder
//when you want to use your URI, use Built
and also the opposite:-
Uri built = new Uri.Builder().build();
// here you created a reference to a built URI
// made a builder with new Uri.Builder() and then change it to a built with
// built();
Uri.Builder builder = built.buildUpon();
hope my answer helped :) <3
for the example in the second Answer I used this technique for the same URL
http://api.example.org/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=7
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("api.openweathermap.org")
.appendPath("data")
.appendPath("2.5")
.appendPath("forecast")
.appendPath("daily")
.appendQueryParameter("q", params[0])
.appendQueryParameter("mode", "json")
.appendQueryParameter("units", "metric")
.appendQueryParameter("cnt", "7")
.appendQueryParameter("APPID", BuildConfig.OPEN_WEATHER_MAP_API_KEY);
then after finish building it get it as URL like this
URL url = new URL(builder.build().toString());
and open a connection
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
and if link is simple like location uri, for example
geo:0,0?q=29203
Uri geoLocation = Uri.parse("geo:0,0?").buildUpon()
.appendQueryParameter("q",29203).build();
Using appendEncodePath() could save you multiple lines than appendPath(), the following code snippet builds up this url: http://api.openweathermap.org/data/2.5/forecast/daily?zip=94043
Uri.Builder urlBuilder = new Uri.Builder();
urlBuilder.scheme("http");
urlBuilder.authority("api.openweathermap.org");
urlBuilder.appendEncodedPath("data/2.5/forecast/daily");
urlBuilder.appendQueryParameter("zip", "94043,us");
URL url = new URL(urlBuilder.build().toString());
Best answer: https://stackoverflow.com/a/19168199/413127
Example for
http://api.example.org/data/2.5/forecast/daily?q=94043&mode=json&units=metric&cnt=7
Now with Kotlin
val myUrl = Uri.Builder().apply {
scheme("https")
authority("www.myawesomesite.com")
appendPath("turtles")
appendPath("types")
appendQueryParameter("type", "1")
appendQueryParameter("sort", "relevance")
fragment("section-name")
build()
}.toString()
You can do that with lambda expressions;
private static final String BASE_URL = "http://api.example.org/data/2.5/forecast/daily";
private String getBaseUrl(Map<String, String> params) {
final Uri.Builder builder = Uri.parse(BASE_URL).buildUpon();
params.entrySet().forEach(entry -> builder.appendQueryParameter(entry.getKey(), entry.getValue()));
return builder.build().toString();
}
and you can create params like that;
Map<String, String> params = new HashMap<String, String>();
params.put("zip", "94043,us");
params.put("units", "metric");
Btw. If you will face any issue like “lambda expressions not supported at this language level”, please check this URL;
https://stackoverflow.com/a/22704620/2057154

URL Query parameter string "=" converts to "&"

In my Android project I'm using Robospice with spring-android. Which works fine for all REST communication. But for the below request query parameter "=" is getting converted to "&". Because of this the request is getting failed.
Query String: tags=["keywords:default=hello"]
By checking the logs the request is converted as below for making call by the library.
http://XXX/rest/media/search?token=123&tags=%5B%22keywords:default&hello%22%5D
here "=" sign is converted to "&" in "keywords:default=hello"
Request Class
here tags = String.format("[\"keywords:default=%s\"]", mTag);
#Override
public MVMediaSearch loadDataFromNetwork() throws Exception
{
String search="";
if(!tags.equals(Constants.EMPTY_DATA))
search="&tags="+tags;
return getRestTemplate().getForObject( Constants.BASE_URL+"/media/search?token="+token+search, MVMediaSearch.class );
}
If I fire the URL in a browser, I'm getting error. And if I change the '&' sign to its corresponding url encoded value in browser, it works fine.
I also have the same issue.
For alternative, I use getForObject(java.net.URI, java.lang.Class).
URI uri = new URI(Constants.BASE_URL+"/media/search?token="+token+search);
getRestTemplate().getForObject(uri, MVMediaSearch.class );
http://docs.spring.io/spring/docs/3.0.x/javadoc-api/org/springframework/web/client/RestTemplate.html#getForObject(java.net.URI, java.lang.Class)
You can do something like this:
URI uri = new URI(
"http",
Constants.BASE_URL,
"/media/search?token=",
token,
search,
null);
String request = uri.toASCIIString();
take a look at THIS and see if you understand (you have to adapt to your code - this is not completely done for you)

Not able to tag photos on facebook using the android sdk

I am using the following code (from sdk examples "Hackbook") to add a tag to a photo after uploading it.
json = Util.parseJson(response);
photo_id = json.getString("id");
String relativePath = photo_id + "/tags/" + Utility.userUID;
Bundle params = new Bundle();
params.putString("x", "25");
params.putString("y", "25");
Utility.mAsyncRunner.request(relativePath, params, "POST", new TagPhotoRequestListener(), null);
However, I sometimes (not always) receive the the response from facebook as "false" instead of "true". Is there a specific reason for this?
Additionally, is there a way to tag the photo while uploading it, instead of making an additional call?

Categories

Resources