Challenge: Preventing Repeat Answers

Hello guys I’d like to share my solution for the challenge mentioned in the title above if you see any issues or would like to improve my solution don’t hesitate please
(Note that my solution won’t fix rotation bug)
first i modified the Question model by adding answered property to become

data class Question(@StringRes val textResId: Int, val answer: Boolean,var answered: Boolean)

after that i set the default value for answered to false

private val questionBank = listOf(
        Question(R.string.question_australia, true,false),
        Question(R.string.question_oceans, true,false),
        Question(R.string.question_mideast, false,false),
        Question(R.string.question_africa, false,false),
        Question(R.string.question_americas, true,false),
        Question(R.string.question_asia, true,false)
    )

then i created the following function called isAnswered

private fun isAnswered(index:Int){
        if (questionBank[index].answered==true){
            trueButton.isEnabled=false
            falseButton.isEnabled=false
        }else{
            trueButton.isEnabled=true
            falseButton.isEnabled=true
        }
    }

after that I modified the nextButton listener and previousButton listener as you can see

nextButton.setOnClickListener {
            currentIndex = (currentIndex + 1) % questionBank.size
            isAnswered(currentIndex)
            updateQuestion()
        }

previousButton.setOnClickListener {
            currentIndex = (currentIndex - 1) % questionBank.size
            if (currentIndex == -1) currentIndex = questionBank.lastIndex
            isAnswered(currentIndex)
            updateQuestion()
        }

and finally I modified trueButton and falseButton to become

trueButton.setOnClickListener {
            trueButton.isEnabled=false
            falseButton.isEnabled=false
            questionBank[currentIndex].answered=true
            checkAnswer(true)
        }

falseButton.setOnClickListener {
            trueButton.isEnabled=false
            falseButton.isEnabled=false
            questionBank[currentIndex].answered=true
            checkAnswer(false)
        }

I’d like to hear your thoughts about the solution above :slight_smile:

2 Likes

This is essentially what needs to be done and accomplishes the challenge. Some possible modifications that could be made are:

First, when creating the Question data class, note how every question is always initializing the answered property to be false. We can set the default value for the 3rd parameter to be false.

data class Question(@StringRes val textResId: Int, 
                    val answer: Boolean,
                    var answered: Boolean = false)

and then nothing changes when creating the question bank, which is nice.

private val questionBank = listOf(
    Question(R.string.question_australia, true),
    Question(R.string.question_oceans, true),
    Question(R.string.question_mideast, false),
    Question(R.string.question_africa, false),
    Question(R.string.question_americas, true),
    Question(R.string.question_asia, true)
)

Next, inside of isAnswered() since the answered property is already a boolean value, we don’t need to check if it equals true and have the if/else structure. We can just set the enabled property to opposite the answered property

private fun isAnswered(index: Int) {
    val isQuestionAnswered = questionBank[index].answered
    trueButton.isEnabled = !isQuestionAnswered
    falseButton.isEnabled = !isQuestionAnswered
}

Finally, I would move the code in each click listener in to the checkAnswer method to prevent code duplication. Specifically the lines

trueButton.isEnabled = false
falseButton.isEnabled = false
questionBank[currentIndex].answered = true

Take notice that these three lines are in both click listeners and the same for both buttons.

3 Likes
// After the user answers a question, disable True and False buttons     
  private fun isAnswered() {
                falseButton.isEnabled = false
                trueButton.isEnabled = false
            }  

 // After the user presses the Next button enable them
  private fun refreshButtons() {
            trueButton.isEnabled = true
            falseButton.isEnabled = true

nextButton.setOnClickListener {
            currentIndex = (currentIndex +1) % questionBank.size
            refreshButtons()  // <---
            updateQuestion()
        }

falseButton.setOnClickListener { view : View ->
  checkAnswer(false)
  isAnswered() // <---
}

 trueButton.setOnClickListener { view : View ->
        checkAnswer(true)
        isAnswered()  // <---

}

I am happy with this simple solution.

This also works. It depends if you want to lock out the current question from repeat answers OR if you want to lock out the specific question from repeat answers (hitting next then previous still disables the buttons).

The challenge doesn’t specify which solution it is looking for.

@jpaone’s code refactor looks cleanest. Maybe one more nit-picky thing from me; when visiting the previous question the modulo operation isn’t necessary. So you can setup your listener as follows:

previousButton.setOnClickListener {
 currentIndex = currentIndex - 1
 if (currentIndex == -1) currentIndex = questionBank.lastIndex
 isAnswered(currentIndex)
 updateQuestion()
}

Its always fun deleting code I guess :smile:

previousButton.setOnClickListener {
        currentIndex = (currentIndex - 1 + questionBank.size) % questionBank.size
        ...
    }

is another way to do it

2 Likes

I maybe over complicated it.

But my solution to both challenges

class MainActivity : AppCompatActivity() {

private lateinit var trueButton: Button
private lateinit var falseButton: Button
private lateinit var nextButton: ImageButton
private lateinit var prevButton: ImageButton
private lateinit var questionTextView: TextView

private val questionBank = listOf(
    Question(R.string.question_australia, true),
    Question(R.string.question_oceans, true),
    Question(R.string.question_mideast, false),
    Question(R.string.question_africa, false),
    Question(R.string.question_americas, true),
    Question(R.string.question_asia, true)
)

private var currentIndex = 0
private var currentScore = 0.0
private var lockedQuestions = mutableListOf<Int>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    trueButton = findViewById(R.id.true_button)
    falseButton = findViewById(R.id.false_button)
    nextButton = findViewById(R.id.next_button)
    prevButton = findViewById(R.id.prev_button)
    questionTextView = findViewById(R.id.question_text_view)

    trueButton.setOnClickListener { view: View ->
        lockedQuestions.add(currentIndex)
        checkAnswer(true)
        disableButtons()
    }

    falseButton.setOnClickListener { view: View ->
        lockedQuestions.add(currentIndex)
        checkAnswer(false)
        disableButtons()
    }

    nextButton.setOnClickListener {
        currentIndex = (currentIndex + 1) % questionBank.size
        updateQuestion()
        disableButtons()
    }

    prevButton.setOnClickListener {
        if (currentIndex > 0) {
            currentIndex = (currentIndex - 1)
            updateQuestion()
        } else {
            currentIndex = questionBank.size - 1
            updateQuestion()
        }
        disableButtons()
    }

    questionTextView.setOnClickListener {
        currentIndex = (currentIndex + 1) % questionBank.size
        updateQuestion()
    }
    updateQuestion()
    disableButtons()
}

private fun updateQuestion() {
    val questionTextResId = questionBank[currentIndex].textResId
    questionTextView.setText(questionTextResId)
}

private fun checkAnswer(userAnswer: Boolean) {
    val correctAnswer = questionBank[currentIndex].answer
    val messageResId: Int

    if (userAnswer == correctAnswer) {
        messageResId = R.string.correct_toast
        currentScore++
    } else {
        messageResId = R.string.incorrect_toast
    }
    if (questionBank.size == lockedQuestions.size) {
        Toast.makeText(
            this, "${(currentScore / questionBank.size * 100).toInt()}%"
           ,
            Toast.LENGTH_SHORT
        ).show()
    } else {
        Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show()
    }
}


private fun disableButtons() {
    if (lockedQuestions.contains(currentIndex)) {
        trueButton.isEnabled = false
        falseButton.isEnabled = false
    } else {
        trueButton.isEnabled = true
        falseButton.isEnabled = true
    }

}

}

Hi!

  1. I modified Question class like @jpaone

data class Question(@StringRes val textResId: Int, val answer: Boolean, var userAnswer: Boolean = false, var answered: Boolean =false)

  1. Then I created a function to check\change “True/False” buttons state

private fun isAnswered() {
val res = questionBank[currentIndex].answered
trueButton.isEnabled = !res
falseButton.isEnabled =!res

}
  1. Finally I modified the following functions:

private fun updateQuestion() {
val questionTextResId = questionBank[currentIndex].textResId
questionTextView.setText(questionTextResId)
isAnswered()
}

and

private fun checkAnswer(userAnswer: Boolean) {
val correctAnswer = questionBank[currentIndex].answer
questionBank[currentIndex].apply {
answered = true
this.userAnswer = userAnswer
}
val messageResId = if (userAnswer == correctAnswer) {
R.string.correct_toast
} else {
R.string.incorrect_toast
}
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
.show()
isAnswered()
}

hello hello
I did both challenges by creating a Mutablemap.

Note: I know i should have make the code look better (creating extensions and functions) but for now take it as it is

private lateinit var answersMap : MutableMap<Int, Boolean>

updates the map inside the checkAnswer()

     private fun checkAnswer(userAnswer : Boolean){
            val correctAnswer = questionBank[currentIndex].answer
            val messageResId = if (userAnswer == correctAnswer){
                if (::answersMap.isInitialized){
                    answersMap.put(currentIndex,true)
                }else{
                    answersMap = mutableMapOf(currentIndex to true)
                }
                R.string.correct_toast

            } else {
                if (::answersMap.isInitialized){
                    answersMap.put(currentIndex,false)
                }else{
                    answersMap = mutableMapOf(currentIndex to false)
                }
                R.string.false_toast
            }
            Toast.makeText(this,messageResId ,Toast.LENGTH_SHORT).show()
}

inside bottoms and text view functions, i added this code to disable the bottoms and notify the user

if (::answersMap.isInitialized) {
            if (answersMap.containsKey(key = currentIndex)) {
                trueButton.isClickable = false
                falseButton.isClickable = false
                Toast.makeText(
                    this,
                    "you answered this question before, move to other questions",
                    Toast.LENGTH_LONG
                ).show()
            }else {
                trueButton.isClickable = true
                falseButton.isClickable = true
            }

last but now least, when all questions are answered i displayed the result in a snackbar and added an action to reset

 nextButton.setOnClickListener {
            if (::answersMap.isInitialized && answersMap.size == questionBank.size){
                val score = answersMap.count { it.value == true }
                val snackbar : Snackbar = Snackbar.make(it, "Your score is $score of ${questionBank.size}", Snackbar.LENGTH_INDEFINITE)
                    .setAction("Reset?"){
                        currentIndex =0
                        answersMap.clear()
                        updateQuestion()
                    }
                snackbar.show()
            }else {
                currentIndex = (currentIndex + 1) % questionBank.size
                updateQuestion()
            }
        }

Using the above code, and making changes to it, I have been able to get all but the percentages at the end to work. I am stuck here because I’m trying to understand “what” has made this work. It largely felt like walking around in a dark maze until you found the way out.

data class Question(@StringRes val textResId: Int, val answer: Boolean, var isAnswered: Boolean = false)

I understand the change to the data class.

I was also able to understand the function created here, as can be seen in the commented out code.

   private fun answered(index: Int) {
      val notAnswered = questionBank[currentIndex].isAnswered
      trueButton.isEnabled = !notAnswered
      falseButton.isEnabled = !notAnswered

      /* The above code performs the below function
       if (bon == true) {
           trueButton.isEnabled = false
           falseButton.isEnabled = false
        } else {
           trueButton.isEnabled = true
           falseButton.isEnabled = true
    }*/

along with adding

answered(currentIndex)

to all of the buttons, but did I need to add it to all of them? I don’t really know. I’m not sure why it needed to be added to the “previous” and the “next” buttons.

There was also the below fix to get the app to not crash when hitting the “previous” button at [0] in the app.

        previousButton.setOnClickListener { view: View ->
            currentIndex -= 1
            if (currentIndex == -1) currentIndex = questionBank.lastIndex
            answered(currentIndex)
            updateQuestion()
        }

But what I really can’t figure out for all the marbles in the world is the what is going on in the following block. Basically I got there by just letting AS auto correct the code for me and then somehow it made the app work the way I wanted it to. Which leaves me feeling like I haven’t learned anything about what’s going on here.

   private fun checkAnswer(userAnswer: Boolean){
        val correctAnswer = questionBank[currentIndex].answer
        questionBank[currentIndex].apply{
            if (true.also { isAnswered = it }) {
            } else {
                trueButton.isEnabled = false
                falseButton.isEnabled = false
            }
        }
        val messageResId = if (userAnswer == correctAnswer) {
            R.string.correct_toast
        } else {

The if (true.also) statement, the apply{} statement, I just don’t understand what’s going on here, why it works, and what’s being done to make it work.

If you can help break this down for me, I would really appreciate it.

After fiddling with it and working with established programmers to try and understand how the results above were able to work, I came up with a simpler solution to prevent duplicate answers. Largely it was nesting function calls to prevent redundancy and reduce code volume.

data class Question(@StringRes val textResId: Int, val answer: Boolean, var isAnswered: Boolean = false)

As you can see, this is the same as before, where variable isAnswered is set to “false”. In order to change the isAnswered variable to “true”, I built a function called “answered” and inserted it into the “checkAnswer” function. The “answered()” is below

   private fun answered() {
       questionBank[currentIndex].isAnswered = true
   }

A new function manageAnswerButtons was created to control when the buttons were on and off, according to the boolean in the data class Questions( isAnswered).

    private fun manageAnswerButtons() {
        if (questionBank[currentIndex].isAnswered) {
            trueButton.isEnabled = false
            falseButton.isEnabled = false
        } else {
            trueButton.isEnabled = true
            falseButton.isEnabled = true
        }
    }

manageAnswerButtons() was inserted into checkAnswer() and updateQuestion(), as well as at the end of onCreate().

    private fun updateQuestion() {
            val questionTextResId = questionBank[currentIndex].textResId
            questionTextView.setText(questionTextResId)
        manageAnswerButtons()
        }
    private fun checkAnswer(userAnswer: Boolean){
        val correctAnswer = questionBank[currentIndex].answer
        val messageResId = if (userAnswer == correctAnswer) {
            R.string.correct_toast
        } else {
            R.string.incorrect_toast
        }
        Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
            .show()
        answered()
        manageAnswerButtons()
    }

No superfluous code, no strange elegance to solve simple problems, nothing that is hard to follow.