[Android Jetpack series] Five, the use of ViewModel and LiveData

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, ViewModelEnglish literal translation: 视图模型. Official words: ViewModelIt 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, ViewModelit can be said that it is 掌控全局because it lives for a long time.

From 生命周期the beginning onCreateto onDestroythe 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.

Life cycle description diagram of ViewModel.png
Also need special attention is:

ViewModelIt is usually built manuallyonCreate by developers in , and it will be automatically erased by the system at time .onDestroyonCleared()

!Attention! ViewModel must never refer to any carried Context(including Lifecyclecarried Activity) objects; as mentioned above, the ViewModel has a long life, and Contextreferences to it may cause problems 内存泄露. If you really want to use Context, you can try it AndroidViewModel, because AndroidViewModelit carries one by default Application.

What is LiveData?

LiveDataEnglish literal translation: 具有生命的数据, it is 可观察的数据存储类(here again: Observer mode), since it has life, the Lifecycle we mentioned earlier comes in handy.

Then LiveDatathe in 可观察is 生命周期(Lifecycle)observable.

Well, here we need to add the content of the previous Lifecycle .

lifecycle.png

In the previous picture, Logone currentStateshows the current life cycle state, and it is not difficult to see currentStatethat it is the same as the name of the life cycle function.

LiveDataOnly the data currentStatein the STARTEDor RESUMEDstate will be notified to proceed 更新, and in other cases it LiveDatawill 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

LiveDataWe need to customize our own ViewModelview model before using it .

class MainVM : ViewModel() {
    
    }

Then MainActivity.ktbuild it in, here are two ways.

Method 1, by ViewModelProviderbuilding:

    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, kotlinbuild by extending dependencies (applicable only kotlin):

  • First, in the App level build.gradle, add the dependencies supported by kotlinthe 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.ktthe code 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)
        }
    }
    

At this point, the ViewModel is ready to be used, and it will survive until the current Activityend.

Basic use of LiveData

LiveDataImplement 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.

count.gif

LiveDataIt needs to be ViewModelused together, it is very simple to use, and it can be done with one line of code.

We modify MainVM.ktthe code in the code

  class MainVM : ViewModel() {
    
    
    var count: MutableLiveData<Int> = MutableLiveData()
  }

Then, you can MainActivity.ktcall 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

FramgentThe power of LiveData and ViewModel is more than that. The communication between them was cumbersome before , but after ViewModelthat, it was completely simplified.

Picture above, code above!

First Fragmentthe container

fragment.png

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

firstFragment.png

<?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

secondFragment.png

<?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 Fragmentonly 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 ViewModeldon't create objects by newgoing to create (never do by newgoing to create), and ViewModelProvider.Factorythe 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.

running.gif

It can be found that whether you click the button above or click the button below, TextViewthe contents of both have changed.

LiveData和MutableLiveData

Through the above code, we found that we both use LiveDataand use MutableLiveData, so what is their relationship? Why LiveDatacan't we modify (update) the data?

MutableLiveDataThe source code we saw

MutableLiveDataSource.png

It is found that it is directly inherited . Although the two methods of and LiveDatahave been rewritten , no new code has been added, but the direct parent class method.setValue()postValue()super

LiveDataSource.png

After clicking on the parent class LiveData, I found out that MutableLiveDataonly one thing was done, which is to add protected-> public.

As for the difference between postValueand setValue: It is to postsValuedeal with data updates in multi-threaded mode (but UI updates are still in the main thread), and setValuecan only be used in single-threaded mode (you can try calling in multi-threaded setValue)

It is worth noting that it postsValuemay 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 newcreate a ViewModel by using it . As for the reason, let's take a brief look at ViewModelProviderthe relevant source code here.

ViewModelProvider_get.png

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).

view_model_store.png

Internally , the uniqueness ViewModelStoreis ensured by the characteristics of HashMap .ViewModel

As mentioned above, the method get()方法used to create an instance.ViewModelProvider.ViewModelFactorycreate(modelClass)

Moreover, when we use it ViewModelProvider, we usually provide one ViewModelProvider.NewInstanceFactory()to see its structure in the figure below.

create.png

反射!Hey guys, the original ViewModelinstance 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!!

threevm.png

Detailed explanation of Transformations【Dianjie Liwa】

We need to implement it ViewModelProvider.ViewModelFactoryourselves

/// 自定义 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 newInstancepassing in userthe instance.

Finally activityuse 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

running.gif

Guess you like

Origin blog.csdn.net/AoXue2017/article/details/126485992