6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Android】MVVMで、Retrofit2 + Kotlin CoroutinesでHttp通信でqiitaの記事検索するやつ作った

Last updated at Posted at 2020-11-14

【Android】MVVMで、Retrofit + Kotlin CoroutinesでHttp通信をやってみた

概要

MVVMの勉強のため、Retrofit2とKotlin Coroutinesを使って、Http通信をして、qiitaの記事を検索するアプリを作成しました。

ゴール

MVVMでのHttp通信の実装に少しでも参考になればと思います。

実装する機能 

Qiita APIを使って、記事一覧を取得する。
文字を入力していくと検索する

今回作ってみたもの

画像サイズ.png

使用するライブラリ

  • AAC

    • LiveData
    • AndroidViewModel
  • Retrofit2

    • Retrofitは、ver2.6以降でcoroutinesに対応したので、2.6以降を使用するように注意してください。
  • Moshi

    • サーバからのレスポンスのJSONをjavaで扱えるように変換するライブラリ
  • Kotlin Coroutines

実装

セットアップ

  • 依存関係
    • appのbuild.gradleに以下を追加 
/app/build.gradle
apply plugin: 'kotlin-kapt'

dependencies {
  // Retrofit2 & Moshi
  def retrofit_version = "2.6.2"
  implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
  implementation "com.squareup.retrofit2:converter-moshi:$retrofit_version"

  // RecyclerView
  implementation 'androidx.recyclerview:recyclerview:1.1.0'
}

レイアウトの作成

メイン画面のレイアウト

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.MainActivity">

    <androidx.appcompat.widget.SearchView
        android:id="@+id/search_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:queryHint="キーワード検索"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/search_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

リストアイテムのレイアウト

list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/author_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/qiita_title"
        android:layout_toRightOf="@id/author_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/user_name"
        android:layout_toRightOf="@id/author_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

実装

View

  • MainActivity
  • Activityでは、リサイクラービューの設定として、viewModelから取得した記事の情報を渡すなどを主にやっています。
MainActivity.kt
class MainActivity : AppCompatActivity() {
  // viewModel
  private val viewModel: MainViewModel by lazy {
    ViewModelProvider.AndroidViewModelFactory().create(MainViewModel::class.java)
  }

  override fun onCreate(savedInstanceState: Bundle?) {
     // searchViewの設定
     var searchView = findViewById(R.id.search_view)
   // searchViewに対する入力のリスナーを設定
   // viewModelを渡しています。
     searchView.setOnQueryTextListener(SearchViewListener(viewModel))
     searchView.setIconifiedByDefault(false)

     // recyclerViewの設定
     var layoutManager = LinearLayoutManager(activity)
     var viewAdapter = ArticleListViewAdapter()

     // viewModelのQiita記事のリストをオブザーブする
     viewModel.articles.observe(viewLifecycleOwner, Observer { it ->
         // recyclerViewのAdapterに、取得した記事の情報を渡す
         it?.let { viewAdapter.setArticles(it) }
     })

     // recyclerViewをセット
     var recyclerView = findViewById<RecyclerView>(R.id.recyclerView).also {
          it.layoutManager = layoutManager
          it.adapter = viewAdapter
     }
  }

  // searchViewのリスナークラス
  class SearchViewListener(val viewModel: SearchViewModel): OnQueryTextListener {
        // 文字が入力されたタイミングで実行される
        override fun onQueryTextChange(newText: String?): Boolean {
            viewModel.searchArticles(newText)
            return false
        }
        
        // 検索が実行されたタイミングで実行される
        override fun onQueryTextSubmit(query: String?): Boolean {
            viewModel.searchArticles(query)
            return false
        }
    }
}

ViewModel

  • MainViewModel
    • viewModelでは、repositoryに対してQiita記事を取得するメソッドを実行しています。
MainViewModel.kt

class SearchViewModel: ViewModel() {

    var articles: LiveData<List<Article>> = MutableLiveData<List<Article>>()
    private val qiitaRepository: QiitaRepository = QiitaRepository()

    fun searchArticles() {
        viewModelScope.launch(Dispatchers.IO) {
            articles = qiitaRepository.getArticles()
        }
    }
}

Model

  • QiitaRepository
QiitaRepository.kt

class QiitaRepository() {
    private var service: QiitaApiInterface = Retrofit.Builder()
        .baseUrl("https://qiita.com/api/v2/")
        .addConverterFactory(MoshiConverterFactory.create())
        .build()
        .create(QiitaApiInterface::class.java)

    // Qiita記事を取得するメソッド
    fun getArticles(query: String?): List<Article>? {
        try {
            val response = service.getArticles(query).execute()
       // リクエストが成功した場合
            if (response.isSuccessful) {
                return response.body()
            } else { // 失敗の時は今回は実装していません。
                Log.d("QiitaRepository", "GET ERROR")
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return null
    }
}
QiitaApiInterface.kt
interface QiitaApiInterface {

    // GET 
    @GET("items")
    suspend fun getArticles(
        @Query("query") query: String?
    ): Call<List<Article>>

}

data class Article (
    val id: String,
    val title: String,
    val user: User,
)

data class User (
    val id: String,
    val name: String,
    val profile_image_url: String,
)

6
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?