Bridge is a simple but powerful HTTP networking library for Android. It features a Fluent chainable API, powered by Java/Android's URLConnection classes for maximum compatibility and speed.
First, add JitPack.io to the repositories list in your app module's build.gradle file:
repositories {
maven { url "https://jitpack.io" }
}
Then, add Bridge to your dependencies list:
dependencies {
compile 'com.afollestad:bridge:1.3.1'
}
- Basics
- Request Headers
- Request Bodies
- Async Requests, Duplicate Avoidance, and Progress Callbacks
- Request Cancellation
- Configuration
- Cleanup
Here's a very basic GET Request
that retrieves Google's home page and saves the content to a String
.
try {
Response response = Bridge.client()
.get("http://www.google.com")
.response();
if (!response.isSuccess()) {
// If response code was not 200 OK
throw new Exception(response.code() + " " + response.phrase());
} else {
String responseContent = response.asString();
// Do something with response
}
} catch (Exception e) {
// An error occurred
}
This could be shortened to:
try {
String content = Bridge.client()
.get("http://www.google.com")
.asString();
// Do something with response
} catch (Exception e) {
// An error occurred OR response code was not 200 OK
}
Behind the scenes, this is actually performing this:
try {
String content = Bridge.client()
.get("http://www.google.com")
.response()
.throwIfNotSuccessful()
.asString();
} catch (Exception e) {
// An error occurred
}
It just takes out the need to use response()
, and it makes use of throwIfNotSuccessful()
to automatically
throw an Exception
if the response code is not 200 OK.
When passing query parameters in a URL, you can use format args:
try {
String searchQuery = "hello, how are you?";
String content = Bridge.client()
.get("http://www.google.com/search?q=%s", searchQuery)
.asString();
// Do something with response
} catch (Exception e) {
// An error occurred OR response code was not 200 OK
}
This is using Java's String.format()
method behind the scenes. The searchQuery
variable replaces
%s
in your URL. You can have multiple format args, and they don't all have to be strings (e.g. %d
for any number variable).
Args that are strings will be automatically URL encoded for you.
Retrieving response headers is simple:
Response response = // ...
String contentType = response.header("Content-Type");
Headers can also have multiple values, separated by commas:
Response response = // ...
List<String> values = response.headerList("header-name");
Since Content-Type and Content-Length are commonly used response headers, there's convenience methods to get these values:
Response response = // ...
String contentType = response.contentType();
int contentLength = response.contentLength();
The above examples all use asString()
to save response content as a String
. There are many other
formats that responses can be converted/saved to:
Response response = // ...
byte[] responseRawData = response.asBytes();
String responseString = response.asString();
// If you set this to a TextView, it will display HTML formatting
Spanned responseHtml = response.asHtml();
JSONObject responseJsonObject = response.asJsonObject();
JSONArray responseJsonArray = response.asJsonArray();
// Don't forget to recycle!
// Once you use this method once, the resulting Bitmap is cached in the Response object,
// Meaning asBitmap() will always return the same Bitmap from any reference to this response.
Bitmap responseImage = response.asBitmap();
// Save the response content to a File of your choosing
response.asFile(new File("/sdcard/Download.extension"));
Changing or adding Request
headers is pretty simple. You just use the header(String, String)
method
which can be chained to add multiple headers.
try {
String content = Bridge.client()
.get("http://www.google.com")
.header("User-Agent", "BridgeSampleProject")
.header("CustomHeader", "Hello")
.asString();
// Do something with response
} catch (Exception e) {
// An error occurred OR response code was not 200 OK
}
Default headers: see the Configuration section for info on how to set default headers that are automatically included in every request.
Here's a basic POST
request that sends plain text in the body:
try {
String postContent = "Hello, how are you?";
String response = Bridge.client()
.post("http://someurl.com/post")
.body(postContent)
.asString();
} catch(Exception e) {
// An error occurred
}
Passing a String
will automatically set the Content-Type
header to text/plain
. The body()
method takes other various forms of data, including:
- Raw
byte[]
data JSONObject
/JSONArray
Form
MultipartForm
Pipe
File
Byte arrays are obviously raw data. Passing a JSONObject
or JSONArray
will automatically set the Content-Type
header
to application/json
. Form
, MultipartForm
, and Pipe
will be discussed in the next few sections.
File
relies on the Pipe
feature, it reads the file and sets the request body to the raw contents.
Form
's are commonly used with PUT/POST requests. They're basically the same thing as query strings
with get requests, but the parameters are included in the body of the request rather than the URL.
Here's a basic example of how it's done:
try {
Form form = new Form()
.add("Username", "Aidan")
.add("Password", "Hello");
String response = Bridge.client()
.post("http://someurl.com/login")
.body(form)
.asString();
} catch (Exception e) {
// An error occurred
}
This will automatically set the Content-Type
header to application/x-www-form-urlencoded
.
A MultipartForm
is a bit different than a regular form. Content is added as a "part" to the request body.
The content is included as raw data associated with a content type, allowing you to include entire files.
Multipart forms are commonly used in HTML forms (e.g. a contact form on a website), and they can be used
for uploading files to a website.
Here's a basic example of how it's done:
try {
MultipartForm form = new MultipartForm()
.add("Subject", "Hello")
.add("Body", "Hey, how are you?")
.add("FileUpload", new File("/sdcard/Download/ToUpload.txt"));
String response = Bridge.client()
.post("http://someurl.com/post")
.body(form)
.asString();
} catch (Exception e) {
// An error occurred
}
This will automatically set the Content-Type
header to multipart/form-data
.
Note: MultipartForm
has an add()
method that accepts a Pipe
. This can be used to add parts
from streams (see the section below on how Pipe
is used). add()
for File
objects is actually using
this indirectly for you.
Bridge allows you to stream data directly into a post body:
try {
Pipe pipe = new Pipe() {
@Override
public void writeTo(OutputStream os) throws IOException {
os.write("Hello, this is a streaming example".getBytes());
}
@Override
public String contentType() {
return "text/plain";
}
};
String response = Bridge.client()
.post("http://someurl.com/post")
.body(pipe)
.asString();
} catch (Exception e) {
// An error occurred
}
Note: when you use a Pipe
as a body, the Content-Type
header will automatically be set based
on what contentType()
in the Pipe
implementation returns. If you want to override this header,
you can change it in the Pipe
or reset the header after setting the body.
Pipe
has three convenience methods that create a pre-designed Pipe
instance:
Pipe uriPipe = Pipe.forUri(this, Uri.parse(
"content://com.example.provider/documents/images/1"));
Pipe filePipe = Pipe.forFile(new File("/sdcard/myfile.txt"));
InputStream is = // ...
Pipe transferPipe = Pipe.forStream(is, "text/plain");
forUri(Context, Uri)
will read from a File URI or content URI, so basically any file on an Android device can
be read. forFile(File)
indirectly uses forUri(Context, Uri)
specifically for file:// URIs.
forStream(InputStream, String)
reads an InputStream
and transfers the content into the Pipe,
you need to specify a Content-Type value in the second parameter.
Here's a basic example of an asynchronous request:
Bridge.client()
.get("http://www.google.com")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
// An error occurred or response was not successful
} else {
String content = response.asString();
}
}
});
There's two major advantages to using async requests:
- Threads are not blocked. You can execute this code on UI thread (e.g. in an
Activity
) without freezing or errors. - Duplicate avoidance (discussed below).
You can replace get()
with post()
, put()
or delete()
too.
Duplicate avoidance is a feature in this library that allows you to avoid making multiple requests to the same URL at the same time. For an example, if you were to do this...
Bridge.client()
.get("http://www.google.com")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
// An error occurred or the response was not successful
} else {
// Use the Response object
}
}
});
Bridge.client()
.get("http://www.google.com")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
// An error occurred or the response was not successful
} else {
// Use the Response object
}
}
});
...Google would only be contacted once by the library. When that single request is complete, both callbacks would be called at the same time with the same response data.
This lets you be very efficient on bandwidth and resource usage. If this library was being used to load images
into ImageView
's, you could display 100 ImageView
's in a list, make a single request, and immediately populate
all 100 ImageView
's with the same image at the same time. Check out the sample project to see this in action.
The Callback
class has an optional progress(Request, int, int, int)
method that can be overridden to receive
progress updates for response downloading. The second int parameter represents the percentage that's been downloaded.
Bridge.client()
.get("http://someurl/bigfile.extension")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
// An error occurred or the response was not successful
} else {
// Use the Response object
}
}
@Override
public void progress(Request request, int current, int total, int percent) {
// Progress updates
}
});
Note: progress callbacks are only called if the library is able to determinate the size of the
content being downloaded. Generally, this means the requested endpoint needs to return a Content-Length
header.
This library allows you easily cancel requests:
Request request = Bridge.client()
.get("http://www.google.com")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
if (e.isCancelled()) {
// Request was cancelled
} else {
// Error occurred
}
} else {
// Use the Response
}
}
});
request.cancel();
When a request is cancelled, the RequestException
will not be null (it will say the request was cancelled),
and RequestException#isCancelled()
will return true.
The Bridge
singleton allows you to cancel managed async requests.
This code will cancel all active requests, regardless of method or URL:
Bridge.client()
.cancelAll();
The cancelAll(Method, String)
allows you to cancel all active requests that match an HTTP method
and a URL or regular expression pattern.
This will cancel all GET requests to any URL starting with http:// and ending with .png
:
Bridge.client()
.cancelAll(Method.GET, "http://.*\\.png");
.*
is a wildcard in regular expressions, \\
escapes the period to make it literal.
If you want to cancel all requests to a specific URL, you can use Pattern.quote()
to specify a regex
that matches literal text:
Bridge.client()
.cancelAll(Method.GET, Pattern.quote("http://www.android.com/media/android_vector.jpg"));
Note: if you pass null
for the first parameter (Method
), it will ignore the HTTP method when
looking for requests to cancel. In other words, you could cancel GET
, POST
, PUT
, and DELETE
requests to a specific URL.
When making a request, you can tag it with a value:
Bridge.client()
.get("http://www.google.com")
.tag("Hello!")
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e.isCancelled()) {
// Request was cancelled
} else {
// Error occurred
}
}
});
By "a value", I mean literally any type of Object. It could be a String
, int
, boolean
, etc.
You can cancel all requests marked with a specific tag value:
// Add a second parameter with a value of true to cancel un-cancellable requests
Bridge.client()
.cancelAll("Hello!");
There are certain situations in which you wouldn't want to allow a request to be cancelled. For an example,
your app may make calls to Bridge.client().cancelAll()
when an Activity
pauses; that way, all requests
that were active in that screen are cancelled. However, there may be a Service
in your app that's making requests
in the background that you would want to maintain. You can make those requests non-cancellable:
Bridge.client()
.get("http://www.google.com")
.cancellable(false)
.request(new Callback() {
@Override
public void response(Request request, Response response, RequestException e) {
if (e != null || !response.isSuccess()) {
// Error occurred
} else {
// Use the Response
}
}
});
If you tried to make a call to cancel()
on this Request
, an IllegalStateException
would be thrown.
If you really want to cancel an un-cancellable request (Request.isCancellable()
returns true), you
can force it to be cancelled with cancel(true)
.
Un-cancellable requests will be ignored by Bridge.cancelAll()
and the other variants of that method,
unless you pass true
for the force
parameter.
Bridge allows you configure various functions globally.
You can set a host that is used as the base URL for every request.
Bridge.client().config()
.host("http://www.google.com");
With Google's homepage set as the host, the code below would request http://www.google.com/search?q=Hello
:
Bridge.client()
.get("/search?q=%s", "Hello")
.asString();
Basically, the URL you pass with each request is appended to the end of the host. If you were to pass a full
URL (beginning with HTTP) in get()
above, it would skip using the host for just that request.
Default headers are headers that are automatically applied to every request. You don't have to do it yourself with every request in your app.
Bridge.client().config()
.defaultHeader("User-Agent", "Bridge Sample Code")
.defaultHeader("Content-Type", "application/json")
.defaultHeader("Via", "My App");
Every request, regardless of the method, will include those headers. You can override them at the individual request level by setting the header as you normally would.
You can configure how long the library will wait until timing out, either for connections or reading:
Bridge.client().config()
.connectTimeout(10000)
.readTimeout(15000);
You can set timeouts at the request level too:
Bridge.client()
.get("http://someurl.com/bigVideo.mp4")
.connectTimeout(10000)
.readTimeout(15000)
.asFile(new File("/sdcard/Download/bigVideo.mp4"));
The default buffer size is 1024 * 4 (4096). Basically, when you download a webpage or file, the buffer size is how big the byte array is with each pass. A large buffer size will create a larger byte array, which can affect memory usage, but it also increases the pace in which the content is downloaded.
The buffer size can easily be configured:
Bridge.client().config()
.bufferSize(1024 * 10);
Just remember to be careful with how much memory you consume, and test on various devices.
You can set the buffer size at the request level too:
Bridge.client()
.get("http://someurl.com/bigVideo.mp4")
.bufferSize(1024 * 10)
.asFile(new File("/sdcard/Download/bigVideo.mp4"));
Note: the buffer size is used in a few other places, such as pre-built Pipe
's (Pipe#forUri
, Pipe#forStream
, etc.).
By default, logging is disabled. You can enable logging to see what the library is doing in your Logcat:
Bridge.client().config()
.logging(true);
When you're done with Bridge (e.g. your app is terminating), you should call the cleanup method to avoid any memory leaks. Your app would be fine without this, but this is good practice and it helps speed up Java's garbage collection.
Bridge.cleanup();
Note: Calling this method will also cancel all active requests for you.