Search code examples
androidkotlinandroid-databindingandroid-viewmodel

Too many XML data bindings


I made a View that I want to reuse across many pages. It contains feedback elements for the user such as a ProgressBar, TextView etc.

Due to high amount of items within, binding all those turns out like this:

<layout ... >

    <data>
        <variable
            name="screenObserver"
            type="my.namespace.ScreenStateObserver" />
    </data>


    <androidx.constraintlayout.widget.ConstraintLayout ... >

        <my.namespace.view.ScreenStateView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:loading="@{screenObserver.isProgressVisible}"
            app:errorText="@{screenObserver.errorTxt}"
            app:buttonText="@{screenObserver.errorBtnTxt}"
            app:errorVisible="@{screenObserver.isTextVisible}"
            app:buttonVisible="@{screenObserver.isButtonVisible}"
            app:onButtonClick="@{() -> screenObserver.onErrorResolve()}" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

I find copy/pasting the whole XML block messy and error-prone. Is there any way to make this simpler ?

ScreenStateObserver is just a interface that I implement in my ViewModel and bind as follows:

override fun onCreateView(...): View? {

    val factory = InjectorUtils.provideViewModelFactory()
    viewmodel = ViewModelProviders.of(this, factory).get(MyViewModel::class.java)
    binding = MyFragmentBinding.inflate(inflater, container, false).apply {
        screenObserver = viewmodel
    }
}
class AtoZViewModel() : ViewModel(), ScreenStateObserver { ... }
interface ScreenStateObserver {
    val isProgressVisible : MutableLiveData<Boolean>
    val isTextVisible : MutableLiveData<Boolean>
    val isButtonVisible : MutableLiveData<Boolean>

    // [..]
}

Thanks !


Solution

  • Here is my suggestion to reduce code.
    First declare a class like this

    interface ScreenState {
    
        class Loading : ScreenState
    
        class Error(val errorMessage: String, val errorButtonText: String) : ScreenState
    }
    

    and inside you CustomView it will be

    internal class ScreenStateView {
    
        fun setState(state: ScreenState) {
            if (state is ScreenState.Loading) {
                // show loading
            } else {
                // hide loading
            }
    
            if (state is ScreenState.Error) {
                //show {state.errorMessage} and {state.errorButtonText}
            } else {
                // hide error
            }
        }
    }
    

    using in xml

        <my.namespace.view.ScreenStateView
            ...
            app:state="@{screenObserver.screenState}"
            ...
            app:onButtonClick="@{() -> screenObserver.onErrorResolve()}" /> // for onButtonClick I think it still better if we keep like this
    

    Hope it help