【kotlin】超簡単なMVPを実装してみた、③RemoteRepository設定(APIのやり取り)

# はじめに
今回はMVP(アーキテクチャ)に沿って、RemoteRepository設定(API通信のやり取り)を実装していきたいと思います。

本記事は「【kotlin】超簡単なMVPを実装してみた、②LocalRepository設定(ローカルデータに保存、読み込み)」の内容を引き継いで実装しています。

これまでの内容は
[【kotlin】超簡単なMVPを実装してみた、①Presenterの実装](https://qiita.com/Na0yaKub0/items/d688c440dfa9c3a49a7f)
[【kotlin】超簡単なMVPを実装してみた、②LocalRepository設定(ローカルデータに保存、読み込み)](https://qiita.com/Na0yaKub0/items/7209258a0d7b084d276a)
をご参照ください

# 実装内容
APIは[「楽天ブックス雑誌検索API」](https://webservice.rakuten.co.jp/documentation/books-magazine-search)を使用する。
EditTextに文字を入力してボタンを押したら、APIから本を検索し、タイトル、画像、値段を表示する。
![Screenshot_1642310088.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784089/a9c4310f-0b38-0477-1550-1d7b0c879346.png)

# 実装
**[準備]**
[「楽天ブックス雑誌検索API」](https://webservice.rakuten.co.jp/documentation/books-magazine-search)
から、アプリIDを獲得する。

**[レイアウト]**
①editText
②button
③textView
④bookTitleTextView
⑤bookImageView
⑥bookPriceTextView
![Screenshot_1642310088.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784089/e8d9a568-e3f8-1b14-5696-530e791954cd.png)

**[コード]**
**・RemoteRepository設定**
①androidmanifestに<uses-permission android:name="android.permission.INTERNET"/>を追加する。
```
<uses-permission android:name="android.permission.INTERNET"/>
```
②Retrofit設定
※詳しい説明は割愛させていただきます。

```kotlin:BooksEntity
data class BooksEntity(
    var Items: ArrayList<ItemsData>?
)

data class ItemsData(
    var Item:ItemData?
)

data class ItemData(
    //商品タイトル
    var title: String ="",
    //値段
    var itemPrice: String ="",
    //画像URL
    var largeImageUrl: String ="",
)
```

```groovy:app/build.gradle
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
```

```kotlin:ApiService
interface ApiService {
    @GET("20170404?applicationId=(獲得した楽天BooksアプリID)")
    fun getBooks(
        @Query("format") format:String,
        @Query("keyword") keyword:String,
        @Query("booksGenreId") booksGenreId:String,
        @Query("sort") sort:String,
    ): Call<BooksEntity>
}
```

```kotlin:ApiClientManager
class ApiClientManager {

    //Retrofitクラスのインスタンス化
    val retrofit = Retrofit.Builder()
        //ベースURLを入力
        .baseUrl("https://app.rakuten.co.jp/services/api/BooksTotal/Search/")
        //レスポンスを変換する設定を追加
        .addConverterFactory(GsonConverterFactory.create())
        //Httpクライアント結合
        .client(getOkHttpClient())
        //onResponseバックグラウンド処理
        .callbackExecutor(Executors.newSingleThreadExecutor())
        .build()

    // サービスクラスの実装オブジェクト取得(インターフェイスを利用する)
    val apiClient:ApiService = retrofit.create(ApiService::class.java)

    private fun getOkHttpClient(): OkHttpClient {
        val httpClient = OkHttpClient.Builder()
            .readTimeout(30,TimeUnit.SECONDS)
            .writeTimeout(30,TimeUnit.SECONDS)
            .build()
        return httpClient
    }

}
```
③RemoteRepositoryを作成
※RemoteRepositoryの役割はPresenterに橋渡し、Presenterで使用するメソッドを定義する。
④RemoteRepositoryで使用するメソッドを追加する。

```kotlin:RemoteRepository
interface RemoteRepository {
    //④本を検索しデータを持ってくる。
    fun getBooks(word: String, callback: Callback<BooksEntity>)
}

```
⑤ApiClientManager戻り値にRemoteRepositoryを追加する。
⑥RemoteRepositoryで設定したメソッドが出るので設定。

```kotlin:ApiClientManager
//⑤戻り値にRemoteRepositoryを追加する。
class ApiClientManager: RemoteRepository {

    val retrofit = Retrofit.Builder()
        .baseUrl("https://app.rakuten.co.jp/services/api/BooksTotal/Search/")
        .addConverterFactory(GsonConverterFactory.create())
        .client(getOkHttpClient())
        .callbackExecutor(Executors.newSingleThreadExecutor())
        .build()
    val apiClient:ApiService = retrofit.create(ApiService::class.java)

    private fun getOkHttpClient(): OkHttpClient {
        val httpClient = OkHttpClient.Builder()
            .readTimeout(30,TimeUnit.SECONDS)
            .writeTimeout(30,TimeUnit.SECONDS)
            .build()
        return httpClient
    }

    //⑥本を検索するメソッド
    override fun getBooks(word: String, callback: Callback<BooksEntity>) {
        apiClient.getBooks(format = "json", keyword = word, booksGenreId = "000", sort = "standard").enqueue(callback)
    }
}
```

**・Presenter設定**
①Presenterの引数にRemoteRepositoryを追加。

```kotlin:MainPresenter
//①Presenterの引数にRemoteRepositoryを追加。
class MainPresenter(private val view: MainContract.View, private val localRepository:  LocalRepository, private val remoteRepository: RemoteRepository): MainContract.Presenter {

    init {
        view.presenter = this
    }

    override fun start() {
        val text =localRepository.loadText()
        view.showTextView(text)
    }

    override fun onClickButton(text: String) {
        localRepository.saveText(text)
        view.showTextView(text)
    }

}
```
**・Contract設定**
①UIが増えたのでview側で使用するメソッドを作成。

```kotlin:MainContract

interface MainContract {

    interface View: BaseView<Presenter> {
        fun showTextView (text: String)

        //①bookTitleTextViewに本のタイトルを表示させるメソッド
        fun showBookTitleTextView (text: String)
        //①bookPriceTextViewに値段を表示させるメソッド
        fun showBookPriceTextView (text: String)
        //①bookImageViewに画像を表示させるメソッド
        fun showBookImageView (bitmap: Bitmap)
    }

    interface Presenter: BasePresenter {
        fun onClickButton(text: String)
    }
}

```
**・View設定**
①追加したUIを獲得。
②Contractで追加したメソッドが出てくるので、設定。
③Presenterに引数にApiClientManager()を追加。

```kotlin:MainFragment

class MainFragment: Fragment(),MainContract.View {
    override lateinit var presenter: MainContract.Presenter
    lateinit var editText :EditText
    lateinit var button :Button
    lateinit var textView :TextView

    //①追加したUIを獲得。
    lateinit var bookTitleTextView :TextView
    lateinit var bookImageView :ImageView
    lateinit var bookPriceTextView :TextView

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_main, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        editText = view.findViewById<EditText>(R.id.editTextText)
        button = view.findViewById<Button>(R.id.button)
        textView = view.findViewById<TextView>(R.id.textView)

        //①追加したUIを獲得。
        bookTitleTextView = view.findViewById<TextView>(R.id.bookTitleTextView)
        bookImageView = view.findViewById<ImageView>(R.id.bookImageView)
        bookPriceTextView = view.findViewById<TextView>(R.id.bookPriceTextView)

        //③Presenterに引数にApiClientManager()を追加。
        initPresenter()
        presenter.start()
        button.setOnClickListener{
            presenter.onClickButton(editText.text.toString())
        }
    }
    override fun showTextView(text: String) {
        textView.text = text
    }

    //②bookTitleTextViewに本のタイトルを表示する。
    override fun showBookTitleTextView(text: String) {
        bookTitleTextView.text = text
    }
    //②bookPriceTextViewに本の値段を表示する。
    override fun showBookPriceTextView(text: String) {
        bookPriceTextView.text = text
    }
    //②bookImageViewに本の画像を表示させる。
    override fun showBookImageView(bitmap: Bitmap) {
        activity?.runOnUiThread {
            bookImageView.setImageBitmap(bitmap)
        }
    }
    fun initPresenter() {
        if (::presenter.isInitialized) {
            return
        }
        context?.let {
            presenter = MainPresenter(this, PreferenceManager(it),ApiClientManager())
        }
    }
}

```

**・Presenter設定**
①LocalRepositoryのメソッドを呼び出す。
②通信が成功した場合、view側に反映させる。

```kotlin:MainPresenter
class MainPresenter(private val view: MainContract.View, private val localRepository:  LocalRepository, private val remoteRepository: RemoteRepository): MainContract.Presenter {
    init {
        view.presenter = this
    }
    override fun start() {
        val text =localRepository.loadText()
        view.showTextView(text)
    }
    override fun onClickButton(text: String) {
        localRepository.saveText(text)
        view.showTextView(text)

        //①LocalRepositoryのgetBooksメソッドを呼び出す。
        remoteRepository.getBooks(text, object: Callback<BooksEntity>{
            override fun onResponse(call: Call<BooksEntity>, response: Response<BooksEntity>) {
                if(response.isSuccessful){
                    //②通信が成功した場合showBookTitleTextViewを使用し、View側に本のタイトルを反映させる。
                    response.body()?.Items?.get(0)?.Item?.title?.let {
                        view.showBookTitleTextView(it)
                    }
                    //②通信が成功した場合showBookImageViewを使用し、View側に画像を反映させる。
                    response.body()?.Items?.get(0)?.Item?.largeImageUrl?.let {
                        val url = URL(it)
                        val streem = url.openStream()
                        val bitmap = BitmapFactory.decodeStream(streem)
                        view.showBookImageView(bitmap)
                    }
                    //②通信が成功した場合showBookPriceTextViewを使用し、View側に値段を反映させる。
                    response.body()?.Items?.get(0)?.Item?.itemPrice?.let {
                        view.showBookPriceTextView(it+"円")
                    }
                } else {

                }
            }
            override fun onFailure(call: Call<BooksEntity>, t: Throwable) {
            }

        })
    }
}

```

以上、RemoteRepositoryの実装でした。

# 関連記事
[【kotlin】超簡単なMVPを実装してみた、①Presenterの実装](https://qiita.com/Na0yaKub0/items/d688c440dfa9c3a49a7f)
[【kotlin】超簡単なMVPを実装してみた、②LocalRepository設定(ローカルデータに保存、読み込み)](https://qiita.com/Na0yaKub0/items/7209258a0d7b084d276a)

# 参考資料
[Android アプリ設計パターン入門](https://peaks.cc/books/architecture_patterns)
[テストが書けない人のAndroid MVP](https://www.amazon.co.jp/%E3%83%86%E3%82%B9%E3%83%88%E3%81%8C%E6%9B%B8%E3%81%91%E3%81%AA%E3%81%84%E4%BA%BA%E3%81%AEAndroid-MVP-%E6%8A%80%E8%A1%93%E3%81%AE%E6%B3%89%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%EF%BC%88NextPublishing%EF%BC%89-%E9%AB%98%E7%95%91-%E5%8C%A1%E7%A7%80/dp/4844398717)
[todo-mvp](https://github.com/android/architecture-samples/tree/todo-mvp-kotlin/todoapp)