Greetings. In my app the user can enter a number in an EditText and that number is then used as the maximum range for generating random numbers from 1 to (the number the user entered), which should then be displayed in a TextView.
When the number is entered by the user and the button is pressed to generate the random number within the TextView, it doesn't work as intended.
For instance:
Initially, the value the LiveData holds for bonusNumber
is 1 as it should be (based on the init
block), but when the button is pressed to display a random number 0 is displayed after the button is pressed to generate the random number, instead of an actual random number between 1 and whatever the user entered in the EditText.
ViewModel:
class QuickPickViewModel : ViewModel() {
private val repository = Repository()
// LiveData for the number entered by the user
private var _userBonusNumber = MutableLiveData<Int>()
val userBonusNumber: LiveData<Int>
get() = _userBonusNumber
// LiveData for the actual result of the randomly generated bonus number
private var _bonusNumber = MutableLiveData<Int>()
val bonusNumber: LiveData<Int>
get() = _bonusNumber
init {
_userBonusNumber.value = 1
_bonusNumber.value = 1
}
fun getRandomBonusNumber() {
// Set the MutableLiveData to be displayed in the TextView, to the number the user entered
_bonusNumber.value = repository.generateBonusRandomNumber(_userBonusNumber.value!!.toInt())
}
}
Fragment:
class QuickPickFragment : Fragment() {
private lateinit var binding: FragmentQuickPickBinding
private lateinit var viewModel: QuickPickViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment using data binding
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_quick_pick,
container,
false
)
viewModel = ViewModelProvider(this).get(QuickPickViewModel::class.java)
binding.quickPickViewModel = viewModel
binding.lifecycleOwner = this
return binding.root
}
}
XML Views(The Relevant Ones):
<TextView
android:id="@+id/bonus_result_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@{String.valueOf(quickPickViewModel.bonusNumber)}"
android:textAllCaps="true"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/main_result_text_view" />
<EditText
android:id="@+id/bonus_set_edit_text"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:ems="10"
android:hint="@string/enter_number_quick_pick_bonus_edit_text_hint"
android:importantForAutofill="no"
android:inputType="number"
android:text="@{String.valueOf(quickPickViewModel.userBonusNumber)}"
app:layout_constraintBaseline_toBaselineOf="@+id/bonus_set_title_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bonus_set_title_text_view" />
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:onClick="@{() -> quickPickViewModel.getRandomBonusNumber()}"
android:text="Generate"
android:textAllCaps="true"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Repository:
class Repository {
/**
* Generate random number for the bonus number within a particular range set by user
*
* [maxNum]: the number that the user entered which acts as the max range for the random numbers
*/
fun generateBonusRandomNumber(maxNum: Int): Int {
val minNum = 1
// Adding 1[minNum] to the bound makes sure the number the person entered is inclusive
return Random().nextInt(((maxNum - minNum)) + minNum)
}
}
As suggested by a comment from @AppDev I could use two-way data-binding.
My previous code:
android:inputType="number"
android:text="@{String.valueOf(quickPickViewModel.userBonusNumber)}"
Should be changed to:
android:inputType="number"
android:text="@={quickPickViewModel.userBonusNumber}"
Then my ViewModel should be updated as follows:
class QuickPickViewModel : ViewModel() {
private val repository = Repository()
val userBonusNumber = MutableLiveData<String>()
private var _bonusNumber = MutableLiveData<Int>()
val bonusNumber: LiveData<Int>
get() = _bonusNumber
init {
userBonusNumber.value = "1"
_bonusNumber.value = 1
}
fun getRandomBonusNumber() {
_bonusNumber.value =
repository.generateBonusRandomNumber(userBonusNumber.value!!.toInt())
}
}
As stated in the comments, two way data-binding isn't always safe because it exposes the actual MutableLiveData which has write capabilities, while LiveData is read-only and safer, but in my case it wasn't applicable because that data, as stated before is read only and the data entered by the user wouldn't change. Thanks to the comments I was aware of my mistake.
So it would be safer to just capture the value from the EditText using the binding object within the Fragment and then setting the TextView to the value. However for my solution above, I just went with the two way data-binding just to show it in effect since there aren't many examples online for Kotlin.
Just thought for future readers, I would leave all of this information. Take care.