Search code examples
androidmvvmdata-bindingmutablelivedata

Too many databinding conditional statements in Android View XML, How to move into ViewModel without additional listener


This is most simplified example I can show here:

Fragment (in case you need it, but NOT IMPORTANT!!)

class EgFragment:Fragment() {

    private lateinit var viewModel: EgViewModel
    private lateinit var binding: EgBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = EgBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = ViewModelProvider(this, ViewModelFactory(requireContext(), this))
            .get(EgViewModel::class.java)
        binding.setLifecycleOwner(viewLifecycleOwner)
        binding.vm = viewModel
    }
}

View Model

class EgViewModel: ViewModel() {



    val emailInput:MutableLiveData<String> = MutableLiveData("")
    val phoneInput:MutableLiveData<String> = MutableLiveData("")

    val phoneEnabled:MutableLiveData<Boolean> = MutableLiveData(false)

}

Layout/XML

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="vm"
            type="air.EgViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <EditText
            android:id="@+id/email"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={vm.emailInput}"
            app:layout_constraintBottom_toTopOf="@id/phone"
            app:layout_constraintTop_toTopOf="parent" />

        <EditText
            android:id="@+id/phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{vm.emailInput.length()>6}"
            android:text="@={vm.phoneInput}"
            app:layout_constraintTop_toBottomOf="@id/email" />
    </androidx.constraintlayout.widget.ConstraintLayout>


</layout>

OK, this is very simple layout, take note of this line:

            android:enabled="@{vm.emailInput.length()>6}"

With this example, after I typing more than 7 chars in email, it automatically enable 2nd EditText (phone).

I DO NOT NEED ANY onTextChangeListener nor onPropertyChangeListener for this case.

QUESTION:

How to move this line of condition vm.emailInput.length()>6 into viewModel and remain everything the same without adding any listener?

I TRIED:

Trial 01:

viewModel:

    fun updatePhoneEnabled(): Boolean {
        return emailInput.value?.length ?: 0 > 6
    }

xml:

        <EditText
            android:id="@+id/phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{vm.updatePhoneEnabled()}"
            android:text="@={vm.phoneInput}"
            app:layout_constraintTop_toBottomOf="@id/email" />

This is definitly not working because it will check on init only and not check on every typing.

Trial 02:

xml phone I changed to following:

        <EditText
            android:id="@+id/phone"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{vm.emailInput != null ? vm.updatePhoneEnabled() : false}"
            android:text="@={vm.phoneInput}"
            app:layout_constraintTop_toBottomOf="@id/email" />

This is looks weired but working solution, because char typed is updating emailInput , thus, it triggered the check on every typing. However, in real scenario, the null-checker that I put in front will be inappropriate. Refer example below.

Why I want to do this

Because my teammate is writing a line of code in this way: (of course this is in real project and I trying to ask question in most simple way)

android:clickable="@{viewmodel.timer &lt;= 0L &amp;&amp; viewmodel.taskState == TaskState.NORMAL &amp;&amp; (viewmodel.gameInfo.data != null ? viewmodel.parseStringToInt(viewmodel.gameInfo.data.fishAvailable) : 0) > 0}"

I do not know how do you guys think but I honestly do not prefer all condition checking appended in this way. Hopefully I am clear. Please help. Thanks.


Solution

  • You can observe a livedata using Transformations and perform simple actions over the value Check the official docs for more information

    You can do the following in your case

        val isEnabled = Transformations.map(emailInput) {
            it?.apply {
                length > 6
            }
        }
    

    and in your XML file, you can use the isEnabled just like any other livedata

            <EditText
                android:id="@+id/phone"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:enabled="@{vm.isEnabled}"
                android:text="@={vm.phoneInput}"
                app:layout_constraintTop_toBottomOf="@id/email" />