0

I'm trying to enable/disable a button when 4 EditTexts have more than X chars and email is valid (ignore the xml, still applying styles):

  <Button
            android:id="@+id/fragment_login_button"
            android:fontFamily="@font/montserrat_regular"
            android:layout_marginTop="20dp"
            android:textColor="@android:color/white"
            android:background="@drawable/button_primary"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="20dp"
            android:enabled="@{viewModel.createAccountDetailsValid}"
            android:layout_marginRight="20dp"
            android:text="create account "
            android:onClick="@{(theView) -> handler.onCreateClick(theView, viewModel)}"
            app:layout_constraintTop_toBottomOf="@id/fragment_login_companyText"
    />

I got it working using MediatorLiveData with the 4 MutableLiveData that the button depends on, but I find that I'm going against the MVVM standards by doing this, but was the only way it works since MediatorLiveData only allows addSource if has at least 1 observer, have a look at the code:

on my View Model:

//USER DATA
val email: MutableLiveData<String> = MutableLiveData()
val name: MutableLiveData<String> = MutableLiveData()
val surname: MutableLiveData<String> = MutableLiveData()
val company: MutableLiveData<String> = MutableLiveData()
val createAccountDetailsValid: MediatorLiveData<Boolean> = MediatorLiveData()

fun populateMediator(owner: LifecycleOwner) {
        createAccountDetailsValid.observe(owner, Observer {  })
        createAccountDetailsValid.addSource(email) {
            createAccountDetailsValid.value = isCreateAccountDetailsValid()
        }
        createAccountDetailsValid.addSource(name) {
            createAccountDetailsValid.value = isCreateAccountDetailsValid()
        }
        createAccountDetailsValid.addSource(surname) {
            createAccountDetailsValid.value = isCreateAccountDetailsValid()
        }
        createAccountDetailsValid.addSource(company) {
            createAccountDetailsValid.value = isCreateAccountDetailsValid()
        }
    }

private fun isCreateAccountDetailsValid() : Boolean {
    if(email.value == null || name.value == null || surname.value == null || company.value == null) return false
    return android.util.Patterns.EMAIL_ADDRESS.matcher(email?.value!!).matches() && name.value?.length!! >= 3 && surname.value?.length!! >= 3 && company.value?.length!! >= 3
}

on my fragment:

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = DataBindingUtil.inflate(inflater, R.layout.fragment_login, container,false)
    viewModel = ViewModelProviders.of(this, viewModelFactory).get(LoginViewModel::class.java)
    binding.viewModel = viewModel
    binding.handler = LoginHandler()
    binding.setLifecycleOwner(this)
    viewModel.setLifecycleOwner(this as LifecycleOwner)
    return binding.root
}

Like I said it's working but ViewModel has a reference to View (LifecycleOwner), yes I could put the empty observer on the Fragment and don't need to pass the LifecycleOwner to the ViewModel but still doesn't feel right, maybe I'm being a bit perfectionist here but I bet there's another way of binding directly from the ViewModel instead of having to have to set isEnable observing the MediatorLiveData in the Fragment?

Thanks!

  • you don't need `createAccountDetailsValid.observe(owner, Observer { })` – Blackbelt Feb 01 '19 at 15:46
  • @Blackbelt yes I need to because of this `@MainThread public void addSource(@NonNull LiveData source, @NonNull f (existing != null) { return; } if (hasActiveObservers()) { e.plug(); } }` Basically, it doesn't have any observers doesn't plug as you can see, so I need to have one for some weird reason. – Emanuel Amiguinho Feb 01 '19 at 15:49
  • you don't need that. `databinding` is gonna observe that for you. Mediator is a `LiveData` object. Do you need to `Observe` for every `LiveData` object you have? – Blackbelt Feb 01 '19 at 15:53
  • that's what I thought as well but if I remove that observe the binding stops working, maybe I'm doing something else wrong here. – Emanuel Amiguinho Feb 01 '19 at 15:54
  • that's probably the case. Are you `setting/posting` anything on `name/surname/company/email` ? – Blackbelt Feb 01 '19 at 15:56
  • I think I found the issue here, was when I was doing addSource at that time the view hasn't been created which means the I was doing it too early maybe? and the databinding didn't observe at that time – Emanuel Amiguinho Feb 01 '19 at 16:00
  • that doesn't make any sense – Blackbelt Feb 01 '19 at 16:01
  • What would be the best time to call addSource to the MediatorLiveData? init of the view model? – Emanuel Amiguinho Feb 01 '19 at 16:01
  • it doesn't matter. E.g I use `lazy` – Blackbelt Feb 01 '19 at 16:02
  • 1
    Yeah, it's working, might have done something wrong and ended up fixing by complicating, it just worked the way I have thought it would from the beginning, thanks for your help mate! – Emanuel Amiguinho Feb 01 '19 at 16:10
  • Consider doing https://stackoverflow.com/questions/54271762/best-practice-for-using-mediatorlivedata-only-by-present-data/54292960#54292960 – EpicPandaForce Feb 01 '19 at 16:34

1 Answers1

0

Has Blackbelt comment I should have been doing something wrong and ended up complicating and fix the issue while trying to make it work.

Has he said databinding observes and you can addSource on the viewModel init like I did and got it working:

 val createAccountResult: MutableLiveData<Resource<Void>> = MutableLiveData()
val loginResult: MutableLiveData<Resource<Void>> = MutableLiveData()

//USER DATA
val email: MutableLiveData<String> = MutableLiveData()
val name: MutableLiveData<String> = MutableLiveData()
val surname: MutableLiveData<String> = MutableLiveData()
val company: MutableLiveData<String> = MutableLiveData()

var createAccountDetailsValid: MediatorLiveData<Boolean> = MediatorLiveData()

init {
    createAccountDetailsValid.addSource(email) {
        createAccountDetailsValid.value = isCreateAccountDetailsValid()
    }
    createAccountDetailsValid.addSource(name) {
        createAccountDetailsValid.value = isCreateAccountDetailsValid()
    }
    createAccountDetailsValid.addSource(surname) {
        createAccountDetailsValid.value = isCreateAccountDetailsValid()
    }
    createAccountDetailsValid.addSource(company) {
        createAccountDetailsValid.value = isCreateAccountDetailsValid()
    }
}

Thanks for your help @Blackbelt