Home > OS >  Can pass data from View to ViewModel?
Can pass data from View to ViewModel?

Time:07-02

Is there mistake if I pass data from View to ViewModel? For example, pass url from onPageFinished event of WebView. I am confused because all source tell that ViewModel mustn't have any link to View. In this case will be such link or not? Or if type of argument will be custom data class than just string?

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.webView.settings.javaScriptEnabled = true
        binding.webView.webViewClient = object : WebViewClient(){
            override fun onPageFinished(view: WebView, url: String) {
                super.onPageFinished(view, url)
                viewModel.onPageFinished(url) // this line
            }
        }

        viewModel.url.observe(this) { url ->
            binding.webView.loadUrl(url)
        }
    }
}
class MainViewModel: ViewModel() {

    private val _cookieManager: CookieManager

    private lateinit var _url: MutableLiveData<String>
    val url: LiveData<String> = _url

    init {
        _url.value = "google.com"
        _cookieManager = CookieManager.getInstance()
    }

    fun onPageFinished(url: String) {
        val cookies = _cookieManager.getCookie(url)
        Log.i("MainViewMovel", url)
        Log.i("MainViewMovel", cookies)
    }

}

CodePudding user response:

The View is the UI, it has to pass some data to the View Model, like button presses, typed search queries, requests for data when the user scrolls etc. It's absolutely a two-way thing - here's the docs talking about the recommended way of designing your app. There's more on the side, including a section on the UI Layer (the View)

The terminology might be a little confusing here, because the View in Model-View-ViewModel is referring to your UI layer. But a View in Android is a layout component, which is generally tied to a specific Fragment or Activity which have shorter lifetimes than a ViewModel (one of the main points of a VM is to retain data even when Activity and Fragment instances are destroyed and recreated).

So for that reason, your ViewModel shouldn't hold any references to any Views. That's why you expose data through things like LiveData which are observed by passing a LifecycleOwner - the observers are automatically removed when they're destroyed, so the LiveData in the VM isn't keeping them alive


As far as your question goes, I don't think it hugely matters - your WebViewClient is a little bit of wiring between the View and the ViewModel, and there's always a bit of that! But if you wanted, you could always put the WebViewClient in the ViewModel, there's nothing obviously tying it to a particular view

I think that makes more sense in general - if you look at the other callbacks in WebViewClient, a lot of them are about program logic, handling events and situations. The UI layer shouldn't really be concerned with any of that, so it makes more sense to me to have the VM take care of that, decide what needs to happen, and just push any resulting UI state updates to the UI.

An alternative to keeping a singleton WebViewClient in the VM would be to have a function in there that creates a new one and configures it. That way you don't need to worry about it having any state relating to the old WebView, but all the code is still internal to the ViewModel:

class MainViewModel: ViewModel() {
    ...
    fun getWebViewClient() = object : WebViewClient(){
        override fun onPageFinished(view: WebView, url: String) {
            super.onPageFinished(view, url)
            onPageFinished(url) // calling the internal VM method
        }
    }
}

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        binding.webView.webViewClient = viewModel.getWebViewClient()
  • Related