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 pass
addLinkedNumber
The ViewModel corresponding LiveData attribute has been modified operation! - Then the activity passes
viewModel!! .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)