Setting up LiveData observer in custom view without LifecycleOwner

Viewed 13840

I'm trying out the new Android Architecture components and have run into a road block when trying to use the MVVM model for a custom view.

Essentially I have created a custom view to encapsulate a common UI and it's respective logic to use throughout the app. I can set up the ViewModel in the custom view but then I'd have to either use observeForever() or manually set a LifecycleOwner in the custom view like below but neither seem correct.

Option 1) Using observeForever()

Activity

class MyActivity : AppCompatActivity() {

    lateinit var myCustomView : CustomView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        myCustomView = findViewById(R.id.custom_view)
        myCustomView.onAttach()
    }

    override fun onStop() {
        myCustomView.onDetach()
    }
}

Custom View

class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){

    private val viewModel = CustomViewModel()

    fun onAttach() {
        viewModel.state.observeForever{ myObserver }
    }

    fun onDetach() {
        viewModel.state.removeObserver{ myObserver }
    }
}

Option 2) Setting lifecycleOwner from Activity`

Activity

class MyActivity : AppCompatActivity() {

    lateinit var myCustomView : CustomView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        myCustomView = findViewById(R.id.custom_view)
        myCustomView.setLifeCycleOwner(this)
    }
}

Custom View

class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){

    private val viewModel = CustomViewModel()

    fun setLifecycleOwner(lifecycleOwner: LifecycleOwner) {
        viewModel.state.observe(lifecycleOwner)
    }
}

Am I just misusing the patterns and components? I feel like there should be a cleaner way to compose complex views from multiple sub-views without tying them to the Activity/Fragment

2 Answers

1 Option - With good intention, you still have to do some manual work - like, calling onAttach\ onDetach Main purpose of Architecture components is to prevent doing this.

2 Option - In my opinion is better, but I would say it's a bit wrong to bind your logic around ViewModel and View. I believe you can do same logic inside Activity/Fragment without passing ViewModel and LifecycleOwner to CustomView. Single method updateData is enough for this purpose.

So, in this particular case, I would say it's overuse of Architecture Components.

it doesn't make sense to manage the lifecycle of the the view manually by passing some reference of the activity to the views and calling onAttach/onDetach, when we already have the context provided when that view is created.

I have a fragment in a NavigationView that has other fragments in a view pager, more like a nested fragment hierarchy scenario.

I have some custom views in these top-level fragments, when the custom view is directly in the top fragment, I can get an observer like this

viewModel.itemLiveData.observe((context as ContextWrapper).baseContext as LifecycleOwner,
   binding.item.text = "some text from view model"         
}

when I have the custom view as a direct child of an activity I set it up directly as

viewModel.itemLiveData.observe(context as LifecycleOwner,
   binding.item.text = "some text from view model"         
}

in these activities, if I have a fragment and it has some custom view and I use the 2nd approach, I get a ClassCastException(), and I have to reuse these custom views in different places, both activities, and fragments (that's the idea of having a custom view)

so i wrote an extension function to set the LifeCycleOwner

fun Context.getLifecycleOwner(): LifecycleOwner {
    return try {
        this as LifecycleOwner
    } catch (exception: ClassCastException) {
        (this as ContextWrapper).baseContext as LifecycleOwner
    }
}

now i simply set it everywhere as

viewModel.itemLiveData.observe(context.getLifecycleOwner(),
   binding.item.text = "some text from view model"         
}
Related