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 View
s. That's why you expose data through things like LiveData
which are observe
d 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()