Android and Model-View-Controller
In this chapter, you are going to upgrade GeoQuiz to present more than one question

To make this happen, you are going to add a class named Question to the GeoQuiz project. An instance of this class will encapsulate a single true-false question.
Then, you will create an array of Question objects for QuizActivity to manage.
Creating a New Class
In the project tool window, right-click the com.bignerdranch.android.geoquiz package and select New → Java Class. Name the class Question and click OK

public class Question {
private int mTextResId;
private boolean mAnswerTrue;
public Question(int textResId, boolean answerTrue) {
mTextResId = textResId;
mAnswerTrue = answerTrue;
}
}
The Question class holds two pieces of data: the question text and the question answer (true or false).
These variables need getter and setter methods. Rather than typing them in yourself, you can have Android Studio generate the implementations for you.
Generating getters and setters
public class Question {
private int mTextResId;
private boolean mAnswerTrue;
...
public int getTextResId() {
return mTextResId;
}
public void setTextResId(int textResId) {
mTextResId = textResId;
}
public boolean isAnswerTrue() {
return mAnswerTrue;
}
public void setAnswerTrue(boolean answerTrue) {
mAnswerTrue = answerTrue;
}
}
Object diagram for GeoQuiz

Model-View-Controller and Android
A model object holds the application’s data and “business logic.”
View objects know how to draw themselves on the screen and how to respond to user input, like touches. A simple rule of thumb is that if you can see it on screen, then it is a view.
Controller objects tie the view and model objects together. They contain “application logic.” Controllers are designed to respond to various events triggered by view objects and to manage the flow of data to and from model objects and the view layer.
MVC flow with user input

Updating the View Layer
Now that you have been introduced to MVC, you are going to update GeoQuiz’s view layer to include a NEXT button.

So the changes you need to make to the view layer are:
Remove the android:text attribute from the TextView. You no longer want a hardcoded question to be part of its definition.
Give the TextView an android:id attribute. This widget will need a resource ID so that you can set its text in QuizActivity’s code.
Add the new Button widget as a child of the root LinearLayout.
New button... and changes to the text view
<LinearLayout ... >
<TextView
android:id="@+id/question_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp" />
<LinearLayout ... >
...
</LinearLayout>
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button" />
</LinearLayout>
Return to res/values/strings.xml. Rename question_text and add a string for the new button.
<string name="app_name">GeoQuiz</string>
<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="next_button">Next</string>
<string name="correct_toast">Correct!</string>
While you have strings.xml open, go ahead and add the strings for the rest of the geography questions that will be shown to the user.
<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="question_oceans">The Pacific Ocean is larger than
the Atlantic Ocean.</string>
<string name="question_mideast">The Suez Canal connects the Red Sea
and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The Amazon River is the longest river
in the Americas.</string>
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest
freshwater lake.</string>
...
Notice that you use the escape sequence \' in the last value to get an apostrophe in your string.
Updating the Controller Layer
Open QuizActivity.java. Add variables for the TextView and the new Button. Also, create an array of Question objects and an index for the array.
Adding variables and a Question array
private Button mTrueButton;
private Button mFalseButton;
private Button mNextButton;
private TextView mQuestionTextView;
private Question[] mQuestionBank = new Question[] {
new Question(R.string.question_australia, true),
new Question(R.string.question_oceans, true),
new Question(R.string.question_mideast, false),
new Question(R.string.question_africa, false),
new Question(R.string.question_americas, true),
new Question(R.string.question_asia, true),
};
private int mCurrentIndex = 0;
...
Wiring up the TextView
public class QuizActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_quiz);
mQuestionTextView = (TextView) findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
mTrueButton = (Button) findViewById(R.id.true_button);
...
}
}
Wiring up the new button
public class QuizActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mFalseButton.setOnClickListener(new View.OnClickListener() {
...
}
});
mNextButton = (Button) findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
}
});
}
}
Encapsulating with updateQuestion()
public class QuizActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mQuestionTextView = (TextView) findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
...
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
updateQuestion();
}
});
updateQuestion();
}
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
}
}
Run GeoQuiz and test your new NEXT button
Now that you have the questions behaving appropriately, it is time to turn to the answers. At the moment, GeoQuiz thinks that the answer to every question is “true.” Let’s rectify that. Here again, you will implement a private method to encapsulate code rather than writing similar code in two places.
The method that you are going to add to QuizActivity is:
private void checkAnswer(boolean userPressedTrue)
Adding checkAnswer(boolean)
public class QuizActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
}
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
}
private void checkAnswer(boolean userPressedTrue) {
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
int messageResId = 0;
if (userPressedTrue == answerIsTrue) {
messageResId = R.string.correct_toast;
} else {
messageResId = R.string.incorrect_toast;
}
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
.show();
}
}
Calling checkAnswer(boolean)
public class QuizActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mTrueButton = (Button) findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkAnswer(true);
}
});
mFalseButton = (Button) findViewById(R.id.false_button);
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
checkAnswer(false);
}
});
...
}
...
}
Challenge
Adding an Icon
Add Next and Previous Icons instead of text, some information you will need to know are:
The suffixes on these directory names refer to the screen pixel density of a device:
Suffixes
Description
mdpi
medium-density screens (~160dpi)
hdpi
high-density screens (~240dpi)
xhdpi
extra-high-density screens (~320dpi)
xxhdpi
extra-extra-high-density screens (~480dpi)
Adding an icon to the NEXT button
<LinearLayout ... >
...
<LinearLayout ... >
...
</LinearLayout>
<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp" />
</LinearLayout>
Last updated
Was this helpful?