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>
2
Answers
Try to use that.
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 anActivity
, that takes youractivity_main.xml
file and inflates it (basically creates all theView
objects described in the XML and hooks them together) and then sets it as the Actvity’sview
, its layout. That’s what gets displayed, and you can callfindViewById
to look up a particularView
in that hierarchy, like yourTextView
s, 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 youractivity_main.xml
file. That has a property for everyView
in your layout that has anid
.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 dobinding.textView1
to access thatTextView
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:
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 usedsetContentView
, or one of the Activity/Fragment constructors that takes an XML layout ID) or if you want toinflate
it from scratch (e.g. if you’re overridingonCreateView
in aFragment
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 anActivity
, or returningbinding.root
fromonCreateView
(which expects aView
– 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:
Remember how
setContentView
inflates a view hierarchy, and displays it? It creates a set ofView
s, 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 throughfindViewById
on the Activity’sview
(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, passingbinding.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 doingfindViewById
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:or
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 throughActivityMainBinding
. 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)