Search code examples
androidkotlinmvvmandroid-livedatamutablelivedata

How To Get LiveData Value From EditText and Display It In A TextView Using Kotlin? MVVM


Overview:

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.

Problem:

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.

Code:

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)
    }
}

Solution

  • Solution:

    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())
       }
    }
         
    

    Note:

    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.