26

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

Community
  • 1
  • 1
kcrimi
  • 261
  • 1
  • 3
  • 6
  • 1
    Why you don't use `observeForever(observer)` instead? It does not require a _LifeCycleOwner_ and you can remove Observer in `onDetachedFromWindow()` – Siamak Aug 31 '20 at 15:38

2 Answers2

6

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.

Mike
  • 2,547
  • 3
  • 16
  • 30
2

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"         
}
  • a Fragment can have have two lifecycle owners: of fragment itself or of view of fragment (`viewLifecycleOwner`). In our case we need to get `viewLifecycleOwner` somehow if custom view was inflated in fragment – user924 Dec 28 '21 at 07:44