Home > database >  TextViews never update
TextViews never update

Time:07-23

Here's the gist:

I'm a beginner, and am trying to create an app where you input a number and if that number matches a randomly generated number you "win".

My issue is that I have 2 textviews towards the bottom of the screen and their values never update. One (tVWinORLose) for displaying a simple "You Win/Lose", and one (tVFinalOutput) for outputting your guess and the randomly generated number. After I click the "Press to Guess" button, they both remain blank.

Assistance would be greatly appreciated! My MainActivity.kt and activity_main.xml are below.

MainActivity.kt

package com.example.randomnumbergame

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import com.example.randomnumbergame.databinding.ActivityMainBinding
import kotlin.random.Random

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        binding = ActivityMainBinding.inflate(layoutInflater)

        val button = findViewById<Button>(R.id.btn_InputGuess)
        val guess = findViewById<EditText>(R.id.eT_NumberGuess)

        button.setOnClickListener {
            val guessValue = guess.text.toString().toInt()
            val randomNumber = Random.nextInt(0, 10)

            if (guessValue == randomNumber) {
                binding.tVWinORLose.text = "You Win!"
                binding.tVFinalOutput.text = "Your guess was "   guessValue.toString()   " and the random number generated was "   randomNumber.toString()   "."
            }else {
                binding.tVWinORLose.text = "You Lose!"
                binding.tVFinalOutput.text = "Your guess was "   guessValue.toString()   " and the random number generated was "   randomNumber.toString()   "."
            }
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@ id/tV_Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Welcome to my Random Number Game!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.07" />

    <Button
        android:id="@ id/btn_InputGuess"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Press to Guess"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/tV_Title" />

    <EditText
        android:id="@ id/eT_NumberGuess"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Enter your guess here"
        android:inputType="textPersonName"
        android:tooltipText="Enter your guess here!"
        app:layout_constraintBottom_toTopOf="@ id/btn_InputGuess"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/tV_Title"
        app:layout_constraintVertical_bias="0.774" />

    <TextView
        android:id="@ id/tV_Directions"
        android:layout_width="230dp"
        android:layout_height="76dp"
        android:text="A random number will be generated. If the number you guess below matches it you win!"
        android:textAlignment="center"
        app:layout_constraintBottom_toTopOf="@ id/eT_NumberGuess"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.497"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/tV_Title"
        app:layout_constraintVertical_bias="0.574" />

    <TextView
        android:id="@ id/tV_WinORLose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@ id/tV_FinalOutput"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/btn_InputGuess" />

    <TextView
        android:id="@ id/tV_FinalOutput"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/btn_InputGuess" />

</androidx.constraintlayout.widget.ConstraintLayout>

CodePudding user response:

Try to use that.

  1. Your main problem, you didnt setContentView as binding.root. That will conflict with your view elements
  2. You didnt need to create button variables, you can use it simply by using binding.
  3. Also you need to nullCheck your editText
package com.example.randomnumbergame

import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import com.example.randomnumbergame.databinding.ActivityMainBinding
import kotlin.random.Random

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btn_InputGuess.setOnClickListener {
            val guessValue = binding.eT_NumberGuess.text.toString().toInt()
            val randomNumber = Random.nextInt(0, 10)

            if (guessValue == randomNumber) {
                binding.tVWinORLose.text = "You Win!"
                binding.tVFinalOutput.text = "Your guess was "   guessValue.toString()   " and the random number generated was "   randomNumber.toString()   "."
            }else {
                binding.tVWinORLose.text = "You Lose!"
                binding.tVFinalOutput.text = "Your guess was "   guessValue.toString()   " and the random number generated was "   randomNumber.toString()   "."
            }
        }
    }
}

CodePudding user response:

Just to explain what's going on since you're a beginner (sorry this ended up a bit long, I wanted to be thorough) - when you call setContentView(R.layout.activity_main) in an Activity, that takes your activity_main.xml file and inflates it (basically creates all the View objects described in the XML and hooks them together) and then sets it as the Actvity's view, its layout. That's what gets displayed, and you can call findViewById to look up a particular View in that hierarchy, like your TextViews, so you can do things with them.

But you're using view binding here - the way that works is an ActivityMainBinding class gets automatically generated from your activity_main.xml file. That has a property for every View in your layout that has an id.

When you generate an instance of that class from an inflated layout (which is the actual hierarchy of View objects remember), all the views with IDs get assigned to their properties, so you can just do binding.textView1 to access that TextView in your view hierarchy. It's like a handy reference - give it a layout, and it gives you back this binding object with all the view references connected up and ready to use. It also has a special property, root, which is the top-level view - the main layout that contains everything else, basically, the thing that got inflated.

There's two ways to create that binding object:

// Use one of the inflate methods and pass it a LayoutInflater
// This inflates the XML file and creates all the View objects
val binding = ActivityMainBinding.inflate(layoutInflater)

// Or, take an -already inflated view hierarchy- and bind to it.
// That basically means "connect up all the view properties to the existing views"
val binding = ActivityMainBinding.bind(view)

Which one you want to do depends on whether you have an existing view hierarchy you just want to bind to (e.g. if you've used setContentView, or one of the Activity/Fragment constructors that takes an XML layout ID) or if you want to inflate it from scratch (e.g. if you're overriding onCreateView in a Fragment where you need to inflate the view hierarchy anyway).

The important thing is, if you're inflating it yourself, you need to make sure you hand over the view hierarchy to whatever needs to display it - e.g. setContentView(binding.root) in an Activity, or returning binding.root from onCreateView (which expects a View - that's what the method is about, creating one!)


That's pretty much the whole deal with inflation and view binding, I hope the general idea makes sense! So with that in mind, your actual problem here is this:

setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)

Remember how setContentView inflates a view hierarchy, and displays it? It creates a set of Views, and those are the ones you see and interact with. But on the next line, you're inflating another view hierarchy - another, completely separate set of views.

And because you're accessing some through the binding object (the second set) and some through findViewById on the Activity's view (the first set), neither hierarchy is set up properly. Your buttons on the displayed layout work, but they update the TextViews in the second layout that you can't see.

If you called setContentView again, passing binding.root, before you did this setup then it would update to show this other hierarchy, wire everything up on it, and everything would be cool. (Ideally you wouldn't be doing findViewById if you're using view binding anyway, but so long as the right hierarchy is displayed when you do it, it'll work.)

But you shouldn't really call setContentView twice, you should only do it once - inflating a layout more than once is a sign you're doing something wrong, and it can lead to confusing behaviour and mixups like this! So you want to inflate the layout once, and here's a couple of ways you could do it:

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

or

setContentView(R.layout.activity_main)
binding = ActivityMainBinding.bind(view) // this is the Activity's (freshly added) view hierarchy

Whichever way you want to do it is up to you! Arguably, the first version is safer - you explicitly inflate a layout through its binding class, and then you tell the Activity to display the results, whatever they are. In the second version, you're specifying the layout twice - once by referencing R.layout.activity_main, and then again through ActivityMainBinding. If someone makes a mistake, and those don't match up, you could get a problem (e.g. bind failing to find the views it's expecting, because you gave it a view hierarchy inflated from the wrong layout file)

  • Related