Use of ViewModel and LiveData
After half a month (touch) and half a month, we finally started ViewModel and LiveData.
First, before we begin, a clarification: What is ViewModel
? What is again LiveData
?
What is ViewModel?
Old routine, ViewModel
English literal translation: 视图模型
. Official words: ViewModel
It aims to store and manage interface-related data in a life-cycle-focused manner.
Indeed, this statement is very official. To put it bluntly: The bridge of ** 视图(View)
and (the concept in MVVM or MVC) is to separate and to reduce the coupling between UI and data, that is, to improve the efficiency of code . Readability, maintainability. **数据(Data)
视图
数据
In the official introduction, ViewModel
it can be said that it is 掌控全局
because it lives for a long time.
From 生命周期
the beginning onCreate
to onDestroy
the end, no matter whether the application is switched to the background or to the foreground, it will not disappear.
The following is the official text:
The time range in which the ViewModel object exists is the Lifecycle passed to the ViewModelProvider when the ViewModel is acquired. A ViewModel remains in memory until the Lifecycle that bounds its lifetime is permanently gone: for an Activity, when the Activity completes; for a Fragment, when the Fragment detaches.
Also need special attention is:
ViewModel
It is usually built manuallyonCreate
by developers in , and it will be automatically erased by the system at time .onDestroy
onCleared()
!Attention! ViewModel must never refer to any carried
Context
(includingLifecycle
carriedActivity
) objects; as mentioned above, the ViewModel has a long life, andContext
references to it may cause problems内存泄露
. If you really want to use Context, you can try itAndroidViewModel
, becauseAndroidViewModel
it carries one by defaultApplication
.
What is LiveData?
LiveData
English literal translation: 具有生命的数据
, it is 可观察的数据存储类
(here again: Observer mode), since it has life, the Lifecycle we mentioned earlier comes in handy.
Then LiveData
the in 可观察
is 生命周期(Lifecycle)
observable.
Well, here we need to add the content of the previous Lifecycle .
In the previous picture, Log
one currentState
shows the current life cycle state, and it is not difficult to see currentState
that it is the same as the name of the life cycle function.
LiveData
Only the datacurrentState
in theSTARTED
orRESUMED
state will be notified to proceed更新
, and in other cases itLiveData
will be judged that非活跃状态
no update notification will be sent to the data in these states.
That's all the talking, let's move on to the text.
The development environment and SDK version used in this article are as follows, and readers should use a development environment no lower than that used in this article.
Android Studio 4.0.1
minSdkVersion 21
targetSdkVersion 30
text
Basic use of ViewModel
LiveData
We need to customize our own ViewModel
view model before using it .
class MainVM : ViewModel() {
}
Then MainActivity.kt
build it in, here are two ways.
Method 1, by ViewModelProvider
building:
private lateinit var mainVm: MainVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
mainVm = ViewModelProvider(MainActivity@ this).get(MainVM::class.java)
//mainVm = ViewModelProvider(MainActivity@ this, ViewModelProvider.NewInstanceFactory()).get(MainVM::class.java)
}
Method 2, kotlin
build by extending dependencies (applicable only kotlin
):
-
First, in the App level
build.gradle
, add the dependencies supported bykotlin
the extended dependencies .
androidx
def fragment_version = "1.3.1" /// 本文所依赖的开发环境不支持1.3.1以上的版本, 读者自行更改版本号 implementation "androidx.activity:activity-ktx:$fragment_version" implementation "androidx.fragment:fragment-ktx:$fragment_version"
List of build numbers
androidx.activity
List of build numbersandroidx.fragment
-
Then modify
MainActivity.kt
the code inimport androidx.activity.viewModels class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val mainVm: MainVM by viewModels() /// 委托加载 override fun onCreate(savedInstanceState: Bundle?) { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) } }
At this point, the ViewModel is ready to be used, and it will survive until the current Activity
end.
Basic use of LiveData
LiveData
Implement a set of observer mechanism by yourself, it will automatically notify observe()
the method after detecting the data change, you can observe()
write the code of UI update in the method.
The counter example seems to be poorly written, but that doesn't prevent it from being the simplest example.
LiveData
It needs to be ViewModel
used together, it is very simple to use, and it can be done with one line of code.
We modify MainVM.kt
the code in the code
class MainVM : ViewModel() {
var count: MutableLiveData<Int> = MutableLiveData()
}
Then, you can MainActivity.kt
call it in .
import androidx.activity.viewModels
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val mainVm: MainVM by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
/// 只需要在这里写观察回调, liveData将自动监测数据变化
mainVm.count.observe(MainActivity@ this) {
binding.myTextView.text = "$it" //setText()
}
/// add按钮, 设置点击事件, 直接更改 count 的值
binding.add.setOnClickListener {
mainVm.count.value = (mainVm.count.value ?: 0) + 1
}
}
}
Well, this is the end of this example, and the use of ViewModel and LiveData ends here.
The use of LiveData and ViewModel in Fragment
Framgent
The power of LiveData and ViewModel is more than that. The communication between them was cumbersome before , but after ViewModel
that, it was completely simplified.
Picture above, code above!
First Fragment
the container
activity_second.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/firstFragment"
android:name="com.example.vl.fragment.FirstFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#55fafa00" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="16dp"
android:background="@color/colorPrimary" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/secondFragment"
android:name="com.example.vl.fragment.SecondFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#558787dd" />
</LinearLayout>
After thatfragment_first.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.FirstFragment">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="FirstFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/minus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="minus"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
and thenfragment_second.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.SecondFragment">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SecondFragment"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/increase"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="increase"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
</androidx.constraintlayout.widget.ConstraintLayout>
After the layout is drawn, it is the code, and Fragment
only the key codes are listed.
FirstFragment.kt
class FirstFragment : Fragment() {
private val secondVM:SecondVM by activityViewModels()
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentFirstBinding.inflate(inflater, container, false)
secondVM.number.observe(requireActivity()) {
binding.textView.text = "$it"
}
binding.minus.setOnClickListener {
secondVM.onMinus() //递减
}
return binding.root
}
}
SecondFragment.kt
class SecondFragment : Fragment() {
private lateinit var secondVM: SecondVM
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
secondVM = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory()).get(SecondVM::class.java)
}
...
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment
binding = FragmentSecondBinding.inflate(inflater, container, false)
secondVM.number.observe(requireActivity()) {
binding.textView2.text = "$it"
}
binding.increase.setOnClickListener {
secondVM.onIncrease() //递增
}
return binding.root
}
}
SecondVM.kt
class SecondVM : ViewModel() {
private var _number: MutableLiveData<Int> = MutableLiveData()
val number: LiveData<Int> = _number //LiveData不允许通过 setValue 和 postsValue 更新数据
fun onIncrease() {
//前文没提, 这里的 value = ... 因为kotlin的特性, 调用的是 setValue() 方法
_number.value = (_number.value ?: 0) + 1
}
fun onMinus() {
_number.value = (_number.value ?: 0) - 1
}
}
SecondFragment.kt
class SecondActivity : AppCompatActivity() {
//private val secondVM: SecondVM by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
There is no need to write here secondVM: SecondVM by viewModels()
why?
Because we ViewModel
don't create objects by new
going to create (never do by new
going to create), and ViewModelProvider.Factory
the following will explain.
ViewModelProvider will look for one that has already been created in memory ViewModel
, and if not found, it will create a new one.
Let's run through the code above.
It can be found that whether you click the button above or click the button below, TextView
the contents of both have changed.
LiveData和MutableLiveData
Through the above code, we found that we both use LiveData
and use MutableLiveData
, so what is their relationship? Why LiveData
can't we modify (update) the data?
MutableLiveData
The source code we saw
It is found that it is directly inherited . Although the two methods of and LiveData
have been rewritten , no new code has been added, but the direct parent class method.setValue()
postValue()
super
After clicking on the parent class LiveData
, I found out that MutableLiveData
only one thing was done, which is to add protected
-> public
.
As for the difference between postValue
and setValue
: It is to postsValue
deal with data updates in multi-threaded mode (but UI updates are still in the main thread), and setValue
can only be used in single-threaded mode (you can try calling in multi-threaded setValue
)
It is worth noting that it postsValue
may cause data loss. For details, see 【Interviewer: Do you know the postValue of LiveData? ] In this article, I won’t go into details here.
Why use ViewModelProvider to build ViewModel?
As mentioned earlier , never new
create a ViewModel by using it . As for the reason, let's take a brief look at ViewModelProvider
the relevant source code here.
As can be seen from the figure above, ViewModelProvider maintains one internally private final ViewModelStore mViewModelStore
. If no corresponding one is found ViewModel
, then mFactory.create(modelClass)
create an instance through it (ViewModelFactory will debut soon).
Internally , the uniqueness ViewModelStore
is ensured by the characteristics of HashMap .ViewModel
As mentioned above, the method get()方法
used to create an instance.ViewModelProvider.ViewModelFactory
create(modelClass)
Moreover, when we use it ViewModelProvider
, we usually provide one ViewModelProvider.NewInstanceFactory()
to see its structure in the figure below.
反射!
Hey guys, the original ViewModel
instance was created here.
customizeViewModelFactory
Now that you know that the ViewModel is 反射
created by , it's easy to handle; now there is a requirement: 在ViewModel初始化的时候, 需要参数初始化
.
Picture here!!
Detailed explanation of Transformations【Dianjie Liwa】
We need to implement it ViewModelProvider.ViewModelFactory
ourselves
/// 自定义 ViewModelFactory
class ThreeViewModelFactory(var user: User) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return modelClass.getConstructor(User::class.java).newInstance(user)
}
}
Customize ViewModelFactory 只需要继承
ViewModelFactory` and then rewrite the create method.
By getConstructor(User::class.java)
getting to the constructor with parameters, and then newInstance
passing in user
the instance.
Finally activity
use it in
class ThreeActivity : AppCompatActivity() {
private lateinit var binding: ActivityThreeBinding
private lateinit var vm: ThreeVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityThreeBinding.inflate(layoutInflater)
setContentView(binding.root)
vm = ViewModelProvider(this, ThreeViewModelFactory(User("张三", 18))).get(ThreeVM::class.java)
// 观察数据变化
vm.userName.observe(this) {
binding.nameText.text = it
}
// 点击按钮改变user
binding.changeName.setOnClickListener {
// kotlin中的三目运算
vm.setName(if (vm.userName.value == "李四") "张三" else "李四")
}
}
}
Running result graph