Androidでmultipartで画像とテキストを同時送信

# まえがき
Android開発で少し手こずったので備忘録として，載せておきます．
幾分冗長だなとか多々あると思いますが，長い目でよろしくお願いします．:star2:

# やりたいこと
Android開発で画像とテキストを`multipart`で自社サーバへ同時送信

# 使用ライブラリ
Retrofit2

# 仕様
## 概要
今回はチャットのようなアプリをイメージしてください．
送信は以下の4通りに分かれます．

| 画像  | テキスト |備考|
|:--:|:--:|:--:|
|◯|◯|画像とテキスト同時送信|
|◯|×|画像のみ送信|
|×|◯|テキストのみ送信|
|×|×|何も入力されていない|

今回の実装プログラムでは，上3つに対応しております．
一番下は必ずエラー処理として，PopUpみたいなのを出すといいと思われます．
今回は実装してないです．:frowning2:

## WebAPIの仕様
画像とテキストを投稿するにあたってサーバへPOSTリクエストを行います．
ここで，POSTリクエストの際に以下のようなbodyが要求されています．

| パラメタ | 型 |備考|
|:--:|:--:|:--:|
|user_id| int |誰が送ったのかが必要なので必須|
|message| string |投稿本文|
|img_file| string |画像|

# 送信実装
## ライブラリのインストール
本セクションでは使用したライブラリを記述します．

```java:build.gradle
dependencies {
...
    //==================== Network ====================
    implementation "com.squareup.okhttp3:logging-interceptor:3.9.1"
    implementation "com.squareup.retrofit2:retrofit:2.3.0"
    implementation "com.squareup.retrofit2:converter-moshi:2.3.0"
    implementation "com.squareup.retrofit2:adapter-rxjava:2.3.0"
    
...
}
```

## 送信
本セクションでは実際にコードを使って`multipart`送信を行います．
postEntry関数を呼び出すと投稿が実行されます．
画像は写真で撮影およびギャラリーからの参照で取得できると思いますが，それはここではあえて言いません．
画像は`File型`です！
これは注意です．

まず実装コードです．

```Kotlin:MainActivity.kt

import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
import retrofit2.converter.moshi.MoshiConverterFactory

...

/**
 * 投稿を送信する関数
 */
private fun postEntry(userId: Int, body: String, image: File) {
    // =====送信するデータを整形する=====
    val map: MutableMap<String, RequestBody> = HashMap() // このオブジェクトを送信する

    val userId = RequestBody.create(MediaType.parse("text/plain"), userId.toString())
    val body = RequestBody.create(MediaType.parse("text/plain"), body)
    val requestBody = RequestBody.create(MediaType.parse("image/jpg"), image) // 拡張子は任意で変えてください
    map.put("user_id", userId)
    map.put("body", body)
    map.put("img_file\"; filename=\"image.png\"", requestBody)
   
    createApiClient().postEntry(params = map)
            .subscribeOn(Schedulers.newThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object: Subscriber<PostResponse>() {
                override fun onNext(r: PostResponse?) {  // 成功
                    Log.d("MainActivity", r.toString())
                }
                override fun onError(e: Throwable?) {
                    Log.d("MainActivity", e.toString())  // 失敗
                }
                override fun onCompleted() {
                }
            })
}

...

/**
 * APIクライアントのインスタンスを生成して返却する関数
 */

fun createApiClient(): Api {
    val moshi = Moshi.Builder()
            .build()
    val okClient = OkHttpClient()

    val builder = Retrofit.Builder()
            .client(okClient)
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .addConverterFactory(MoshiConverterFactory.create(moshi))
            .baseUrl("https://自社サーバのどめいん/")
            .build()
    return builder.create(ApiInterface::class.java)
}

...
```

次にレスポンスモデルです．

```Kotlin:PostResponse.kt
import java.io.Serializable

data class PostResponse(
        var message: String
) : Serializable
```

次に，`Retrofit`を用いる時のインタフェースです．

```Kotlin:ApiInterface.kt
...
@JvmSuppressWildcards
@Multipart
@POST("/ディレクトリhoge/ディレクトリfuga")
fun postEntry(
        @PartMap params: Map<String, RequestBody>): Observable<PostResponse>
...
```

# 終わりに
ちょっと改造してテストはしてないですが，こちらで多分通ると思います．
質問や改善要求などどしどしためらいもなく，うぇるかむです．
読んでいただきありがとうございました．

swiftの場合-> https://qiita.com/hisakioomae/items/0190165674fd241454d4