Kotlin coroutine (2) – application in Android

A simple introduction

First impression

Lifecycle, LiveData, and ViewModel have been integrated in Android Jetpack to quickly use coroutines

The basic concept

ViewModelScope is a predefined CoroutineScope that is included in the ViewModel KTX extension

CoroutineScope keeps track of all coroutines it creates using launch or Async. Unlike the scheduler, CoroutineScope does not run coroutines

A Job is a handle to a coroutine. Each coroutine created using Launch or Async returns a Job instance, which uniquely identifies the coroutine and manages its life cycle

Simple to use

The basic use

  1. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  2. Switch scope via viewModelScope.launch to add delay functions

    fun updateTaps(a) {
        viewModelScope.launch {
            _tapCount++
            delay(1 _000)
            _taps.postValue("$_tapCount taps")}}Copy the code

Simulated network request

  1. Kotlin coroutines andRetroftThis will be demonstrated in the next article, where the example is used to simulate local requests
  2. Synchronous code form initiates an asynchronous request throughtry... catchCatch exceptions
    private fun refreshTitle(a) {
        viewModelScope.launch {
            try {
                _spinner.value = true
                repository.refreshTitle()
            } catch (error: TitleRefreshError) {
                _snackBar.value = error.message
            } finally {
                _spinner.value = false}}}Copy the code
  3. inRepositoryTriggers the network request
    suspend fun refreshTitle(a) {
        withContext(Dispatchers.IO) {
            val result = try {
                network.fetchNextTitle().execute()
            } catch (cause: Throwable) {
                throw TitleRefreshError("Unable to refresh title", cause)
            }
    
            if (result.isSuccessful) {
                wordDao.insert(Word(result.body()!!))
            } else {
                throw TitleRefreshError("Unable to refresh title".null)}}}Copy the code
  4. writeservice
    // Here local data is used to simulate network requests through interceptors
    interface MainService {
    
        @GET("next_title.json")
        fun fetchNextTitle(a): Call<String>
    
        companion object {
            private const val BASE_URL = "http://localhost/"
    
            fun create(a): MainService {
                val logger = HttpLoggingInterceptor()
                logger.level = HttpLoggingInterceptor.Level.BASIC
    
                val client = OkHttpClient.Builder()
                    .addInterceptor(logger)
                    .addInterceptor(SkipNetworkInterceptor())
                    .build()
                return Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .client(client)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
                    .create(MainService::class.java)
            }
        }
    }
    Copy the code
  5. throughHiltregisteredservice
    @InstallIn(SingletonComponent::class)
    @Module
    object NetworkModule {
    	...
        @Provides
        @Singleton
        fun provideMainService(a): MainService {
            return MainService.create()
        }
    }
    Copy the code

Optimizing network requests

  1. RetrofitSupports mount functions that can return data types directly
    @GET("next_title.json")
    suspend fun fetchNextTitle(a): String
    Copy the code
  2. inRepositoryThere is no need to manually switch the thread, the mount function automatically switch
    suspend fun refreshTitle(a) {
        try {
            val result = network.fetchNextTitle()
            wordDao.insert(Word(result))
        } catch (cause: Throwable) {
            throw TitleRefreshError("Unable to refresh title".null)}}Copy the code
  3. Higher-order functions encapsulate specific requests
    private fun refreshTitle(a) {
        launchDataLoad {
            repository.refreshTitle()
        }
    }
    
    private fun launchDataLoad(block: suspend() - >Unit): Job =
        viewModelScope.launch {
            try {
                _spinner.value = true
                block()
            } catch (error: TitleRefreshError) {
                _snackBar.value = error.message
            } finally {
                _spinner.value = false}}Copy the code

Coroutines test

  1. updateTaps()Methods test
    // coroutineRule is the TestWatcher implementation class that specifies the coroutine scope
    @Test
    fun testDefaultValues(a) = coroutineRule.runBlockingTest {
        viewModel.updateTaps()
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("0 taps")
        coroutineRule.testDispatcher.advanceTimeBy(1 _000)
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("1 taps")
        viewModel.updateTaps()
        coroutineRule.testDispatcher.advanceTimeBy(1 _000)
        Truth.assertThat(viewModel.taps.getOrAwaitValue()).isEqualTo("2 taps")}Copy the code
  2. Network test
  • andRetroftCombing with

Related knowledge points

The Kotlin coroutine uses the scheduler to identify threads, calling withContext() to create a block of code to run in the specified thread

  • Dispatchers.Main – Use this scheduler to run coroutines on the Android Main thread.
  • Dispatchers.IO – This scheduler has been optimized for performing disk or network I/O outside of the main thread
  • Dispatchers.Default – This scheduler has been specially optimized for performing cpu-intensive work outside of the main thread, the Default scheduler

2. Processes and threads

  • When an application component is started and no other components are running, the Android system uses a single thread of execution to start a new Linux process for the application
  • By default, all components of the same application run in the same process, and most applications should not change this. Component elements are supportedandroid:processProperty that specifies in which process the component should run
  • When an application is started, the system creates a thread of execution called “main” for the application
  • Android provides several ways to access interface threads from other threads
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)

A link to the

  • Kotlin coroutine (I) — language characteristics

The resources

  • codelab
  • website