preface
In the last two years, the voice of MVVM is getting higher and higher. To tell the truth, AFTER experiencing the bloated MVP, THE tedious MVP, I am a little afraid. But I was really surprised to see Google officially bring Jetpack, a series of weapons designed for the MVVM architecture.
If you haven’t used this new weapon yet, I really recommend that you do, get a feel for how fast and hard this new weapon is, and get a feel for the great decoupling of this new architecture.
introduce
Google I/O 2018: Jetpack
Jetpack is a set of libraries, tools, and guidelines that make it easier for developers to write great apps. These components help you follow best practices, get you out of boilerplate code, and simplify complex tasks so you can focus on the code you need.
Android development has long been riddled with irregularities and repetitive code, such as lifecycle management, duplication of development processes, project architecture choices, and so on. So In order to regulate development behavior, Google has created this set of guidelines to enable developers to develop quality apps better, faster, and in a more standardized way.
Of course, Jetpack has proved to be as convenient, fast and high-quality as it claims to be. It’s important that we, as developers, use these tools early to improve our development efficiency and regulate our own development behavior.
Today I’m going to show you the architecture component in Jetpack, which is the component that serves the MVVM framework, and each library can be used separately.
Jetpack- Architectural components
Let’s start with MVVM, Model — View — ViewModel.
Model layer
Primary data, such as server data, local database data, so network operations and database reads are at this level, only data is saved.The View layer
Mainly refers to the UI related, such as XML layout files, Activity interface displayThe ViewModel layer
It is the core of MVVM. It connects the View and model. The model data needs to be displayed on the View and the operation data reflected on the View is converted to the Model layer, so it is equivalent to a two-way binding.
So you need databinding to do databinding, one-way or two-way. Viewmodel does data management, binding views to data. Lifecycle performs lifecycle management. LiveData provides timely feedback of data. Can’t wait to see the wonders of each library with me.
Data binding
A data binding library is a support library that allows you to bind interface components in a layout to data sources in an application using declarative personalities rather than programmatically.
DataBinding refers to the DataBinding library DataBinding, which is described in six aspects
Configure the application to use data binding:
android {
. dataBinding {
enabled = true
}
} Copy the code
With data binding, we can have views in XML layout files bind and assign values to data objects, and we can write expressions to handle view-dispatched events with the help of the expression language. An 🌰 :
/ / layout activity_main. XML
<? xml version="1.0" encoding="utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.name}"/> </layout> // Entity class User data class User(val name: String) / / the Activity assignment override fun onCreate(savedInstanceState: Bundle?). { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.user = User("Bob") } Copy the code
With the @{} symbol, you can use data objects in your layout, and you can get assignment objects through DataBindingUtil. And the expression language in @{} supports a variety of operators, including arithmetic operators, logical operators and so on.
Observability refers to the ability of an object to inform other objects of changes in its data. With data binding libraries, you can make objects, fields, or collections observable.
For example, in the User class, we change the name property to an observable,
data class User(val name: ObservableField<String>)
val userName = ObservableField<String>()
userName.set("Bob")
val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) binding.user = User(userName) Copy the code
If userName changes, the TextView in the layout will change as well. This is the observable data object.
3) Generated binding classes
Just now we get binding layout is through DataBindingUtil. The setContentView method to generate ActivityMainBinding object and bind the layout. So how is the ActivityMainBinding class generated? As long as your layout is surrounded by the Layout attribute, the compiler will automatically generate a Binding class based on the layout file name, which will be converted to Pascal case with a Binding suffix at the end.
Binding objects are normally created by writing:
//Activity
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
} //Fragment @Nullable fun onCreateView( inflater: LayoutInflater? , container:ViewGroup? , savedInstanceState:Bundle?).: View? { mDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_layout, container, false) return mDataBinding.getRoot() } Copy the code
4) Bind the adapter
The adapter here refers to properties in the layout. For example, with the Android :text=”@{user.name}” expression, the library looks for setText(arg) methods that accept the type returned by user.getName(). The important thing is that we can now customize this adapter, which is a property in the layout and we can define its name and what it does. To a 🌰
@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String) {
Picasso.get().load(url).into(view)
}
<ImageView app:imageUrl="@{venue.imageUrl}" /> Copy the code
Define an externally accessible method in the class called loadImage, annotation @bindingadapter property name you need to define the property, in this case set to imageUrl. So you can use app:imageUrl in the layout and pass the value as String, and the system will find the adapter method and execute it.
5) Binding layout views to architectural components is a practical application, which is used in combination with other components of Jetpack to form a complete LAYERED MVVM architecture.
// Obtain the ViewModel component.
val userModel: UserViewModel by viewModels()
// Inflate view and obtain an instance of the binding class.
val binding: ActivityDatabindingMvvmBinding =
DataBindingUtil.setContentView(this, R.layout.activity_databinding_mvvm) // Assign the component to a property in the binding class. binding.viewmodel = userModel <data> <variable name="viewmodel" type="com.panda.jetpackdemo.dataBinding.UserViewModel" /> </data> class UserViewModel : ViewModel() { val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } init { currentName.value="zzz" } } Copy the code
6) Two-way data binding
So what we’ve been talking about is one-way binding, which means that in the layout, the view is bound to the data object, so how do you get the data object to be bound to the view? That is, when the view changes, the data object can also receive the message, forming a two-way binding.
It’s very simple, like an EditText, and the requirement is that when the EditText changes, the user object name changes, so you just change it from “@{}” to “@={}”.
/ / layout activity_main. XML
<? xml version="1.0" encoding="utf-8"? > <layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/> </data> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={user.name}"/> </layout> Copy the code
Simple enough, the bidirectional binding feature is also customizable. To a 🌰
object SwipeRefreshLayoutBinding {
// Method 1, bind data to view
@JvmStatic
@BindingAdapter("app:bind_refreshing")
fun setSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout,newValue: Boolean) { if(swipeRefreshLayout.isRefreshing ! = newValue) swipeRefreshLayout.isRefreshing = newValue } // Method 1, bind_refreshingChanged is notified when the view changes, and the view data is retrieved from that method @JvmStatic @InverseBindingAdapter(attribute = "app:bind_refreshing",event = "app:bind_refreshingChanged") fun isSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout): Boolean =swipeRefreshLayout.isRefreshing // Method 3, how does the view change to affect the data content @JvmStatic @BindingAdapter("app:bind_refreshingChanged",requireAll = false) fun setOnRefreshListener(swipeRefreshLayout: SwipeRefreshLayout,bindingListener: InverseBindingListener?). { if(bindingListener ! =null) swipeRefreshLayout.setOnRefreshListener { bindingListener.onChange() } } } <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:layout_width="match_parent" android:layout_height="match_parent" app:bind_refreshing="@={viewModel.refreshing }"> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> Copy the code
Add the bind_refreshing parameter viewModel.refreshing to the view, and then add it to the view as well. When the view changes, the bind_refreshingChanged event is called via the InverseBindingAdapter annotation, and the bind_refreshingChanged event tells us when the view changes. In this case, that is, swipeRefreshLayout decline will cause the data to change, so the data objects from isSwipeRefreshLayoutRefreshing method to obtain the latest numerical, namely from the view to update data.
When the View changes, the data object is updated, and the update notifies the View layer to refresh the UI, and the View changes cause the data object to update, and the loop continues indefinitely. So the way to prevent endless loops is to determine the data state of the view and update the view only when it changes.
Official document Demo code address
Lifecycles
Life-cycle-aware components perform operations in response to changes in the life-cycle state of another component, such as an Activity or Fragment. These components help you write more organized and often leaner code that is easier to maintain.
Lifecycles, called life-cycle-aware components, sense and respond to changes in the lifecycle state of another component, such as activities and fragments.
Some people might wonder, why should I import a library when there are only a few life cycles? Is there no need to write a life cycle with a library? What’s the benefit? Take 🌰 to give you a feel.
The first one is imported into the library, which can be imported according to the actual project situation
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" / /... Copy the code
There is now a location listener that needs to be turned on when the Activity is started and off when it is destroyed. The normal code is as follows:
class BindingActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState) myLocationListener = MyLocationListener(this) { location -> // update UI } } public override fun onStart(a) { super.onStart() myLocationListener.start() } public override fun onStop(a) { super.onStop() myLocationListener.stop() } internal class MyLocationListener( private val context: Context, private val callback: (Location) -> Unit ) { fun start(a) { // connect to system location service } fun stop(a) { // disconnect from system location service } } } Copy the code
At first glance, this is fine, but if you have too many classes to manage the life cycle, it’s not manageable. All classes are managed in the Activity and are easy to miss. So the solution is to decouple and let the classes that need to manage the lifecycle manage themselves so that activities don’t miss out and get bloated. The code:
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
myLocationListener = MyLocationListener(this) { location ->
// update UI
} lifecycle.addObserver(myLocationListener) } internal class MyLocationListener ( private val context: Context, private val callback: (Location) -> Unit ): LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun start(a) { } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun stop(a) { // disconnect if connected } } Copy the code
As simple as implementing the LifecycleObserver interface, you can annotate the methods to be executed for each lifecycle. Then bind the addObserver in the Activity.
Lifecycle also supports custom LifecycleOwner, simply by inheriting LifecycleOwner and setting your own class’s Lifecycle through the markState method, for example 🌰
class BindingActivity : AppCompatActivity(), LifecycleOwner {
private lateinit var lifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState) lifecycleRegistry = LifecycleRegistry(this) lifecycleRegistry.markState(Lifecycle.State.CREATED) } public override fun onStart(a) { super.onStart() lifecycleRegistry.markState(Lifecycle.State.STARTED) } } Copy the code
Official document Demo code address
LiveData
LiveData is an observable data store class. Unlike regular observable classes, LiveData has lifecycle awareness, meaning that it follows the lifecycle of other application components such as activities, fragments, or services. This awareness ensures that LiveData updates only application component observers that are in an active lifecycle state.
LiveData is an observable data store class. Wait, this introduction sounds familiar? That’s right. When we said data binding, we had an ObservableField, an ObservableField data object. So what’s the difference?
1) LiveData has life cycle awareness, which can sense the life cycle of activities and so on. What’s the good of that? One of the most common things you can do is to reduce memory leaks and crashes. Think of the old days when you had to check whether the current interface was destroyed when you returned data to the network interface. Now LiveData helps you solve this problem.
Why, exactly, should crashes and leaks be addressed?
Memory leaks do not occur
The observer will bind to Lifecycle objects and clean up after its associated Lifecycle has been destroyed.It does not crash when the Activity stops
If the observer’s life cycle is inactive (such as returning an Activity in the stack), it will not receive any LiveData events.Automatically determine the lifecycle and call back the method
If an observer’s life cycle is STARTED or RESUMED, LiveData thinks it is active and calls the onActive method. Otherwise, if a LiveData object does not have any active observers, the onInactive() method is called.
2) LiveData is more flexible in updating data, not necessarily by changing data, but by calling methods (postValue or setValue) to update UI or other operations.
All right. Or take a more intuitive look at 🌰 :
// Import library:
implementation "Androidx. Lifecycle: lifecycle - livedata - KTX: 2.2.0." "
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive(a) { stockManager.requestPriceUpdates(listener) } override fun onInactive(a) { stockManager.removeUpdates(listener) } } public class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?). { super.onViewCreated(view, savedInstanceState) val myPriceListener: LiveData<BigDecimal> = StockLiveData("") myPriceListener.observe(this, Observer<BigDecimal> { price: BigDecimal? -> // Listen for changes to liveData and call onChanged if setValue or postValue is called // Update UI data or other processing }) } } Copy the code
This is a stock data object. StockManager is the StockManager. If the object has active observers, it listens for the stock market. When a stock change is monitored, the stock data object is updated using the setValue method, reflecting the observer’s onChanged method. Note that setValue can only be called on the main thread, while postValue can be called on other threads. When the Fragment observer life cycle changes, LiveData will remove the observer and no longer send messages, thus avoiding crashes.
Official document Demo code address
Navigation
The Navigation component is intended for applications with one main Activity and multiple Fragment destinations. The main Activity is associated with the navigation diagram and contains a NavHostFragment responsible for exchanging destinations as needed. In an application with multiple Activity destinations, each Activity has its own navigation diagram.
So Navigation is basically a Fragment management framework. How do you do that? Create an Activity, Fragment, and connect.
1) Import the library
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
Copy the code
2) Create 3 Fragments and 1 Activity
3) Create the res/navigation/my_nav. XML file
<? xml version="1.0" encoding="utf-8"? ><navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/myFragment1"
tools:ignore="UnusedNavigation"> <fragment android:id="@+id/myFragment1" android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1" android:label="fragment_blank" tools:layout="@layout/fragmetn_my_1" > <action android:id="@+id/action_blankFragment_to_blankFragment2" app:destination="@id/myFragment2" /> </fragment> <fragment android:id="@+id/myFragment2" android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1" android:label="fragment_blank" tools:layout="@layout/fragmetn_my_1" > <action android:id="@+id/action_blankFragment_to_blankFragment2" app:destination="@id/myFragment3" /> </fragment> <fragment android:id="@+id/myFragment3" android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1" android:label="fragment_blank" tools:layout="@layout/fragmetn_my_1" > </fragment> </navigation> Copy the code
Create a new navigation directory under the RES folder and create the my_nav. XML file. Configure each Fragment, where:
app:startDestination
Property represents the fragment initially displayedandroid:name
Attribute represents the corresponding Fragment pathaction
Represents a jump event for the Fragment. For example, myFragment1 can jump to myFragment2.
- Modify the Activity layout file:
<? 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"
android:id="@+id/container"
android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/my_nav" /> </androidx.constraintlayout.widget.ConstraintLayout> Copy the code
As you can see, the layout file of the Activity is a fragment control. The name is NavHostFragment. NavGraph is the new myNavigation file.
5) After the configuration, you can set the specific jump logic.
override fun onClick(v: View) {
// No parameters
v.findNavController().navigate(R.id.action_blankFragment_to_blankFragment2)
/ / parameters
var bundle = bundleOf("amount" to amount)
v.findNavController().navigate(R.id.confirmationAction, bundle) } // Receive data tv.text = arguments? .getString("amount") Copy the code
Note that the jump section officially recommends the Safe Args Gradle plug-in, which generates simple Object and Builder classes for browsing and accessing any associated parameters in a type-safe manner. I won’t go into details here. If you are interested, you can go to the official website
Official document Demo code address
Room
The Room persistence library provides an abstraction layer on top of SQLite, allowing users to take full advantage of SQLite’s power while enjoying a more robust database access mechanism.
So Room is a database framework. There are so many database components on the market, such as ormLite, Greendao, etc., why Google also need to create a room, what are the advantages?
- Performance advantageA database operation consists of constructing SQL statements – compiling statements – passing in parameters – performing operations.
ORMLite
Mainly when the parameter attribute value is obtained, it is obtained through reflection, so the speed is slow.GreenDao
When constructing SQL statements, code concatenation is used, so it is slower.Room
SQL statements are generated from annotations of the interface method, that is, when compiled into bytecode, SQL statements are generated, so they are faster to run. - Support for other jetpack components (such as LiveData, Paging) and RxJava“, this is like the advantage of the current environment, can give you some unique advantages. Of course, it’s actually a lot easier to use, like
liveData
In combination, you can perform automatic UI updates after a data query.
Since Room is so good, use it. There are three main points for Room access: DataBase, Entity, and Dao. Corresponding to database, table and data access respectively.
1) First and foremost warehousing:
apply plugin: 'kotlin-kapt'
dependencies {
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor // optional - Kotlin Extensions and Coroutines support for Room implementation "androidx.room:room-ktx:$room_version" // optional - RxJava support for Room implementation "androidx.room:room-rxjava2:$room_version" } Copy the code
2) create a database class, declare the database table members, database name, database version, singleton, etc
@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDb : RoomDatabase() {
abstract fun userDao(a): UserDao
companion object { private var instance: UserDb? = null @Synchronized fun get(context: Context): UserDb { if (instance == null) { instance = Room.databaseBuilder(context.applicationContext, UserDb::class.java."StudentDatabase").build(a) } return instance!! } } } Copy the code
3) create table, you can set primary key, foreign key, index, increment and so on
@Entity
data class User(@PrimaryKey(autoGenerate = true) val id: Int. val name: String)
Copy the code
4) Dao, data operation
@Dao
interface UserDao {
@Query("SELECT * FROM User")
fun getAllUser(a): DataSource.Factory<Int, User>
@Query("SELECT * FROM User") fun getAllUser2(a): LiveData<List<User>> @Query("SELECT * from user") fun getAllUser3(a): Flowable<List<User>> @Insert fun insert(users: List<User>) } Copy the code
Then you can perform database operations, very simple. Official document Demo code address
Paging
Paging libraries help you load and display a small piece of data at a time. Loading partial data on demand reduces network bandwidth and system resource usage.
Therefore, Paging is a Paging library, mainly used for Recycleview list display. I’m going to talk about Paging in terms of Room. There are two main classes to note when using Paging: PagedList and PagedListAdapter. 1) PagedList is used to load application data blocks, bind data lists, set data pages, etc. Combined with the above Room Demo, I continue to write a UserModel for data management:
class UserModel(app: Application) : AndroidViewModel(app) {
val dao = UserDb.get(app).userDao()
var idNum = 1
companion object {
private const val PAGE_SIZE = 10 } // Initialize PagedList val users = LivePagedListBuilder( dao.getAllUser(), PagedList.Config.Builder() .setPageSize(PAGE_SIZE) .setEnablePlaceholders(true) .build() ).build() // Insert the user fun insert(a) = ioThread { dao.insert(newTenUser()) } // Get 10 new users fun newTenUser(a): ArrayList<User> { var newUsers = ArrayList<User>() for (index in 1.10) { newUsers.add(User(0."bob${++idNum}")) } return newUsers } } Copy the code
2) PagedListAdapter use Recycleview necessary to use Adatper, so here need to bind a inherit adapter from PagedListAdapter adapter:
class UserAdapter : PagedListAdapter<User, UserAdapter.UserViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder = UserViewHolder(parent) companion object { private val diffCallback = object : DiffUtil.ItemCallback<User>() { override fun areItemsTheSame(oldItem: User, newItem: User): Boolean = oldItem.id == newItem.id override fun areContentsTheSame(oldItem: User, newItem: User): Boolean = oldItem == newItem } } class UserViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)) { private val tv1 = itemView.findViewById<TextView>(R.id.name) var user: User? = null fun bindTo(user: User?). { this.user = user tv1.text = user? .name } } } Copy the code
The DiffUtil.ItemCallback class is also used to compare and update data.
Ok, data source, adapter are set, next is to listen to the data, refresh the data is ok
// Listen on users data and call submitList method when data changes
viewModel.users.observe(this, Observer(adapter::submitList))
Copy the code
Yeah, that’s it, listen for the PagedList and call the submitList method of the PagedListAdapter when it changes. This is the advantage that paging, or jetpack, brings to our project. The layers are decoupled so that adapters don’t have to maintain list data sources.
Official document Demo code address
ViewModel
The ViewModel class is designed to store and manage interface-related data in a life-cycle oriented manner. The ViewModel class allows data to persist after configuration changes such as screen rotation.
So ViewModel, which we’ve used a lot in previous demos, basically separates the view data from the interface controller logic, why do we do that? Mainly to solve two major problems:
- In the past, if an Activity is destroyed by the system or needs to be recreated, the temporary data of the page will be lost
onSaveInstanceState()
Method is saved and read in the onCreate method. And a large amount of data is even more inconvenient. - In an Activity, it is inevitable that there will be some asynchronous calls, so it is easy to make those calls exist when the interface is destroyed. There will be a memory leak or a crash.
So the ViewModel was born, again decoupled, and I managed the data separately, plus the life cycle, and that solved all of these problems. And when the owner Activity is completely destroyed, the ViewModel calls its onCleared() method to clean up the resource.
Here’s 🌰 to see how the ViewModel is used:
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
class SharedViewModel : ViewModel() { var userData = MutableLiveData<User>() fun select(item: User) { userData.value = item } override fun onCleared(a) { super.onCleared() } } class MyFragment1 : Fragment() { private lateinit var btn: Button override fun onViewCreated(view: View, savedInstanceState: Bundle?). { super.onViewCreated(view, savedInstanceState) valmodel=activity? .let { ViewModelProvider(it).get(SharedViewModel::class.java)} btn.setOnClickListener{ model? .select(User(0."bob")) } } } class MyFragment2 : Fragment() { private lateinit var btn: Button override fun onViewCreated(view: View, savedInstanceState: Bundle?). { super.onViewCreated(view, savedInstanceState) valmodel=activity? .let { ViewModelProvider(it).get(SharedViewModel::class.java)} model? .userData? .observe(viewLifecycleOwner, Observer<User> { item -> // Update the UI }) } } Copy the code
Fragment, obtain the instance of the ViewModel, and perform data listening operations. Wait, can you see anything? Oh, and data communications. Different fragments can use their parent Activity to share the ViewModel for data communication. Cool. There are many other uses, so find them in your project!
Official document Demo code address
WorkManager
Using the WorkManager API, you can easily schedule delayable asynchronous tasks that should run even when the application exits or the device restarts.
It’s amazing to hear that the app exits and the device restarts automatically, right? Over the air? How is the data stored? I heard that you can also perform periodic asynchronous tasks, sequential chain call oh! So let’s decrypt it one by one
- About application exit and device restart
If your APP is running,WorkManager
A new thread is started in the APP process to run the task. If your APP isn’t running,WorkManager
An appropriate method is chosen to schedule background tasks — which, depending on the system level and APP state, WorkManager might useJobScheduler, FireBase JobDispatcher
orAlarmManager
. - About Data Preservation
WorkManager
The created task data is saved to the database usingRoom
Framework. Then restart and other time will go to the database to find the task to be arranged and executed, and then judgeThe constraint
If yes, the command can be executed.
What kind of scenarios does this API apply to? Think about it, reliably running, and periodically asynchronously. By the way, send logs. You can set periodic tasks using the WorkManager to send logs once a day. And can ensure your task reliable operation, must be able to upload, of course, is also support to monitor the results of the task. 🌰 :
1) Import the library
dependencies {
def work_version = "2.3.4"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
// optional - RxJava2 support implementation "androidx.work:work-rxjava2:$work_version" // optional - GCMNetworkManager support implementation "androidx.work:work-gcm:$work_version" } Copy the code
2) Create a task class, inherit Worker, rewrite doWork method, and return the task result.
class UploadLogcatWork(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(a): Result {
if (isUploadLogcatSuc()) { return Result.success() } else if (isNeedRetry()){ return Result.retry() } return Result.failure() } fun isUploadLogcatSuc(a): Boolean { var isSuc: Boolean = false return isSuc } fun isNeedRetry(a): Boolean { var isSuc: Boolean = false return isSuc } } Copy the code
3) Finally, it is to set constraints (whether network is required, whether low battery is supported, whether charging execution is supported, delay, etc.) and execute tasks (single task or cyclic task).
// Set constraints
val constraints =
Constraints.Builder()
// Use it when you are connected to a network
.setRequiredNetworkType(NetworkType.CONNECTED)
// Whether to execute when the device is idle .setRequiresDeviceIdle(false) // Whether to execute when the battery is low .setRequiresBatteryNotLow(true) // Whether to execute when memory is low .setRequiresStorageNotLow(true) // Whether to execute when charging .setRequiresCharging(true) // Delay execution .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS) .build() // Set the loop task val uploadRequest = PeriodicWorkRequestBuilder<UploadLogcatWork>(1, TimeUnit.HOURS) .setConstraints(constraints) .addTag("uploadTag") .build() / / execution WorkManager.getInstance(applicationContext).enqueue(uploadRequest) // Listen for execution results WorkManager.getInstance(this) / / getWorkInfosByTagLiveData (" uploadTag ") through the tag / / get the work .getWorkInfoByIdLiveData(uploadRequest.id) // Get work by id .observe(this, Observer { it? .apply { when (this.state) { WorkInfo.State.BLOCKED -> println("BLOCKED") WorkInfo.State.CANCELLED -> println("CANCELLED") WorkInfo.State.RUNNING -> println("RUNNING") WorkInfo.State.ENQUEUED -> println("ENQUEUED") WorkInfo.State.FAILED -> println("FAILED") WorkInfo.State.SUCCEEDED -> println("SUCCEEDED") else -> println("else status ${this.state}") } } }) Copy the code
4) In addition to support task cancellation, task chain sequential call, etc
/ / cancel
fun cancelWork(a){
WorkManager.getInstance(applicationContext).cancelAllWorkByTag("uploadTag")
}
fun startLineWork(a){ // Image filter 1 val filter1 = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() // Image filter 2 val filter2 = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() // Image compression val compress = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() // Image upload val upload = OneTimeWorkRequestBuilder<UploadLogcatWork>() .build() WorkManager.getInstance(applicationContext) .beginWith(listOf(filter1, filter2)) .then(compress) .then(upload) .enqueue() } Copy the code
Official document Demo code address
conclusion
Jetpack- Architecture components end, everyone should eat 🌰 haha. Hopefully this article will help those of you who are not familiar with Jetpack.
Of course, this is more than enough, and in my opinion, this article is more of a popular science article that tells you who the Jetpack-architecture component is and what it does. In actual projects, we also need to establish the idea of MVVM, deeply understand the design significance of each component, and flexibly use components. (Attached is an official Jetpack practice project, you can check it out.) Finally, I hope everyone can build high-quality, simple and high-quality project architecture through Jetpack, so as to liberate productivity and become efficiency experts.
The attachment
Jetpack Practice Official Demo – Sunflower Article All related demos
One of your 👍 is my motivation to share ❤️.