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 layerPrimary data, such as server data, local database data, so network operations and database reads are at this level, only data is saved.
  • The View layerMainly refers to the UI related, such as XML layout files, Activity interface display
  • The ViewModel layerIt 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 occurThe observer will bind to Lifecycle objects and clean up after its associated Lifecycle has been destroyed.
  • It does not crash when the Activity stopsIf 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 methodIf 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:startDestinationProperty represents the fragment initially displayed
  • android:nameAttribute represents the corresponding Fragment path
  • actionRepresents a jump event for the Fragment. For example, myFragment1 can jump to myFragment2.
  1. 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.ORMLiteMainly when the parameter attribute value is obtained, it is obtained through reflection, so the speed is slow.GreenDaoWhen constructing SQL statements, code concatenation is used, so it is slower.RoomSQL 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, likeliveDataIn 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 lostonSaveInstanceState()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,WorkManagerA new thread is started in the APP process to run the task. If your APP isn’t running,WorkManagerAn appropriate method is chosen to schedule background tasks — which, depending on the system level and APP state, WorkManager might useJobScheduler, FireBase JobDispatcherorAlarmManager.
  • About Data Preservation

    WorkManagerThe created task data is saved to the database usingRoomFramework. Then restart and other time will go to the database to find the task to be arranged and executed, and then judgeThe constraintIf 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 ❤️.