Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android ViewModel for a custom view

I would like to refactor my Custom View to use android architecture components. However, I see that

ViewModelProviders.of(...)

takes only Activity or fragment. Any idea how to make it work? Should I use fragment instead of Custom View?

like image 997
qbait Avatar asked Mar 01 '26 05:03

qbait


1 Answers

You can use findViewTreeViewModelStoreOwner() to retrieve a ViewModel from within your custom View.

And then implement DefaultLifecycleObserver in that view so you can hook onto lifecycle methods.

Those 2 combined should give you all the ViewModel and Scope options you get with Activity and Fragment implementations`.

Example kotlin code

Custom View code, here it's based on a ConstraintLayout and I've added KoinViewModelFactory so that your dependencies can be injected via the constructor. Otherwise just use ViewModelProvider(owner).

import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.findViewTreeViewModelStoreOwner
import androidx.lifecycle.viewModelScope
import org.koin.androidx.viewmodel.factory.KoinViewModelFactory
import org.koin.core.component.getScopeId
import org.koin.core.qualifier.named
import org.koin.mp.KoinPlatform.getKoin

class MyCustomView : ConstraintLayout, DefaultLifecycleObserver {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    private val viewModel by lazy {
        findViewTreeViewModelStoreOwner()?.let { owner ->
            ViewModelProvider(
                owner,
                KoinViewModelFactory(
                    kClass = MyCustomViewModel::class,
                    scope = getKoin().getOrCreateScope(scopeId = owner.getScopeId(), named(owner.getScopeId())),
                )
            )[MyCustomViewModel::class.java]
        }
    }

    init {
        inflate(context, R.layout.my_custom_view, this)
        // makes this view lifecycle aware
        (context as? LifecycleOwner)?.lifecycle?.addObserver(this)
    }

    override fun onCreate(owner: LifecycleOwner) {
        super.onCreate(owner)
        // observe changes
        viewModel?.events?.observeNonNull(owner) { handleEvent(it) }
    }

    override fun onDestroy(owner: LifecycleOwner) {
        // other lifecycle methods like this are available...
    }

    fun handleEvent(event: UiEvent) {}
}

ViewModel implementation

import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
import org.koin.core.component.KoinComponent

class MyCustomViewModel(
    private val injectedService: SomeService, // will be injected
) : ViewModel(), KoinComponent {

    val events = MutableLiveData<UiEvent>() // live data example

    fun someAction() {
        events.value = UiEvent.SomeAction
    }

    sealed class UiEvent {
        data object SomeAction : UiEvent()
    }
}
like image 108
hcpl Avatar answered Mar 03 '26 18:03

hcpl



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!