This is the 23rd day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

preface

In the last article, I got a glimpse of Jetpack and went into detail about how LifeCycle will be used in practice. In this article, we’ll take a look at the ViewModel for Jetpack!

1, the ViewModel

Problems before the ViewModel was born:

  • Transient data loss
  • Memory leaks for asynchronous calls
  • Class inflation increases maintenance difficulty and testing difficulty

The role of the ViewModel

As is shown in

  • It is the bridge between the View and the Model
  • Enables views and data to be separated and communicate

Lifecycle characteristics of the ViewModel

As is shown in

We see:

  • A ViewModel has a long life cycle and an Activity has a long life cycle. Therefore, when using a ViewModel, do not pass the Activity Context to the ViewModel, because this will cause memory leaks!
  • If you have to use the Context, use the Application in the AndroidViewModel

Concept said, next actual combat:

1.1 Actual Combat I (Without Using ViewModel)

The layout file is just a TextView, so I won’t post the layout code here!

class Step1Activity : AppCompatActivity() {
    private var textView: TextView? = null
    private var count = 0
    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.textView)
    }
    fun plusNumber(view: View){ count++ textView!! .text ="$count"}}Copy the code

Here we see that when we click the button, the global variable count increments by one and then assigns the value to the textView!

Let’s see how it works (before the screen rotates)

Hold this value and rotate the screen to see the effect:

When the screen rotates, we find that the self-added data is lost in this way!

Of course, there must be a solution to this, and this is not the solution to the problem. Just for comparison with the ViewModel!

So now what’s going to happen with the ViewModel?

1.2 Actual Combat 2 (Using ViewModel)

MyViewModel.kt


class MyViewModel(application: Application) : AndroidViewModel(application) {
    var number = 0 //
}
Copy the code

So this code is pretty simple, just inheriting the AndroidViewModel, which defines the number variable.

Let’s see how it works

class Step2Activity : AppCompatActivity() {

    private var textView: TextView? = null
    private var viewModel: MyViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.textView)
        
        // How to instantiate the viewModel
        viewModel =
            ViewModelProvider(this, AndroidViewModelFactory(application))[MyViewModel::class.java] textView!! .text = String.valueOf(viewModel!! .number) }fun plusNumber(view: View){ textView!! .text = String.valueOf(++viewModel!! .number) } }Copy the code

Here we see the logic implemented by accessing properties in the viewModel after instantiating the corresponding viewModel.

At this time, we found that no matter how to switch the vertical and horizontal screen, the corresponding screen numbers will not be cleared, that is to say, there is no previous data loss situation! I will not put the running renderings here.

Next component!

2, LiveData

Relationship between LiveData and ViewModel

As is shown in

They correspond to notifying the page when data changes in the ViewModel

In this case, then start actual combat check some!

2.1 Actual Combat I (Simple Application)

LiveDta needs a ViewModel to function, so:

MyViewModel.kt

class MyViewModel : ViewModel() {

	// Define the corresponding LiveData collection
    private var linkNumber: MutableLiveData<Int>? = null

	// Get the corresponding set of LiveData
    fun getLinkNumber(a): MutableLiveData<Int>? {
        // Ensure that linkNumber is not null
        if (linkNumber == null) {
        	/ / initializationlinkNumber = MutableLiveData() linkNumber!! .value =0
        }
        return linkNumber
    }

	// Provide external methods to modify the internal properties of the collection
    fun addLinkedNumber(n: Int){ linkNumber!! .value = linkNumber!! .value!! + n } }Copy the code

We can see that in the corresponding ViewModel, we define the set of LiveData corresponding to MutableLiveData

, and then we initialize, modify, and so on.

How does it work?

class MainActivity : AppCompatActivity() {
    private var viewModel: MyViewModel? = null

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView: TextView = findViewById(R.id.textView)
        
        viewModel =
            ViewModelProvider(this, AndroidViewModelFactory(application))[MyViewModel::class.java] viewModel!! .getLinkNumber()!! .observe(this, Observer {
            textView.text = String.valueOf(it)
        })
    }

    fun reduce(view: View){ viewModel!! .addLinkedNumber(-1)}fun add(view: View){ viewModel!! .addLinkedNumber(1)}}Copy the code

The layout of the page is very simple, just two buttons and one text, so there is no layout code attached.

Here we see:

  • Initialize the ViewModel as above, and when the corresponding button is clicked, it will passaddLinkedNumberThe ViewModel corresponding LiveData attribute has been modified operation!
  • Then the activity passesviewModel!! .getLinkNumber()!! .observe(this, Observer {xx}To receive notifications of changes from the ViewModel and update them to textView in time

Let’s see how it works:

Ha, ha, ha, ha! But as long as the effect is achieved!

Next to the next actual combat!

2.2 Actual Combat 2 (Fragment Communication)

2.2.1 Corresponding layout file

Fragment.xml (two identical layouts)


      
<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=".FirstFragment">


    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="0dp"
        android:min="0"
        android:max="100"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

The activity layout

As is shown in

Home page layout: two fragments, one at the top and one at the bottom, evenly distributed. Each Fragment has the same layout and contains the same SeekBar

Desired effect: No matter which Fragment you drag the SeekBar in, another SeekBar moves with it

2.2.2 Corresponding implementation logic

See first MyViewModel. Kt

class MyViewModel : ViewModel() {

    private var progress: MutableLiveData<Int>? = null

    fun getProgress(a): MutableLiveData<Int>? {
        if (progress == null) { progress = MutableLiveData() progress!! .value =0
        }
        return progress
    }
}
Copy the code

Very simple, directly see how to use!

FirstFragment.kt

class FirstFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup? , savedInstanceState:Bundle?).: View? {
        
        var rootView = inflater.inflate(R.layout.fragment_first, container, false)
        var seekBar = rootView.findViewById<SeekBar>(R.id.seekBar)
        
        // instantiate the corresponding ViewModel
        val viewModel = ViewModelProvider(
            requireActivity(), AndroidViewModelFactory(
                requireActivity().application
            )
        )[MyViewModel::class.java]

        // Accept notifications corresponding to the ViewModelviewModel.getProgress()!! .observe(requireActivity(), Observer { seekBar.progress = it }) seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            	// Change the LiveData property value in the corresponding ViewModelviewModel.getProgress()!! .setValue(progress) }override fun onStartTrackingTouch(seekBar: SeekBar) {}
            override fun onStopTrackingTouch(seekBar: SeekBar){}})return rootView
    }

}
Copy the code

When the seekBar property value changes, the LiveData value is modified and the corresponding Fragment is notified to refresh the UI!

The second Fragment implements exactly the same logic: copy, paste and rename the Fragment.

Let’s see how it works

Perfect, of course, using the ViewModel, even if you rotate the screen ha, data will not be lost!

2.3 summarize

From the above actual combat, we can see the advantages of LiveData

  • Ensure that the interface conforms to the data state
  • No memory leaks will occur
  • It does not crash when the Activity stops
  • You no longer need to handle the life cycle manually
  • Data is always up to date
  • Appropriate configuration changes
  • Shared resources

conclusion

Well, that’s about the end of this article, and I believe you have some idea of ViewModel+LiveData! LifeCycle+ViewModel+LiveData+DataBinding will be explained in detail in the next article and put together into one: LifeCycle+ViewModel+LiveData+DataBinding!

Download the Demo:Click me to download (contains the next part of the source)