Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you resolve a relative Uri?

Given an absolute Uri and a relative Uri or relative path, how do you get the absolute Uri pointing to the relative location?

For example, suppose we have the Uri for file:///android_asset/dir, pointing to a location in our assets. Further suppose that elsewhere, we have a relative path of /foo. The absolute Uri for that relative path should be file:///android_asset/foo. Is there something on Uri, or elsewhere in the Android SDK, that I am missing that give me that result Uri?

Uri.withAppendedPath() is not the answer, as all it seems to do is handle trailing directory separators:

Uri abs=Uri.parse("file:///android_asset/");
Uri rel=Uri.withAppendedPath(abs, "/foo");
Assert.assertEquals("file:///android_asset/foo", rel.toString());
  // actually returns file:///android_asset//foo

Uri abs2=Uri.parse("file:///android_asset/dir");
Uri rel2=Uri.withAppendedPath(abs2, "/foo");
Assert.assertEquals("file:///android_asset/foo", rel2.toString());
  // actually returns file:///android_asset/dir//foo

Uri.Builder, via buildUpon() on Uri, is not an improvement:

Uri rel3=abs.buildUpon().appendPath("/foo").build();
Assert.assertEquals("file:///android_asset/foo", rel3.toString());
  // actually returns file:///android_asset/%2Ffoo

Uri rel4=abs.buildUpon().appendEncodedPath("/foo").build();
Assert.assertEquals("file:///android_asset/foo", rel4.toString());
// actually returns file:///android_asset//foo

In a pinch I can try using java.net.URL and its URL(URL context, String spec) constructor, or just roll some code for it, but I was hoping to stay in the realm of Android Uri values if possible, just for any quirks differentiating URL and Uri.

like image 279
CommonsWare Avatar asked Sep 15 '15 15:09

CommonsWare


2 Answers

Android doesn't make this easy.

In my case, I had to take a base url that may or may not have an included path:

http://www.myurl.com/myapi/

...and append a REST API method path, like:

api/where/agencies-with-coverage.json

...to produce the entire url:

http://www.myurl.com/myapi/api/where/agencies-with-coverage.json

Here's how I did it (compiled from various methods within the app - there may be a simpler way of doing this):

String baseUrlString = "http://www.myurl.com/myapi/";
String pathString = "api/where/agencies-with-coverage.json";
Uri.Builder builder = new Uri.Builder();
builder.path(pathString);

Uri baseUrl = Uri.parse(baseUrlString);

// Copy partial path (if one exists) from the base URL
Uri.Builder path = new Uri.Builder();
path.encodedPath(baseUrl.getEncodedPath());

// Then, tack on the rest of the REST API method path
path.appendEncodedPath(builder.build().getPath());

// Finally, overwrite builder with the full URL
builder.scheme(baseUrl.getScheme());
builder.encodedAuthority(baseUrl.getEncodedAuthority());
builder.encodedPath(path.build().getEncodedPath());

// Final Uri
Uri finalUri = builder.build();

In my case, the Builder classes for the API client code assembled the path prior to combining it with the baseURL, so that explains the order of things above.

If I've pulled together the above code correctly, it should handle port numbers as well as spaces in the URL string.

I pulled this source code from the OneBusAway Android app, specifically the ObaContext class. Note that this code on Github also handles the additional case where the user typed in a baseUrl (String serverName = Application.get().getCustomApiUrl() in the above code) that should override the region base URL (mRegion.getObaBaseUrl()), and the user-entered URL may not have http:// in front of it.

The unit tests that pass for the above code on Github, including cases where port numbers and spaces are included in the baseUrl and path, and the leading/trailing / may or may not be included, are here on Github. Related issues on Github where I was banging my head on the wall to try and get this all to work - 72, 126, 132.

I haven't tried this with non-HTTP URIs, but I believe it may work more generally.

like image 139
Sean Barbeau Avatar answered Oct 19 '22 05:10

Sean Barbeau


There is an equivalent to urllib.parse.urljoin (Python) in Android URI.create(baseUrl).resolve(path).

import java.net.URI

URI.create("https://dmn92m25mtw4z.cloudfront.net/helpvids/f3_4/hls_480/480.m3u8")
    .resolve("0.ts")
// output:
// https://dmn92m25mtw4z.cloudfront.net/helpvids/f3_4/hls_480/0.ts

Sean Barbeau answer returns wrong URL, it's just appending the 0.ts to the url.

like image 1
Jeeva Avatar answered Oct 19 '22 04:10

Jeeva