skull

Robert Tamayo

R
B
blog
skull

Sending a POST Request in Android

It's easy to send an ajax request with POST data from the web, but the first time I needed to do this in Android I found it was a little different. To make things more complicated, Android has deprecated the DefaultHTTPClient class, so when I wanted to reuse old code for sending a POST request, I found it was unusable.

This led me to learning about AsyncTask and HttpURLConnection.

1. AsyncTask


Sending a POST request implies multithreading. Otherwise, the application would not be able to do anything until the remote server received the request, processed the request by performing tasks such as calculations and database reads and writes, and then finally sent the data back to the application. The point is that we can't rely on how long that would take, so we need to do this asynchronously. That's where Android's AsyncTask comes in.

Here is the basic structure of an AysncTask implementation:

// in some class
private function startAsyncTask() {
    new MyAsyncTask().execute();
}
private class MyAysncTask extends AsyncTask<Void, Void, <List<MyData>> {
    @Override
    protected List<ShoppingItem> doInBackground(Void... voids) {
        try {
            // do async task
        catch {Exception e) {
           // handle it
        } finally {
            // clean up
        }
    }
    @Override
    protected void onPostExecute(List<MyData> messages) {
        // do something with the messages
    }
}

To start the async task, all you need to do instantiate it and call execute(). The AsyncTask takes care of the rest. In my application, though, I want to keep the MyAsyncTask class in a separate file, and I also want to let the calling class handle the data when onPostExecute(<T> t) is called.

Here's the new MyAsyncTask:

private public class MyAysncTask extends AsyncTask<Void, Void, <List<MyData>> {
    private AsyncTaskExecutable mCaller;
private List<MyData> mMessages;
    public MyAsyncTask(AsyncTaskExecutable caller) {
        mCaller = caller;
    }
    @Override
    protected List<MyData> doInBackground(Void... voids) {
mMessages = new ArrayList<>;
        try {
            // do async task
        catch {Exception e) {
           // handle it
        } finally {
            // clean up
        }
    }
    @Override
    protected void onPostExecute(List<MyData> messages) {
        // do something with the messages
        mCaller.onFinish(messages);
    }
}

We introduced AsyncTaskExecutable, which is an interface. We'll define that next:

public interface AsyncTaskExecutable<T> {
    void onFinish(T t);
}

Interfaces don't get much simpler than that. Now we just need to implement the interface AsyncTaskExecutable with the type List<MyData> in the calling class:

public class MyActivity extends AppCompatActivity implements AsyncTaskExecutable<List<MyData>> {
    private MyAsyncTask mFetcher;
    private List<MyData> mMessages;
    ...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startAsyncTask();
}
    private void startAsyncTask() {
        mFetcher = new MyAsyncTask()
        mFetcher.execute();
    }
    public void onFinish(List<MyData> messages) {
        mMessages = messages;
    }
    ...
}

That's all for our AsyncTask. Let's add the POST request now.

2. HttpURLConnection


To do the actual POST request, we need to create a new HttpURLConnection. We also need to configure it for both input and output operations so that we can both write data to the request and receive data when it comes back. Let's add the following to the try/catch block in MyAsyncTask:

@Override
    protected List<MyData> doInBackground(Void... voids) {
        try {
            // do async task
            URL url = new URL("http://www.roberttamayo.com/shoplist/index.php");
            HttpURLConnection client = (HttpURLConnection) url.openConnection();
            client.setRequestMethod("POST");
            client.setDoInput(true);
            client.setDoOutput(true);
        catch {Exception e) {
           // handle it
        } finally {
            // clean up
        }
    }


Next we need to add some data to the request. To write to the request, we will use BufferedWriter, OutputStream, and OutputStreamWriter. To store the data, we will use the Pair class, which is part of the Android support library. Also, in order to format the data from the Pair objects into POST data fit for http requests, we'll use a custom getPostQueryFromData() function.

@Override
    protected List<MyData> doInBackground(Void... voids) {
        try {
            URL url = new URL("http://www.<your api domain name>.com/ajax/index.php");
            HttpURLConnection client = (HttpURLConnection) url.openConnection();
            client.setRequestMethod("POST");
            client.setDoInput(true);
            client.setDoOutput(true);
            List<Pair<String, String>> params = new ArrayList<>();
            params.add(new Pair<>("action", "get_messages"));
            params.add(new Pair<>("limit", "20"));
            params.add(new Pair<>("order_by", "newest"));
            String postQuery = getPostQueryString((ArrayList<Pair<String, String>>) params);
            OutputStream outputStream = client.getOutputStream();
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
            bufferedWriter.write(postQuery);
            bufferedWriter.flush();
            bufferedWriter.close();
            outputStreamWriter.close();
        catch {Exception e) {
           // handle it
        } finally {
            // clean up
        }
    }
public String getPostQueryString(ArrayList<Pair<String, String>> params) throws UnsupportedEncodingException {
    StringBuilder builder = new StringBuilder();
    for (int i = 0; i < params.size(); i++) {
        if (i != 0) {
            builder.append('&');
        }
        builder.append(URLEncoder.encode(params.get(i).first, "UTF-8"))
                    .append('=')
                    .append(URLEncoder.encode(params.get(i).second, "UTF-8"));
    }
    return builder.toString();
}

With what we have so far, we have sent a request for the newest 20 messages from the remote server at the specified url. We just have one more thing left to do, and that is to handle the data when it comes back. We do this by first checking the response code from the connection, and if we received 200 proceed to build our messages ArrayList.

        ...
        outputStreamWriter.close();
        int responseCode = client.getResponseCode();
        String data = "";
        if (responseCode == 200) {
            String line;
            BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
            while ((line = br.readLine()) != null) {
                data += line;
            }
            JSONArray jsonItems = new JSONArray(data);
            for (int i = 0; i < jsonItems.length(); i++) {
                JSONObject item = new JSONObject(jsonItems.get(i).toString());
                MyData message = new MyData();
                message.setText(item.getString("message_text");
                message.setSender(item.getString("message_sender");
                // etc...
                mMessages.add(item);
            }
        }
        client.disconnect();
        catch {Exception e) {
           // handle it
        } finally {
            // clean up
        }
    }


This last step assumes the data comes back in JSON format, and particularly as an array of messages. The JSONArray object lets you iterate through the list and access object members by key.

And that's all! onPostExecute() gets called automatically, and then the original calling class will handle the data from there. What I have here is very basic, and even though it's asynchronous, it wouldn't really let the user know that anything is happening. So next time, I'm going to throw in 2 features: a ProgressBar that shows and hides automatically when the loading is complete, and a feature that lets the user manually trigger a reload to check for new messages.
Comments:
Leave a Comment
Submit