Kotlin coroutine and use summary in Android (two and Jetpack architecture components used together)

Insert picture description here

In Android, it is officially recommended to use with Jetpack architecture components

(1) Introduction of coroutines

  • implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3’

(2) Introduce KTX extension of Jetpack architecture components

  • For ViewModelScope, please use androidx.lifecycle: lifecycle-viewmodel-ktx: 2.1.0-beta01 or higher.
  • For LifecycleScope, please use androidx.lifecycle: lifecycle-runtime-ktx: 2.2.0-alpha01 or higher.
  • For liveData, please use androidx.lifecycle: lifecycle-livedata-ktx: 2.2.0-alpha01 or higher.

ViewModelScope

Each ViewModel in the application defines a ViewModelScope. If the ViewModel is cleared, the coroutines started in this range will be automatically canceled. If you have work that needs to be done only when the ViewModel is active, coroutines are very useful at this time. For example, if you want to calculate some data for the layout, you should limit the work scope to the ViewModel, so that after the ViewModel is cleared, the system will automatically cancel the work to avoid consuming resources.

class MyViewModel: ViewModel() {
        init {
            viewModelScope.launch {
                // Coroutine that will be canceled when the ViewModel is cleared.
            }
        }
    }

LifecycleScope

LifecycleScope is defined for each Lifecycle object. Coroutines started within this range will be canceled when Lifecycle is destroyed. You can access the CoroutineScope of Lifecycle through the lifecycle.coroutineScope or lifecycleOwner.lifecycleScope property.

The following example demonstrates how to use lifecycleOwner.lifecycleScope to create pre-calculated text asynchronously:

class MyFragment: Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            viewLifecycleOwner.lifecycleScope.launch {
                val params = TextViewCompat.getTextMetricsParams(textView)
                val precomputedText = withContext(Dispatchers.Default) {
                    PrecomputedTextCompat.create(longTextContent, params)
                }
                TextViewCompat.setPrecomputedText(textView, precomputedText)
            }
        }
    }

LifecycleScope + lifecycle status

Lifecycle provides other methods: lifecycle.whenCreated, lifecycle.whenStarted and lifecycle.whenResumed. If Lifecycle is not at least in the minimum required state, any coroutines running in these blocks will be suspended.

The following example contains code blocks that will only run if the associated Lifecycle is at least STARTED:

class MyFragment: Fragment {
        init { // Notice that we can safely launch in the constructor of the Fragment.
            lifecycleScope.launch {
                whenStarted {
                    // The block inside will run only when Lifecycle is at least STARTED.
                    // It will start executing when fragment is started and
                    // can call other suspend methods.
                    loadingView.visibility = View.VISIBLE
                    val canAccess = withContext(Dispatchers.IO) {
                        checkUserAccess()
                    }

                    // When checkUserAccess returns, the next line is automatically
                    // suspended if the Lifecycle is not *at least* STARTED.
                    // We could safely run fragment transactions because we know the
                    // code won't run unless the lifecycle is at least STARTED.
                    loadingView.visibility = View.GONE
                    if (canAccess == false) {
                        findNavController().popBackStack()
                    } else {
                        showContent()
                    }
                }

                // This line runs only after the whenStarted block above has completed.

            }
        }
    }

If the Lifecycle is destroyed by a certain when method when the coroutine is active, the coroutine will be automatically canceled. In the following example, once the Lifecycle status becomes DESTROYED, the finally block will run:

class MyFragment: Fragment {
        init {
            lifecycleScope.launchWhenStarted {
                try {
                    // Call some suspend functions.
                } finally {
                    // This line might execute after Lifecycle is DESTROYED.
                    if (lifecycle.state >= STARTED) {
                        // Here, since we've checked, it is safe to run any
                        // Fragment transactions.
                    }
                }
            }
        }
    }

LifecycleScope + life cycle state usage summary
通过一种声明式的写法(即在Activity或者Fragment的初始化时,声明好各个生命周期需要做的事情),而不是程序性的写法(即在各个生命周期回调方法中处理相应逻辑),更有利于整体代码结构的把握,更易于理解和维护。所以建议大家多使用这种具有声明式的写法处理业务逻辑。

Use coroutines with LiveData

You can use the liveData builder function to call the suspend function and transfer the result as a LiveData object.

In the following example, loadUser () is a pause function declared elsewhere. Use the liveData builder function to call loadUser () asynchronously, then use emit () to issue the result:

val user: LiveData<User> = liveData {
        val data = database.loadUser() // loadUser is a suspend function.
        emit(data)
    }

When LiveData becomes active, the code block begins to execute; when LiveData becomes inactive, the code block is automatically cancelled after a configurable timeout. If the code block is cancelled before completion, it will restart after LiveData becomes active again; if it completed successfully in the previous run, it will not restart. Please note that the code block will only restart if it is automatically cancelled. If the code block is cancelled for any other reason (for example, a CancerationException is thrown), it will not restart.

You can also issue multiple values ​​from the code block. Each emit () call suspends execution of the code block:

val user: LiveData<Result> = liveData {
        emit(Result.loading())
        try {
            emit(Result.success(fetchUser()))
        } catch(ioException: Exception) {
            emit(Result.error(ioException))
        }
    }

You can also use liveData with Transformations, as shown in the following example:

class MyViewModel: ViewModel() {
        private val userId: LiveData<String> = MutableLiveData()
        val user = userId.switchMap { id ->
            liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
                emit(database.loadUserById(id))
            }
        }
    }

You can emit multiple values ​​from LiveData by calling the emitSource () function each time you want to emit a new value. Please note that each call to emit () or emitSource () will remove the previously added source.

class UserDao: Dao {
        @Query("SELECT * FROM User WHERE id = :id")
        fun getUser(id: String): LiveData<User>
    }

    class MyRepository {
        fun getUser(id: String) = liveData<User> {
            val disposable = emitSource(
                userDao.getUser(id).map {
                    Result.loading(it)
                }
            )
            try {
                val user = webservice.fetchUser(id)
                // Stop the previous emission to avoid dispatching the updated user
                // as `loading`.
                disposable.dispose()
                // Update the database.
                userDao.insert(user)
                // Re-establish the emission with success type.
                emitSource(
                    userDao.getUser(id).map {
                        Result.success(it)
                    }
                )
            } catch(exception: IOException) {
                // Any call to `emit` disposes the previous one automatically so we don't
                // need to dispose it here as we didn't get an updated value.
                emitSource(
                    userDao.getUser(id).map {
                        Result.error(exception, it)
                    }
                )
            }
        }
    }

Reference:
Android official website introduction:
use Kotlin coroutines to improve application performance,
use Kotlin coroutines with architecture components

Published 82 original articles · Like 86 · Visit 110,000+

Guess you like

Origin blog.csdn.net/unicorn97/article/details/104196789