Welcome to this post.
In this post, I am going to talk about, how to communicate between view and viewModel.
Let's take a look on ViewModel
ViewModel was introduced on Google I/O 17, a part of Lifecycle extensions. Now it's released version is 1.1.0.
From the picture, you can see that it launch with the creation of activity. And it will be cleared only when the activity is finished. The biggest advantage is that it can survive configuration changes.
You should not put any view reference in the viewModel. Why?
The answer is so simple When activity recreated the also. So if you pass view to ViewModel then it creates a leak, because, on the recreation of activity, all the views are brand new, but your ViewModel is still holding the older view that already destroyed.
Don't do this.
Now a question, so how to communicate between view and ViewModel?
you can show a dialog and after successful signup, we have to show a notification or perform any task. In this case how you contact view and ViewModel.
Let's start-
To solve this problem I create a class that inherited Mutable Live Data Class.
This is the sample from google, it is the blueprint of architecture component. Check this.
But If you want to set a value from the main thread then it's ok. you can use this without changing anything.
But what about the background thread?
If you want to set a value from a background thread, then you have to override postValue() method.
Create a status class
Now come on ViewModel Code:
create an instance of SingleLiveEvent
I create 3 mock methods, just with a thread that wait for 2 seconds and set the value on the variable, observableEvent.
mockLogin()
just set value from the main thread and start a new background thread.
mockSignup()
start a thread and wait for 2s and set value. and also start a new background thread.
background()
start a background thread and with the handler and wait for 2s
Now time for activity code:
I have 3 view
set onclick event for every button and also called the method from ViewModel
Login:
hide progress bar
Sign up:
hide progress bar
now observe that event and show toast message. see the code
Now run the code and click the button see the toast message.
So, that's it.
you can find this project on GitHub
Thanks for reading.
Hope this tutorial will help you a lot.
Happy coding.
In this post, I am going to talk about, how to communicate between view and viewModel.
Let's take a look on ViewModel
ViewModel was introduced on Google I/O 17, a part of Lifecycle extensions. Now it's released version is 1.1.0.
Src: Medium |
From the picture, you can see that it launch with the creation of activity. And it will be cleared only when the activity is finished. The biggest advantage is that it can survive configuration changes.
You should not put any view reference in the viewModel. Why?
The answer is so simple When activity recreated the also. So if you pass view to ViewModel then it creates a leak, because, on the recreation of activity, all the views are brand new, but your ViewModel is still holding the older view that already destroyed.
Don't do this.
Now a question, so how to communicate between view and ViewModel?
Case Study:
Imagine you build an app that has an option for the user signup. When the user clicks signup button thenyou can show a dialog and after successful signup, we have to show a notification or perform any task. In this case how you contact view and ViewModel.
Let's start-
To solve this problem I create a class that inherited Mutable Live Data Class.
class SingleLiveEvent<T> : MutableLiveData<T>() { private val pending = AtomicBoolean(false) @MainThread override fun observe(owner: LifecycleOwner, observer: Observer<T>) { if (hasActiveObservers()) { //Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") } // Observe the internal MutableLiveData super.observe(owner, Observer<T> { t -> if (pending.compareAndSet(true, false)) { observer.onChanged(t) } }) } @MainThread override fun setValue(t: T?) { pending.set(true) super.setValue(t) } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread fun call() { value = null } companion object { private const val TAG = "SingleLiveEvent" } }
This is the sample from google, it is the blueprint of architecture component. Check this.
But If you want to set a value from the main thread then it's ok. you can use this without changing anything.
But what about the background thread?
If you want to set a value from a background thread, then you have to override postValue() method.
override fun postValue(value: T) { pending.set(true) super.postValue(value) }If you have any confuse, then check GitHub
Create a status class
enum class Status{ LOGIN, SIGNUP, FAILED }
Now come on ViewModel Code:
create an instance of SingleLiveEvent
val observableEvent = SingleLiveEvent<Status>()
I create 3 mock methods, just with a thread that wait for 2 seconds and set the value on the variable, observableEvent.
mockLogin()
just set value from the main thread and start a new background thread.
fun mockLogin(){ observableEvent.value = Status.LOGIN background() }
mockSignup()
start a thread and wait for 2s and set value. and also start a new background thread.
fun mockSignUp(){ Thread({ try { Thread.sleep(2000) //2 sec wait } catch (e:Exception){ //nothing } finally { observableEvent.postValue(Status.SIGNUP) //do some task from background background() } }).start() }
background()
start a background thread and with the handler and wait for 2s
private fun background(){ val thread = HandlerThread("Background") thread.start() Handler(thread.looper).post({ //wait for 2 sec Thread({ try { Thread.sleep(2000) //2 sec wait } catch (e:Exception){ //nothing } finally { observableEvent.postValue(Status.FAILED) } }).start() }) }
Now time for activity code:
I have 3 view
- progress bar
- login button
- signup button
val progressBar:ProgressBar = progressBar
progressBar.gone()
Note, here I use an extension function. see here set onclick event for every button and also called the method from ViewModel
Login:
hide progress bar
button.setOnClickListener { progressBar.vis() viewModel.mockLogin() }
Sign up:
hide progress bar
button2.setOnClickListener { progressBar.vis() viewModel.mockSignUp() }
now observe that event and show toast message. see the code
viewModel.observableEvent.observe(this, Observer { if (it == Status.LOGIN){ progressBar.gone() "Login Successful".showToast() } else if (it == Status.SIGNUP) { progressBar.gone() "Sign up Successful".showToast() } if (it == Status.FAILED){ "Job Failed from background thread".showToast() } })here, showToast is another extension function
private fun String.showToast(){ Toast.makeText(context, this, Toast.LENGTH_SHORT).show() }
Now run the code and click the button see the toast message.
So, that's it.
you can find this project on GitHub
Thanks for reading.
Hope this tutorial will help you a lot.
Happy coding.