この記事はPart3です。Part2(UI実装編)はこちらをご覧ください。
Part3では、Web APIからデータを取得してアプリに反映させます。楽しくなってきた。
Web API通信実装の準備
Web APIからデータを取得するため、いくつかのライブラリを導入します。
app/build.gradle
にライブラリの設定を追加します。
...
dependencies {
....
+ // REST APIを使うためのライブラリ
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+
+ // JSONをKotlinのオブジェクトに変換するためのライブラリ
+ implementation "com.squareup.retrofit2:converter-moshi:2.9.0"
+ implementation "com.squareup.moshi:moshi-kotlin:1.13.0"
+
+ // ViewModelやActivityのKotlin拡張ライブラリ
+ implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
+ implementation "androidx.activity:activity-ktx:1.4.0"
}
データ取得処理の実装
Web API通信関連の処理はapp/src/main/java/com/example/wikiclient/data
ディレクトリ(パッケージ)に実装します。
ここで作成するクラス/インターフェースは、下記になります。
- APIレスポンスの内容を保存するクラス
- ApiClient
- Repository
APIレスポンスの内容を保存するクラス
まずは、app/src/main/java/com/example/wikiclient/data/model
にGetArticlesResponse
を作成しましょう。
data class GetArticlesResponse(
val query: Query,
) {
data class Query(
val pages: List<Page>,
) {
data class Page(
val index: Int,
val ns: Int,
val pageid: Int,
val terms: Terms?,
val thumbnail: Thumbnail?,
val title: String,
) {
data class Terms(
val description: List<String>,
)
data class Thumbnail(
val height: Int,
val source: String,
val width: Int,
)
}
}
}
Web APIから取得したデータはこのクラスに格納されます。
ApiClient
次に、app/src/main/java/com/example/wikiclient/data/remote
にWikiApiClient
を作成しましょう。
これはretrofitを使ったWeb APIへのリクエストをする際に必要なインターフェースです。
import com.example.wikiclient.data.model.GetArticlesResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface WikiApiClient {
@GET("w/api.php?action=query&format=json&formatversion=2&generator=prefixsearch&prop=pageimages%7Cpageterms&piprop=thumbnail&pithumbsize=50&pilimit=10&wbptterms=description")
suspend fun getArticleList(
@Query("gpssearch") gpssearch: String, // キーワード
@Query("gpslimit") gpslimit: Int, // 一回のリクエストで取得する最大件数
): Response<GetArticlesResponse>
}
このインターフェースでは、リクエストを行うAPIのエンドポイントと、そこに渡すクエリパラメータの設定を記述したメソッドを定義ます。
Repository
最後に、app/src/main/java/com/example/wikiclient/data
にWikiRepository
を作成します。
このクラスを作成しなくてもWeb APIからのデータ取得はできるのですが、Repository
クラスを作成することはGoogleが推奨しているため今回はそれに従います。
import com.example.wikiclient.data.model.GetArticlesResponse
import com.example.wikiclient.data.remote.WikiApiClient
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
interface WikiRepository {
suspend fun getArticles(keyword: String, limit: Int): GetArticlesResponse?
}
class WikiRepositoryImpl : WikiRepository {
// JSONからKotlinのクラスに変換するためのライブラリの設定
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
// REST APIを利用するためのライブラリの設定
private val retrofit = Retrofit.Builder()
.baseUrl("https://ja.wikipedia.org/") // 今回利用するWeb APIのURL
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
// WikiApiClientに定義したメソッドを呼び出すための設定
private val wikiService = retrofit.create(WikiApiClient::class.java)
// Web APIからデータを取得するメソッド
override suspend fun getArticles(keyword: String, limit: Int): GetArticlesResponse? {
return wikiService.getArticleList(keyword, limit).body()
}
}
このクラスでは、Web APIにリクエストを行うためのライブラリの初期化と、実際にWeb APIからデータを取得するメソッドを定義しています。
retrofitやmoshiのインスタンス生成について
今回の実装では、Repository中でmoshiやretrofitなどのライブラリのインスタンスを作成していますが、これは本当はあまり良くないです。(主にパフォーマンス面で)
一般的にはretrofitのインスタンスはシングルトンで生成し、Repositoryのコンストラクタに渡して利用することが多いです。
Repositoryクラスを作成する理由
ここで説明すると長くなりそうなので興味のある方は下記のリンク先をご参照ください。
https://developer.android.com/jetpack/guide/data-layer?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-architecture%23article-https%3A%2F%2Fdeveloper.android.com%2Fjetpack%2Fguide%2Fdata-layer
UIへのデータ反映の実装
ここまでの実装で、Web APIからのデータ取得ができるようになりました。
ここからは、実際にWeb APIからデータを取得して、データをUIに反映させましょう。
MainViewModel
app/src/main/java/com/example/wikiclient/ui
にMainViewModel
を作成します。
最近のAndroid開発では、ViewModel
というクラスを作成することが多いです。
Androidアプリは、画面回転などを行うと、Activityなどで保持している変数などが破棄されてしまうため、なんとか保持する必要があります。
ViewModel
はそういったデータを保持するために作成するクラスです。
ViewModelはAndroidアプリのデータ保持以外にも、アーキテクチャ上の様々な役割があります。
class MainViewModel(
private val repository: WikiRepository = WikiRepositoryImpl()
) : ViewModel() {
// 取得したデータを別クラスに通知するためのクラス
private val _articles = MutableStateFlow<List<Article>>(emptyList())
val articles: StateFlow<List<Article>> = _articles
// Repositoryからデータ取得のメソッドを呼び出す
fun fetchArticles(keyword: String, limit: Int) {
viewModelScope.launch(Dispatchers.IO) {
runCatching {
repository.getArticles(keyword, limit)
}.onSuccess { response ->
// データ取得成功時
if (response != null) postArticles(response)
}.onFailure {
// データ取得失敗時
Log.e("Fetch Failure", it.toString())
}
}
}
// 取得したデータをMainActivityに通知する
private fun postArticles(response: GetArticlesResponse) {
_articles.value = response.query.pages.map {
Article(
id = it.pageid,
title = it.title,
thumbnailUrl = it.thumbnail?.source ?: "",
)
}
}
}
MainViewModel
のコンストラクタに、先ほど作成したWikiRepository
を渡します。
L10のfetchArticles
メソッドでは、WikiRepository
のメソッドを呼び出し、Web APIからデータを取得します。
viewModelScope.launch(Dispatchers.IO)
は非同期処理のための記述です。
ネットワーク通信は、UIの描画などと異なるスレッドで行います。
取得したデータは、postArticles
メソッドでMainActivity
に通知します。
L5,6で使用しているStateFlowとは、あるクラスから別のクラスにデータを通知するためのクラスです。
MutableStateFlowは値を変更可能で、StateFlowは値の変更ができません。
一般的なAndroid開発では、L5, 6のように、内部で値を更新するためのMutableStateFlowと、別クラスに公開する読み取り専用のStateFlowをそれぞれ定義することが多いです。
https://developer.android.com/kotlin/flow?hl=ja
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ja
MainActivity
class MainActivity : AppCompatActivity() {
...
+ // ViewModelを作成
+ private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initArticleList()
- updateArticleList()
+ observeArticles()
+ // Web APIからのデータ取得開始("Android"というキーワードで20件取得)
+ viewModel.fetchArticles("Android", 20)
}
...
+ private fun observeArticles() {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.articles.collect { articles ->
+ // Web APIからのデータ取得に成功すると、ここに流れてくる
+ updateArticleList(articles)
+ }
+ }
+ }
+ }
+ // データをUIに反映
+ private fun updateArticleList(articles: List<Article>) {
+ articleListAdapter.submitList(articles)
+ }
- // RecycleViewにデータを反映
- private fun updateArticleList() {
- // リストに表示するデータをリスト形式で作成
- val articles: List<Article> = (0..10).map { number ->
- Article(number, "記事$number", "https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/155135/0c2db45b0bd4b1aa023f5a7da835b76c2d191bd4/x_large.png?1585895165")
}
MainActivity
では、先ほど作成したMainViewModel
のインスタンスを作成し、データの取得を開始します。
observeArticles
メソッドを作成し、データの取得が完了した際にViewModelからデータを受け取るための処理を記述します。
動かしてみる
ここまでできたら、例の再生ボタンを押してアプリを起動します。
画像がない記事も結構ありますが...これでデータ反映ができました。よしっ
次のパートではテキストの入力とボタンタップ時の処理を、ここまでのおさらいも兼ねてやってみます。次回、感動の最終回。