Monday, July 6, 2009

POST JSON from Android using HttpClient

I'm using Ruby on Rails on the server and needed to post JSON to it from Android. Here's some quick code that I wrote to do this. Hopefully it helps someone else. All of the tutorials that I found didn't work. You basically need to make sure you post data in JSON format. Just seetting parameters won't do the trick.

In my case I had to create JSON something like:
{ fan: { email : 'foo@bar.com' } }

which equates to the HTML form input:
<input name="fan[email]" type="text"/>

To POST in Android. You can use something like this code.

public static String makeRequest(String path, Map params)
throws Exception {

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpPost httpost = new HttpPost(path);
Iterator iter = params.entrySet().iterator();

JSONObject holder = new JSONObject();

while(iter.hasNext()) {
Map.Entry pairs = (Map.Entry)iter.next();
String key = (String)pairs.getKey();
Map m = (Map)pairs.getValue();

JSONObject data = new JSONObject();
Iterator iter2 = m.entrySet().iterator();
while(iter2.hasNext()) {
Map.Entry pairs2 = (Map.Entry)iter2.next();
data.put((String)pairs2.getKey(), (String)pairs2.getValue());
}
holder.put(key, data);
}

StringEntity se = new StringEntity(holder.toString());
httpost.setEntity(se);
httpost.setHeader("Accept", "application/json");
httpost.setHeader("Content-type", "application/json");

ResponseHandler responseHandler = new BasicResponseHandler();
response = httpclient.execute(httpost, responseHandler);
}

21 comments:

vinod said...

super, this rocks, had been searching for a way to stuff in post data to httpclient, didnt know that entity is to be used for the same

kanakochii said...

Thanks a lot
Can you give me some example how to use this code please? I'm new in Android and RoR. T_T

I just got some stuck with my code. I don't know how to post the string that I got from mobile user to Ruby on Rails database.

Unknown said...

Thanks alot.. StringEntity does the magic for me.

Anonymous said...

thanks, exactly what I was looking for.

Cross-Domain said...

Nice quick example - I like it! Seems StringEntity is what I was looking for too!

Jeffrey said...

Your the man. Finally found something I need here at your blog.

Daniel said...

Hi everyone!
How can we initialize the Map (2nd argument of the function) to get something like this to pass to ruby on rails?
{ post: { title : 'Title number one' } }


Thanks in advance

Localtone, LLC said...

Matt,
Isn't this just a Map of Maps? You will handle this with nested JSONObjects. The code would need to change slightly, but you would pass in a Map with a key="post" and a value of a Map with key="title" and value="title number one". You then would create the JSONObject with name of "post" and another JSONObject with a map of the "title" and "title number one" as it's value. Look at the API docs for JSONObject for further details. http://developer.android.com/reference/org/json/JSONObject.html

A Industry Spoiled Software Engineer said...

Justin,
I am doing the same thing, I am trying to post a request to Rest service hosted on localhost, the call goes but the request object is null.

Not sure whats wrong, I tried the same code with a small java program and works fine, not only call goes but also the request on api (rest wcf) is fine.

Any clue as whats is wrong.

Thanks,
Harsh

Localtone, LLC said...

H Harsh,
Without seeing your code, it's hard to know, but are you certain that the call is getting to your server running on localhost? I'd suggest doing some logging on the server side and trace that the call is actually coming back. Recall, that typically you need to make the call to 10.0.1.2. or something similar to access your service (as "localhost" on Android will loopback to the device and that is not what you want).

Unknown said...

Hey I referenced your blog (and your code ) pretty hard and gave you the kudos here http://stackoverflow.com/questions/6218143/android-post-json-using-http/6218563#6218563
I will take it down if you mind but if not I will be sure to click your ads a couple times a week. Thanks.

Localtone, LLC said...

Hey, No problem using it as long as you gave me credit. I'm glad that I could help!

Brian_E said...

On the server side, how do you pull this object back out of the request? Do you access it from the reader?

Thanks

Localtone, LLC said...

@Brian_E - I typically use Ruby on Rails ( or Grails ) as my web service implementation and they both provide a great way to access the response data that has been sent in using JSON.
I'm sure that they are just wrapping some sort of reader interface for the data stream that is coming from the client, but I would recommend looking at some sort of framework that can send and receive JSON, rather than trying to roll your own. The hard work has been done for you by these next generation server side technologies.

Manu said...

I am trying to pass this Map to the function...
Map createMap = new HashMap();
and getting this runtime error !!!

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot
be cast to java.util.Map
at RestThree.makeRequest(RestThree.java:205)
at RestThree.main(RestThree.java:241)

Localtone, LLC said...

Hi Manu,
The Map being passed in the second paramater isn't just an empty HashMap like you have defined, but is a Map of Maps.
Remember, we are trying to represent a JSON object that is nested like so:

{ fan: { email : 'foo@bar.com' } }

and you'll see in the Java code that we iterate over each entry expecting there to be something that implements the Map interface.

while(iter.hasNext()) {
Map.Entry pairs = (Map.Entry)iter.next();
String key = (String)pairs.getKey();
Map m = (Map)pairs.getValue();
}

You just need to make sure you have put a Map in the value of Map that you pass in to the function. I'm assuming since it's blank it's assuming String and thus is causing the ClassCastException you are seeing. Hope that makes sense and helps.

Gonza said...

Hi, thanks, this is what I was looking for.

What is RequestHandler for? Should I take the server response from "response" or "responseHandler"?

Previous samples of HttpClient i've seen had just execute(httppost) without that second parameter.

Another question, what's the difference between HttpClient and DefaultHttpClient?

Thank you!

Localtone, LLC said...

Response to @gonze:

What is RequestHandler for? Should I take the server response from "response" or "responseHandler"?

Well, it depends on what you want. In most cases you should just use response as this is a String. I realize now that I should have shown it's definition in the code. Sorry about that. The response variable will be set as the response of your call from the server ( JSON Data ), and then you will parse it into the objects that you are expecting.

With regards to just using .execute(httppost) without the second argument?

That works too, but in that situation you received a HttpResponse which then needs to have .getEntity() called on it and then the content read from the stream. I prefer to just use the BasicResponseHandler as this handles all of this work.
I suggest looking over first sample ( Response Handling ) sample code at: http://hc.apache.org/httpcomponents-client-ga/examples.html

Another question, what's the difference between HttpClient and DefaultHttpClient?

HttpClient is an interface. DefaultHttpClient is the concrete class implementing the interface, so DefaultHttpClient is the class you will want to use in this particular case.

Good luck! I hope this post has been helpful.

DaveSav said...

Hi.

When I get to StringEntity se = new StringEntity(holder.toString());

holder.toString() is correctly saying {"MyDC":{"uName":"myUName","uPass":"myUPass"}}

But se becomes [123, 34, 77, 121, 68, 67, 34, 58, 123, 34, 117, 78, 97, 109, 101, 34, 58, 34, 109, 121, 85, 78, 97, 109, 101, 34, 44, 34, 117, 80, 97, 115, 115, 34, 58, 34, 109, 121, 85, 80, 97, 115, 115, 34, 125, 125]

which then kills the post to my rest service.

Do you know what might be happening here?

Thanks

Dave

Kevin said...

Thanks a bunch! Rails wasn't getting my JSON, and it's because I was missing the Accept header! All fixed thanks!

Unknown said...

Hey, I just updated the example I used at StackOverflow. I just noticed it wasn't returning a string and updated it to return a HttpResponse instead.