Android Studio Project

The Android Quiz app

In an earlier lecture I put together a Spring Boot application that could serve up questions for an online quiz. In that earlier example I used a web page as a front end for the application. In today's example we are going to replace the web front end with an Android application. The Android application will work with the Spring Boot server we have already built, using the set of GET and POST requests that the server already provides.

Three activities

Our app will consist of three activities. The first activity to appear will display a list of students to choose from.

After selecting their name from the list and clicking the Continue button, the app will move to the second activity, which will display a list of possible quizzes to take.

The user will select the quiz they want to take and then press the Continue button. This takes them to the third activity.

The third activity will display the quiz questions one at a time. For each question the activity will display the text of the question and then provide four buttons for the four possible answers. The user will press the button for the answer they choose. Pressing an answer button will cause the app to post the user's answer back to the Spring Boot server. Once that is done, the activity will display the next question.

This process continues until the user has answered all of the questions in the quiz. After answering the last question the app will take the user back to the second activity where they have the option of selecting a different quiz to take.

Configuring the project

We are going to start our Android Studio project in the same way that we started the previous two examples by selecting the Empty Views Activity project type. Since we will need a total of three views for our app we can start by adding two additional views and setting up the user interface for all three activities. The first two activities will need to display lists of items: we will use the Android ListView for this. (In the designer the ListView object appears under the Legacy category.)

Before we dive into the specifics of the code there are some configuration steps we will need to complete. The first special configuration step we need is to bring a couple of libraries into the project. To help with the communication with the Spring Boot server we will be using the Gson library to help translate Java objects to and from JSON text. To send the GET and POST requests to the server we will also be making use of the Volley library.

To add special purpose libraries to an Android Studio project we have to access the Gradle build system's settings. These appear in the Gradle Scripts section of the project. The script we need to modify is the build.gradle.kts file. In that file there is a section for dependencies: we need to add these two lines to that section:

implementation("com.android.volley:volley:1.2.1")
implementation("com.google.code.gson:gson:2.8.2")

Another special configuration file we need to modify is the app's manifest file, which appears in the Manifests folder in the project. In this file we need to make two changes. The first is to add the line

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

before the start of the <application> element. This line gives our app permission to access the Internet.

The second change is to add an entry

android:usesCleartextTraffic="true"

to the <application> element itself. This option gives our app permission to communicate with the Spring Boot server using unsecure http traffic in place of the more secure https protocol.

Setting up the List Views

Our app will use ListView elements to display the list of students and the list of quizzes. Both of these ListViews will need some special configuration in the designer. With the ListView selected in the designer we need to access the choiceMode and listSelector properties. The choiceMode needs to be set to singleChoice, while the listSelector needs to be set equal to a highlight color to use when the user clicks on an element in the list.

Plain Java objects

Since we are going to be sending data back and forth from the Spring Boot server we will need to set up a number of Java classes for each type of object the server may send us. I went into the Spring Boot server project and copied the code for the Student, Quiz, Question, and Answer classes and added them to the Android Studio project.

We need to make sure that the classes we are working with on both ends of the network are identical in their structure, otherwise we will not be able to send objects back and forth to the server.

The first activity

The first activity needs to display a list of students for the user to pick from. When the activity starts up will immediately send a request to the server for that list of students. Here is how that process works.

The starting point for any activity is its onCreate() method, which is responsible for initializing everything in the activity. At the very end of the onCreate() method I put a call to this method:

private void requestStudents() {
        String url = "http://10.0.2.2:8085/students";
        StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        showStudents(response);
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(getBaseContext(), "Could not fetch students.", Toast.LENGTH_SHORT).show();
            }
        });

// Add the request to the RequestQueue.
        queue.add(stringRequest);
    }

This method uses the Volley library to send a GET request to the server to fetch the list of Students. To send a GET request to a server we will make use of the Volley StringRequest class. After constructing the StringRequest object we will place that request object on a Volley RequestQueue. Once the request object is placed in the request queue the Volley libary will take over and manage the process of sending the request to the server and receiving the response that comes back.

One of the parameters we are providing the StringRequest constructor is a Response.Listener object. This object contains an onResponse() method that will receive the response from the server as a String. The body of that onResponse() method passes that JSON response text to the next method I have written, the showStudents() method.

private void showStudents(String json) {
        students = null;
        String[] studentStrs = null;

        ListView studentList = (ListView) findViewById(R.id.student_list);

        students = gson.fromJson(json,Student[].class);
        studentStrs = new String[students.length];
        for(int n = 0;n < studentStrs.length;n++) {
            studentStrs[n] = students[n].getName();
        }


        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                android.R.layout.simple_list_item_1, studentStrs);
        studentList.setAdapter(adapter);

        studentList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView,
                                    View view, int i, long l) {
                // remember the selection
                selected_student = i;
            }
        });
    }

This code receives the raw JSON code that the server sent us, turns it into an array of Student objects, and displays those students in a ListView.

To do the initial translation of the JSON code into an array of Student objects we will work with two member variables:

private Gson gson;
private Student[] students = null;

The Gson object gson will do the translation from JSON into Java for us. The line of code that does this step is

students = gson.fromJson(json,Student[].class);

The fromJson() method requires two parameters. The first parameter is the String containing the JSON code, and the second parameter gives the method a hint to tell it that you expect it to translate the JSON code into an array of Student objects.

To display the list of students in a ListView we start by constructing an array of Strings, studentStrs, that will hold the names of each of the students. To display that list of Strings in the ListView we will use a special ArrayAdapter object.

Finally, we will need to provide a way to determine which item in the list the user has selected. Unfortunately, there is no way to ask the ListView for this information directly. The next best thing we can do is to attach a special click listener to the ListView. That object has an onItemClick() method that the ListView will call when the user clicks on an item in the list. The third parameter in that method tells us the index of the item the user clicked on, so we will go ahead and store that index in a selected_student member variable.

The last thing the first activity will need is an action method for its Continue button. Here is the code for that method:

public void nextActivity(View view) {
        if(selected_student >= 0) {
            int id = students[selected_student].getId();
            Intent intent = new Intent(this, QuizPickerActivity.class);
            intent.putExtra(STUDENT_ID, id);
            startActivity(intent);
        }
    }

This method contains pretty standard code to move on to the second activity. On piece of information we will need to pass to that second activity is the id number of the student the user selected. Since we have the index selected_student of the last item the user clicked on, we can use that index to look up the Student object for that person and get the id from that Student.

The second activity

The second activity is very similar to first, so I won't go into the code for that activity in as much detail.

Once the user has selected a quiz to take they will click the Continue button to move on to the third activity. Here is the code for the action method for that button:

public void nextActivity(View view) {
        if(selected_quiz >= 0) {
            int id = quizzes[selected_quiz].getId();
            Intent intent = new Intent(this, QuestionActivity.class);
            intent.putExtra(StudentPickerActivity.QUIZ_ID, id);
            intent.putExtra(StudentPickerActivity.STUDENT_ID,studentID);
            startActivity(intent);
        }
    }

Note that here we are passing both the id number of the student that we got from the first activity and the id number of the quiz they want to take to the third activity.

The third activity

The third activity displays quiz questions until the user has answered every question in the quiz.

The onCreate() method for this activity will start the process by calling a method to fetch the full set of questions for the quiz. Once the server sends us the questions we will convert the questions into objects and store those Question objects in a member variable questions. To keep track of our progress through the quiz we are also going to use a second member variable currentQuestion that stores the index of the question that is currently being displayed. We then call this method to display the first question:

private void showNextQuestion() {
        Question q = questions[currentQuestion];
        String choices[] = q.getChoices().split(",");
        TextView text = (TextView) findViewById(R.id.question);
        text.setText(q.getQuestion());
        Button a = (Button) findViewById(R.id.buttonA);
        a.setText(choices[0]);
        Button b = (Button) findViewById(R.id.buttonB);
        b.setText(choices[1]);
        Button c = (Button) findViewById(R.id.buttonC);
        c.setText(choices[2]);
        Button d = (Button) findViewById(R.id.buttonD);
        d.setText(choices[3]);
    }

In this method we display the text of the question in a TextView, and display each of the available choices as the text of a button.

There are four buttons for the four possible answers, so I set up four very simple action methods for them:

public void clickA(View view) {
        answerQuestion("a");
    }

    public void clickB(View view) {
        answerQuestion("b");
    }

    public void clickC(View view) {
        answerQuestion("c");
    }

    public void clickD(View view) {
        answerQuestion("d");
    }

The method that is responsible for POSTing the user's answer to the server is this one:

private void answerQuestion(String answer) {
        Answer ans = new Answer();
        ans.setQuestion(questions[currentQuestion].getId());
        ans.setStudent(studentID);
        ans.setReponse(answer);
        String url = "http://10.0.2.2:8085/answers";
        StringRequest stringRequest = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        currentQuestion++;
                        if (currentQuestion >= questions.length)
                            finish();
                        else
                            showNextQuestion();
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Toast.makeText(getBaseContext(), "Could not post answer.", Toast.LENGTH_SHORT).show();
            }
        }) {
            @Override
            public String getBodyContentType() {
                return "application/json; charset=utf-8";
            }

            @Override
            public byte[] getBody() throws AuthFailureError {
                return gson.toJson(ans).getBytes();
            }

        };
        queue.add(stringRequest);
    }

This method contains a very big and ugly chunk of Volley code needed to implement a POST. Once again, we will be constructing a StringRequest object and adding it to the Volley request queue. The two differences this time are that method has changed from Request.Method.GET to Request.Method.POST, and we now have to manage sending a body with the request. To attach a body to a request in Volley we add a couple of additional methods to the StringRequest object. These methods are the getBodyContentType() and getBody() methods. The line of code

gson.toJson(ans).getBytes()

uses the Gson library to convert our Answer object into JSON, and then turns that JSON string into an array of raw bytes that will form the body of the request.

As usual, the Volley StringRequest object will include a response listener with an onResponse() method that will get called after the server accepts our request. I put logic in that onResponse() method to move on to the next question in the quiz and display that question. If we have just answered the last question in the quiz we will call the activity's finish() method instead: that will return us back to the second activity where the user has the option to select a different quiz to take.