なぜcorutineを使用するのか?
非同期処理のコールバック地獄を防ぐ為です。
corutineを使用することにより、非同期処理をより高速に、少ない行数で書くことができます。
もちろんcorutineを使用しなくても非同期処理をかけますが、corutineを使った方が遥かに簡単に実装できます。
この記事では3分でcorutine + retorofit2 を理解することを目指します。
セットアップ
Android StudieでEmptyActivityを選択し、kotolinでHelloWorldというアプリを作成してください。
4つのライブラリを追加
retrofit = http通信ライブラリ 2020年のhttpリクエストのデファクトスタンダード
converter-gson = retorofitで使用する jsonのパーサー
coroutines-core = coroutineのコア。 coroutineは、kotlinとは別々に提供される。
lifecycle-runtime = 後述するlifecycleScopeを使用する為のもの。 kotlin公式
// 以下二行は、retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
// 以下二行はcoroutine
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'
http通信を許可する
Androidでは通信を行うにはAndroidManifestに以下の一行を加え、通信を許可する必要がある。
<uses-permission android:name="android.permission.INTERNET"/>
をAndroidiManifestに加えて、http通信を許可する。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloworld">
+ <uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
実装
これから出てくるコードは全てMainActivityの中に記述する。厳密には正しくないが、シンプルさの為にいらないコードは削除している。
初期MainActivityは以下
package com.example.helloworld
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
モデルを作成する。
今回取得するjsonは以下
{
"name": "kabaya",
"job": "engineer"
}
そこでこのjsonに対応するモデルを作成し、retorofitがAPIレスポンスの結果を代入する。
// MainActivity.kt
data class ApiResponse(
val name: String,
val job: String
)
retrofitのセットアップ
retrofit自体は、jsonのパースをする機能はないため、ここでは有名なGsonというjsonをパースするライブラリを利用する。
GsonConverterFactory.create()が、retrofit用のGsonだ。
よくあるBuilderパターンでビルドした後createメソッドに後述するretorofitのinterfaceを渡す。
val api = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://gist.githubusercontent.com/t-kabaya/")
.build().create(ApiInterface::class.java)
retorofitのセットアップは一行で完成した。
interfaceを定義する
interfaceを定義する。
このinterface内に関数とその戻り値を定義しておく。
interface ApiInterface {
@GET("0616752fd86043f79f8a61e93f25d022/raw/a129469a6895c7e95fa0511efcf4c4f012ef0e0e/helloRetrofit")
suspend fun getUser(): ApiResponse
}
coroutineを作成する。
onCreateの中に以下の2行を書くことで、非同期処理を行うためのcoroutineを作成出来る。
lifecycleScopeは、Activity, Fragmentなどlifecycleがあるクラスで使用可能なcorutineのためのスコープだ。
lifecycleが破棄される時に、lifecycleScopeも同時に破棄される。
他にもいろいろな種類のScopeがあるので、適切に選択する必要がある。
今回はonCreate内部でcoroutineを作成するのでlifecycleScopeを使用する。
lifecycleScope.launch {
}
suspend関数
suspend関数は、coroutineまたは他のsuspend関数内でのみ使用可能な関数で、suspend関数内部で発生する非同期な処理が終了するのを待ってから、次の処理へ移る。
つまり、suspend関数内部でhttpリクエストを行えば、リクエストの結果が帰ってくるまで待ち、その後処理を進めることが可能だ。
先ほどインターフェースで定義したgetUserをここで使用する。
suspend fun getRequest(): ApiResponse? {
try {
val response: ApiResponse = api.getUser()
return response
} catch (e: Exception) {
return null
}
}
corutineの中でsuspend関数を実行
suspend関数は、corutineの中か、他のsuspend関数の中でしか使用できないことを思い出してほしい。
先ほど作成したcorutineの中でgetRequsetを呼ぶ。
非同期処理を、同期的に書くことに成功した。
lifecycleScope.launch {
val response = getRequest()
Log.d("response ", response.toString())
}
完成したコード
package com.example.helloworld
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
val api = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://gist.githubusercontent.com/t-kabaya/")
.build().create(ApiInterface::class.java)
data class ApiResponse(
val name: String,
val job: String
)
interface ApiInterface {
@GET("0616752fd86043f79f8a61e93f25d022/raw/a129469a6895c7e95fa0511efcf4c4f012ef0e0e/helloRetrofit")
suspend fun getUser(): ApiResponse
}
suspend fun getRequest(): ApiResponse? {
try {
val response: ApiResponse = api.getUser()
return response
} catch (e: Exception) {
return null
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
lifecycleScope.launch {
val response = getRequest()
Log.d("response ", response.toString()) // ApiResponse(name=kabaya, job=engineer)
}
}
}
お願い
いいねください。