Then I thought about how to preserve that alreadyAnswered value over the activities destruction during a device rotation. I thought about making Question serializable and put them into my bundel but instead I decided to save all the questions’ booleans in an extra array and store that one in the bundle.
@Override
protected void onSaveInstanceState(Bundle outState) {
...
boolean[] questionsAnswered = new boolean[mQuestionBank.length];
for (int i = 0; i < mQuestionBank.length; i++)
questionsAnswered[i] = mQuestionBank[i].isAlreadyAnswered();
outState.putBooleanArray(KEY_INDEX_QUESTIONS_ANSWERED, questionsAnswered);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
...
if (savedInstanceState != null) {
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX_CURRENT_INDEX, 0);
boolean[] questionsAnswered =
savedInstanceState.getBooleanArray(KEY_INDEX_QUESTIONS_ANSWERED);
assert questionsAnswered != null;
for (int i = 0; i < questionsAnswered.length; i++)
mQuestionBank[i].setAlreadyAnswered(questionsAnswered[i]);
}
...
}
Does someone have concerns and suggestions for improvement for this solution or an alternative one?
Also, how did you handle the warning about getBooleanArray possibly yielding null (the line after which I wrote the assert statement)?
I believe this was a very smart way to do things, the Getter and Setter within the Question class is brilliant.
I put them in a HashMap and when clicked, I added the true value to the map, then when the user answers the question the button are disabled/enabled accordingly.
private HashMap<Question, Boolean> mAnsweredQuestions = new HashMap<>();
mAnsweredQuestions.put(mQuestionBank[mCurrentIndex], true);
if (mAnsweredQuestions.containsKey(mQuestionBank[mCurrentIndex])) {
mTrueButton.setEnabled(false);
mFalseButton.setEnabled(false);
} else {
mTrueButton.setEnabled(true);
mFalseButton.setEnabled(true);
}
I am having issues with the screen rotating though. It seems the addresses that it points to in a memory are different so having the serializable isn’t the way I should do it.
My solution is similar to the first one, but I stored the answers in an int[]. I also created a method to set the buttons enabled or disabled.
Added code to QuizActivity.java
private static final String QUESTION_LIST = “question_list”;
if (savedInstanceState != null) {
// Save Current Index of question
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0);
// Save whether question has been answered. Do not let user answer again.
int[] mQuestionAnswerArray = savedInstanceState.getIntArray(QUESTION_LIST);
for (int i=0; i<mQuestionBank.length; i++) {
mQuestionBank[i].setAnswered(mQuestionAnswerArray[i]);
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i(TAG, "onSaveInstnaceState");
savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
int[] mQuestionAnswerArray = new int[mQuestionBank.length];
for (int i=0; i<mQuestionBank.length; i++) {
mQuestionAnswerArray[i] = mQuestionBank[i].isAnswered();
}
savedInstanceState.putIntArray(QUESTION_LIST, mQuestionAnswerArray);
}
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
setButtons();
}
private void setButtons() {
if (mQuestionBank[mCurrentIndex].isAnswered() > 0) {
// make buttons disabled
mTrueButton.setEnabled(false);
mFalseButton.setEnabled(false);
} else {
mTrueButton.setEnabled(true);
mFalseButton.setEnabled(true);
}
}
private void checkAnswer(boolean userPressedTrue) {
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
int messageResId = 0;
if (userPressedTrue == answerIsTrue) {
mQuestionBank[mCurrentIndex].setAnswered(2);
messageResId = R.string.correct_toast;
} else {
mQuestionBank[mCurrentIndex].setAnswered(1);
messageResId = R.string.incorrect_toast;
}
setButtons();
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show();
}
Question.java
private int mAnswered;
public Question(int textResId, boolean answerTrue) {
mTextResId = textResId;
mAnswerTrue = answerTrue;
mAnswered = 0;
}
public int isAnswered() { return mAnswered; }
public void setAnswered(int answered) { mAnswered = answered; }
*** I would probably change isAnswered to getAnswered. I try to only use is when returning boolean values.
To prevent the warning on getBooleanArray, wouldn’t you need to initialize the values to false first? I did that in my example by initializing the mAnswered value in the constructor as it wasn’t used by the end user, just a tracking answer mechanism.
This is an interesting problem to solve. There are many ways to do this. This is how I have done it, hopefully it is easy to follow:
First step - create a boolean array to keep track of which questions have been answered: private boolean[] mQuestionsAnswered = new boolean[mQuestionBank.length];
Second step - in checkAnswer() method, set mQuestionsAnswered[mCurrentIndex] = true to specify that the question in that position has been answered. Also, at the same time, we have to immediately set the buttons’ enabled state to false:
and called it in :
private void checkAnswer(boolean userPressedTrue) {
…
if (userPressedTrue == answerIsTrue) {
…
setButtonEnabled(false);
} else {
…
setButtonEnabled(false);
}
It works for me. Could you please paste your code?
You need to make sure to preserve the mQuestionsAnswered array in onSaveInstanceState(Bundle outState) {...} and get it immediately at the beginning of onCreate(Bundle savedInstanceState)
Also, I call updateQuestion() in onCreate(Bundle savedInstanceState) so make sure you call this piece of code before it: if (savedInstanceState != null) { mCurrentIndex = savedInstanceState.getInt(QUESTION_INDEX_KEY); mQuestionsAnswered = savedInstanceState.getBooleanArray(QUESTIONS_ANSWERED_KEY); }
ljubinkovicd’s answer is best.(I don’t know why someone thinks it doesn’t work). The OP’s solution can work too if other parts are correct, but it is confusing, and not very well.
This is the best solution, but one catch: boolean[] getBooleanArray (String key) may return null
and unexpectedly redefine your mQuestionsAnswered
So, I do check:
Works great, thanks ljubinkovicd. Both mCurrentIndex and mQuestionAnswered data were saved correctly in bundle. But the true and false buttons defaulted back to enabled when screen rotated. @ ntristo the fix is to call the checkAnswer method after both true and false buttons are inflated. This will check mQuestionAnswered when screen is rotated and disable clickable button if the current question was answered.
Hi Everyone, Thank you all for submitting your questions ans answers it really helped me out to come to a solution.
However i still encounter a problem after i complete the quiz and rotate my device. The app just crashes. I don’t know it has to do with the onSavedInstance Bundle but it’s the last method to be called before it dies.
private static final String KEY_SCORE = "score";
// Boolean array of answered indexes
private boolean[] mQuestionsAnswered = new boolean[mQuestionBank.length];
// score
private int mScore = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0);
mQuestionsAnswered = savedInstanceState.getBooleanArray(KEY_QUESTIONS_ANSWERED);
mScore = savedInstanceState.getInt(KEY_SCORE);
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i(TAG, "onSavedInstanceState");
//writes the value of mCurrentIndex to the bundle with KEY_INDEX = index
savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
savedInstanceState.putBooleanArray(KEY_QUESTIONS_ANSWERED, mQuestionsAnswered);
savedInstanceState.putInt(KEY_SCORE, mScore);
}
private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getTextResId();
mQuestionTextView.setText(question);
if(mQuestionsAnswered[mCurrentIndex] == true) {
mTrueButton.setEnabled(false);
mFalseButton.setEnabled(false);
}
}
private void checkAnswer(boolean userPressedTrue) {
mQuestionBank[mCurrentIndex].setAnswered(true);
mQuestionsAnswered[mCurrentIndex] = mQuestionBank[mCurrentIndex].isAnswered();
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
int messageResId = 0;
if (userPressedTrue == answerIsTrue) {
messageResId = R.string.correct_toast;
mScore++;
}
else {
messageResId = R.string.incorrect_toast;
}
Toast answerToast = Toast.makeText(this, messageResId, Toast.LENGTH_SHORT);
answerToast.show();
/**
* Final Score
*/
for (boolean answered : mQuestionsAnswered)
{
if(!answered) return;
}
int scorePercent = Math.round(((float) mScore / (float) mQuestionBank.length) * 100);
String finalScore = "You Scored " + scorePercent + "%";
Toast scoreToast = Toast.makeText(this, finalScore, Toast.LENGTH_LONG);
scoreToast.setGravity(Gravity.TOP, 0, 400);
scoreToast.show();
}