TL;DR: In the second part of this tutorial, you will learn how to integrate your Android application with a backend API. For starters, you will spin up a simple REST API (you will have different alternatives to achieve that), then you will adjust your app to replace the static to-do list with one provided by this API. In the end, you will leverage the app integration with Auth0 to make it use a private (also referred to as secured) endpoint to persist new to-do items. You can find the final code developed in this article in the following GitHub repository: Android Tutorial, Part 2: Building and Securing Your First App.

"Learn how to build and secure Android apps and how to integrate them with backend APIs!"

Previously, on Part 1

In the first part of this tutorial, you started by scaffolding a new Android project with the help of Android Studio, then you configured your app to use the Android Material component library to enhance the User Interface (UI). After that, you learned how to show a static list of to-do items in your app and how to create a form to enable users to insert new items. In the end, you learned how to handle user registration and authentication in your Android app with Auth0.

If you haven't followed the instructions on the previous part, you can fork and clone this GitHub repository to get a copy of the app created there. However, before proceeding, make sure you follow the instructions on the Creating and configuring an Auth0 account and Adding and configuring the Auth0 dependency sections to configure the app with your Auth0 properties.

Spinning Up a Backend API

In this section, you will spin up a backend API locally to support your Android app. This API will provide to your app two endpoints:

  • a public one that returns a list of to-do items;
  • and a private one that allows authenticated users to add new items to this list.

With this API, you will have the opportunity to learn how to handle these two different types of endpoints (public and private) in your Android app.

To run this API, you have two alternatives. You can either clone a GitHub repository and use Node.js to run the API, or you can use Docker to fetch an image from Docker Hub and run it in a container. Feel free to choose the alternative that suits your better.

Using Node.js and NPM to run the backend API

If you prefer to run the API with Node.js, you will have to clone this GitHub repository, then you will have to use NPM to install its dependencies and run it in your machine. The following commands will help you achieve that:

# clone the repository
git clone https://github.com/auth0-blog/to-dos-api-express.git

# move into it
cd to-dos-api-express

# install the dependencies
npm install

# run it locally
npm start

Note: As this project depends on an in-memory MongoDB database to run, the npm install command might take a few seconds to download this database and complete the operation.

Using Docker to run the backend API

If you prefer using Docker instead of Node.js and NPM, you can use the following command to create a containerized instance of the backend API:

# use docker to run the backend API
docker run -p 3001:3001 -d --name to-dos-api auth0blog/to-dos-api-express

Note: To run the command above, you will need Docker installed in your machine. If you don't have this tool yet and would prefer using this approach, check this resource.

This command will download this Docker image and use it to run a container called to-dos-api that listens on port 3001.

Testing the backend API

After following the instructions above to run the backend API, you can issue HTTP requests to the public endpoint to test if everything is working as expected. For example, if you are on Unix-like systems (e.g., Ubuntu, Fedora, or Mac OS), you can issue the following curl command to test the API:

# issuing a GET HTTP request to the public endpoint
curl localhost:3001

If you prefer using a graphical HTTP client (like Postman or Insomnia), you will have to configure it to issue a GET HTTP request to http://localhost:3001.

Issuing a GET HTTP request to the backend API.

Consuming Backend APIs in Android Apps

After spinning up the backend API, you are ready to start working on your Android app again. In this section, you will replace the static to-do list that your app loads with one provided by the API. To issue requests to your API, you will use a popular Android HTTP library called Volley.

So, back on Android Studio, you will open the build.gradle file and update it as follows:

// ./app/build.gradle

dependencies {
    // ... leave the rest untouched and add ...
    implementation 'com.android.volley:volley:1.1.1'
}

Then, you will click on the Sync Now link that the IDE shows to download Volley. After that, you will open the AndroidManifest.xml file and update it as follows:

<!-- ./app/src/main/AndroidManifest.xml -->

<manifest ...>

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        ...
        android:usesCleartextTraffic="true">
        <!-- activities definition -->
    </application>

</manifest>

You are making two changes in this file:

  • First, you are adding a setting (uses-permission) that tells the Android device that your app needs INTERNET to function.
  • Second, you are adding a property (usesCleartextTraffic) to your app definition to tell the Android device that your app is allowed to use insecure HTTP connections. Without this configuration, you would need to use HTTPS in your backend API while testing the application.

Note: It is highly recommended that you do not publish a production-ready Android app with the usesCleartextTraffic="true" property. To release secure applications, you must ensure that your backend APIs can handle HTTPS connections and that your apps use them.

After making these adjustments to your app, the next thing you will do is to create an entity model to represent the to-do item returned by the backend API. To do so, create a new class called ToDoItem inside the com.auth0.todo package and add the following code to it:

// ./app/src/main/java/com/auth0/todo/ToDoItem.java

package com.auth0.todo;

public class ToDoItem {
    private final String id;
    private final String message;

    ToDoItem(String _id, String message) {
        this.id = _id;
        this.message = message;
    }

    public String getId() {
        return id;
    }

    public String getMessage() {
        return message;
    }
}

As you can see, the to-do items that the API return are quite simple. They have only two properties: id and message. The id property is the identifier of an item on the backend API, and the message property is the description of this item. You will use this entity to parse the results returned by the API and, on the next section, to serialize data while pushing new items to it.

With that in place, the next thing you will have to do is to open the ToDoListAdapter class and replace its code with this:

// ./app/src/main/java/com/auth0/todo/util/ToDoListAdapter.java

package com.auth0.todo.util;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.auth0.todo.R;
import com.auth0.todo.ToDoItem;

import java.util.ArrayList;
import java.util.List;

public class ToDoListAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private List<ToDoItem> toDoList = new ArrayList<>();

    public ToDoListAdapter(Context context) {
        inflater = LayoutInflater.from(context);
    }

    @Override
    public View getView(int position, View view, ViewGroup parent) {
        ToDoItem toDoItem = (ToDoItem) getItem(position);
        if (view == null) {
            view = inflater.inflate(R.layout.to_do_item, null);
        }

        TextView textView = view.findViewById(R.id.to_do_message);
        textView.setText(toDoItem.getMessage());

        return view;
    }

    @Override
    public Object getItem(int position) {
        return toDoList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public int getCount() {
        return toDoList.size();
    }

    public void setToDoList(List<ToDoItem> toDoList) {
        this.toDoList = toDoList;
        notifyDataSetChanged();
    }
}

The changes you are making here are related to replacing String with the new entity class (i.e., with ToDoItem). You need this because, instead of handling lists of strings, your app will now handle lists of instances of this entity class.

Lastly, open the MainActivity class and replace its code with this:

// ./app/src/main/java/com/auth0/todo/MainActivity.java

package com.auth0.todo;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;

import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.Volley;
import com.auth0.todo.identity.AuthAwareActivity;
import com.auth0.todo.util.ToDoListAdapter;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.List;

import androidx.appcompat.app.AlertDialog;

public class MainActivity extends AuthAwareActivity implements Response.Listener<JSONArray>, Response.ErrorListener {
    private ToDoListAdapter toDoListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // create and configure the adapter
        this.toDoListAdapter = new ToDoListAdapter(this);
        ListView microPostsListView = findViewById(R.id.to_do_items);
        microPostsListView.setAdapter(toDoListAdapter);

        // issue the request
        String url = "http://10.0.2.2:3001";
        RequestQueue queue = Volley.newRequestQueue(this);
        JsonArrayRequest microPostsRequest = new JsonArrayRequest(url, this, this);
        queue.add(microPostsRequest);
    }

    @Override
    public void onResponse(JSONArray response) {
        try {
            List<ToDoItem> toDoItems = new ArrayList<>(response.length());
            for (int i = 0; i < response.length(); i++) {
                JSONObject item = response.getJSONObject(i);
                String id = item.getString("_id");
                String message = item.getString("message");

                toDoItems.add(new ToDoItem(id, message));
            }
            toDoListAdapter.setToDoList(toDoItems);
        } catch (JSONException error) {
            new AlertDialog.Builder(this)
                    .setTitle("Error")
                    .setMessage(error.toString())
                    .show();
        }
    }

    @Override
    public void onErrorResponse(VolleyError error) {
        toDoListAdapter.setToDoList(new ArrayList<ToDoItem>());
        new AlertDialog.Builder(this)
                .setTitle("Error")
                .setMessage(error.getMessage())
                .show();
    }

    public void openToDoForm(View view) {
        if (authenticationHandler.hasValidCredentials()) {
            startActivity(new Intent(this, ToDoFormActivity.class));
        }
    }
}

The list below summarizes the changes made here:

  • You are making this class implement two Volley interfaces: Response.Listener<JSONArray> and Response.ErrorListener. By implementing these interfaces, you make this class capable of handling successful and unsuccessful responses sent by the API.

  • You are making this class use Volley to issue an HTTP request to the http://10.0.2.2:3001 URL. Your app will issue this request when Android uses this class to create an activity (onCreate(Bundle savedInstanceState)). As described on the Set up Android Emulator networking resource, you can use the 10.0.2.2 IP address to make an app on an Android emulator communicate with the development machine (3001 is the port of the backend API in your machine). As such, if you are using a real device, you will have to replace this IP address.

  • You are implementing the onResponse method (which is defined by the Response.Listener interface) to transform the response sent by the API into a list of to-do items (List<ToDoItem>). Then, you are using this list to update the toDoListAdapter, which ends up updating the UI as well.

  • You are implementing the onErrorResponse method (which is defined by the Response.ErrorListener interface) to handle errors that occur while issuing requests to the API. In this case, you are clearing up the toDoListAdapter (which removes all items from the UI) and showing a quick error message (with the help of the AlertDialog class) describing the problem.

  • You are changing the way your app starts the ToDoFormActivity class. Now, instead of expecting a result back from this activity, you will let it work detached from the MainActivity. By doing that, when your users finish using the ToDoFormActivity, your app will recreate the MainActivity and will fetch a new (and up-to-date) to-do list.

After changing the MainActivity class, you are ready to see the new version of your app in action. So, click on the green play button and wait until the IDE finishes building and running the app. If everything works as expected, you will see your new app running on the Android device and showing a single to-do item: "Buy pizza!".

Android app using volley to consume a public endpoint.

Consuming Private Endpoints in Android Apps

After learning how to make your Android app consume a public endpoint, the next thing you will do is to learn how to integrate it with a private/secure endpoint. As you are already using Auth0 to secure your app, you will use this service to secure your backend API as well. Then, you will use access tokens to secure the communication (the HTTP requests) between the two parties: the app and the API.

An access token is a credential that can be used by an application to access an API. They inform the API that the bearer of the token has been authorized to access the API. - Access Tokens

Registering the API on Auth0

While integrating the Android app with Auth0, you needed to create a representation of the app in your Dashboard. Now, you will need to do a similar process, but this time you will register the backend API.

To do so, open the APIs section on your Auth0 dashboard and click on the Create API button. Then, fill in the form presented by Auth0 as follows:

  • Name: Enter a friendly name to your API (e.g., "To-Do API")
  • Identifier: Enter https://to-dos-api. Auth0 recommends using an URL-like identifier, but this doesn't need to be a real URL.
  • Signing Algorithm: Leave this field as RS256.

After that, click on the Create button to finish the process.

Running a secure backend API

Now that you have your API registered in your Auth0 dashboard, you will need to run another version of the backend API you started earlier. This new version was configured to secure the endpoint that accepts new to-do items. So, if you are running the backend API with Node.js, stop the server (Ctrl + C), and issue the following commands to start the new version:

# checkout the auth0 branch
git checkout auth0

# install the new dependencies
npm install

# set your env variables
export AUTH0_DOMAIN=...
export AUTH0_API=...

# run the application
npm start

Note that you must use your Auth0 properties to set the environment variables defined above. More specifically, set AUTH0_DOMAIN to the domain you chose while creating your Auth0 account (or tenant) (e.g., blog-samples.auth0.com), and AUTH0_API to the identifier of the API you just created (i.e., https://to-do-api).

On the other hand, if you decided to use Docker to run your backend API, you will have to run the following commands:

# stop and remove the to-dos instance
docker rm -f to-dos

# set your env variables
AUTH0_DOMAIN=...
AUTH0_API=...

# run the auth0 version of the image
docker run \
  -p 3001:3001 \
  -e "AUTH0_DOMAIN="$AUTH0_DOMAIN \
  -e "AUTH0_API="$AUTH0_API \
  --name to-dos \
  -d auth0blog/to-dos-api-express:auth0

Just like on the Node.js alternative, use your Auth0 properties to set the environment variables defined above. That is, set AUTH0_DOMAIN to the domain you chose while creating your Auth0 account (or tenant) (e.g., blog-samples.auth0.com), and AUTH0_API with the identifier of the API you just created (i.e., https://to-do-api).

After running the new version of the backend API, you can test it to see if it is working. If you are on a Unix-like system, you can check the API with the following commands:

# issue a GET request
curl http://localhost:3001

# issue a POST request
curl -X POST -H 'Content-Type: application/json' -d '{
  "message": "Buy some milk."
}' http://localhost:3001/

If everything works as expected, the first command will get you a list of to-do items, just like before. However, the second request will not work. As the new version of the API is restricting access to the POST endpoint, you will need access tokens to be able to insert items again. Luckily, you already configured your Android app to get these tokens when your users log in.

Note: If you would like to learn how to build the backend API you are using, check this resource: Node.js and Express Tutorial: Building and Securing RESTful APIs.

Adding new to-do items to the secure API

With the new backend API up and running, you can head back to your Android project and adjust it to send new to-do items to the API. There, open the ToDoFormActivity class and replace its code with this:

// ./app/src/main/java/com/auth0/todo/ToDoFormActivity.java

package com.auth0.todo;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;
import com.auth0.todo.identity.AuthAwareActivity;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;
import java.util.Map;

import androidx.appcompat.app.AlertDialog;

public class ToDoFormActivity extends AuthAwareActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_to_do_form);
    }

    public void addToDoItem(View view) {
        EditText editText = findViewById(R.id.editText);
        String message = editText.getText().toString();

        try {
            final Context context = this;
            JSONObject newPost = new JSONObject();
            newPost.put("message", message);

            String url = "http://10.0.2.2:3001";
            JsonObjectRequest postRequest = new JsonObjectRequest(Request.Method.POST, url, newPost,
                    new Response.Listener<JSONObject>() {
                        @Override
                        public void onResponse(JSONObject response) {
                            startActivity(new Intent(context, MainActivity.class));
                        }
                    },
                    new Response.ErrorListener() {
                        @Override
                        public void onErrorResponse(VolleyError error) {
                            new AlertDialog.Builder(context)
                                    .setTitle("Error")
                                    .setMessage(error.getMessage())
                                    .show();
                        }
                    }
            ) {
                @Override
                public Map<String, String> getHeaders() {
                    Map<String, String> headers = new HashMap<>();
                    headers.put("Authorization", "Bearer " + authenticationHandler.getAccessToken());
                    return headers;
                }
            };

            // Add the request to the RequestQueue.
            RequestQueue queue = Volley.newRequestQueue(context);
            queue.add(postRequest);
        } catch (JSONException e) {
            System.out.println(e.getMessage());
        }
    }
}

The new version of this file updates the implementation of the addToDoItem method to use Volley to issue a request to the backend API. As you can see, this method creates an instance of the JSONObject class and adds an attribute called message to it (this attribute gets the value that the user inputs on the text field). Then, this method uses JsonObjectRequest to issue a POST HTTP request to your backend API with the JSONObject instance.

Note: Just like the one that the MainActivity issues, this request is aiming at the http://10.0.2.2:3001 URL (which is the IP address of the host machine that is running the Android emulator). So, if you are not using an emulator (i.e., if you are using a real device), make sure you replace this URL accordingly.

If everything works as expected, Volley will call the onResponse implementation you defined, which will make your app send the user back to the main activity. If an error occurs, Volley will call the onErrorResponse implementation, which will show a new AlertDialog with the error message.

What is important to notice in that the JsonObjectRequest you are creating is overriding (@Override) the getHeaders method to add the access token (authenticationHandler.getAccessToken()) in the Authorization header. As explained in Auth0's documentation:

Access tokens should be used as a Bearer credential and transmitted in an HTTP Authorization header to the API. - Access Tokens

With that in place, you can rerun your Android app. After using your app to log in, by clicking on the + floating button you will be able to use the ToDoFormActivity to add new items to the backend API.

Conclusion

"I just built my first Android app and made it consume a secure backend API!"

Bingo! You just learned how to build and secure Android apps with ease. In this series, you started from scratch, creating a brand new Android project. Then you went all the way to make an app that allows users to log in and that communicates with a secure backend API to persist data. With this setup, you are ready to move on and start building amazing and production-ready apps.