0

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.

elliotching
  • 972
  • 1
  • 10
  • 33

2 Answers2

1

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" />
Sekiro
  • 1,537
  • 1
  • 7
  • 21
0

Thanks to @rcs 's answer, and in extend of that I found this answer, which is useful as well to solve. On top of it, let's say I have 2 dependencies need to listen, I can do follow on top of utility provided from the answer, this CombinedLiveData can listen to any source that provided from constructor arguments, i.e. emailInput and phoneInput.

    val isPhoneEnabled: CombinedLiveData<Boolean> = CombinedLiveData(emailInput, phoneInput) {
        checkEnablity()
    }

    private fun checkEnablity(): Boolean {
        return emailInput.value?.length ?: 0 > 6 &&
                phoneInput.value?.length ?: 0 == 0
    }
elliotching
  • 972
  • 1
  • 10
  • 33