PicassoでBasic認証がかかった画像を表示する

[前回](https://qiita.com/sudachi808/items/896fcd997f4d776144e1)からの続きです。

Picassoで画像をダウンロードしてみたものの、特定の画像だけ表示されず、

「なんで表示されねーんだ:anger:」
「Basic認証がかかってるだと:question:」
「Picassoでどーやるんだ:question:」

ってなりました。

大部分で[前回](https://qiita.com/sudachi808/items/896fcd997f4d776144e1)の記事と重複しますが、この記事だけで解決できるように全部書きます。

## picasso2-okhttp3-downloaderの導入

~~Jakeさんが作った[picasso2-okhttp3-downloader](https://github.com/JakeWharton/picasso2-okhttp3-downloader)を使います。~~

(2018/11/9 追記)
ダウンローダ（OkHttp3Downloader）はPicasso組み込みのものを使います。
そうしないと、setSingletonInstanceでPicassoの全通信にOkHttp3Downloaderを指定した場合、画像のダウンロードが行われません。

ログを見たいので[logging-interceptor](https://github.com/square/okhttp/tree/master/okhttp-logging-interceptor)というのも追加しておきます。

```groovy:app/build.gradle
dependencies {

    // (省略)

    implementation 'com.squareup.picasso:picasso:2.71828'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
}
```

## 実装

このレイアウトの`imageView`にインターネットから画像をダウンロードして表示しようと思います。

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

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

</android.support.constraint.ConstraintLayout>
```

### 認証なし

まず、なんにも認証がかかっていない場合は、
[公式](http://square.github.io/picasso/)のサンプル通りに実装すれば、普通に画像ダウンロードされて表示されます。

```kotlin:MainActivity.kt
package com.example.picassosample

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Picasso.get()
                .load("http://i.imgur.com/DvpvklR.png")
                .into(this.imageView)
    }
}
```

### 認証あり

**Basic認証あり**の場合は、こんな感じで、Picassoのインスタンスを`Builer`経由で生成して、downloadメソッドの引数に`OkHttp3Downloader`クラスのインスタンスを渡すようにします。
この時、通信に使う`OkHttpClient`クラスに`Interceptor`を追加して、HTTP通信に認証ヘッダをねじ込むようにしておきます。

```kotlin:MainActivity.kt
package com.example.picassosample

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.squareup.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.Credentials
import okhttp3.OkHttpClient

class MainActivity : AppCompatActivity() {

    private val picasso: Picasso by lazy {

        val client = OkHttpClient.Builder()
                .addInterceptor { chain ->
                    val request = chain.request().newBuilder()
                            .addHeader("Authorization", Credentials.basic("basic_username", "basic_password"))
                            .build()
                    chain.proceed(request)
                }
                // ↓↓ログ出力用
                .addInterceptor(HttpLoggingInterceptor().apply {
                    level = HttpLoggingInterceptor.Level.HEADERS
                })
                // ↑↑ログ出力用
                .build()

        val downloader = OkHttp3Downloader(client)

        Picasso.Builder(this)
                .downloader(downloader)
                .build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        this.picasso
                .load("https://url/with/basic/auth/sample.png")
                .into(this.imageView)
    }
}
```
こうすることで、Basic認証のかかった画像を表示できるようになります。

`addInterceptor`の代わりに↓↓でもいけるようです。
`authenticator`と言うからにはこちらの方が正しい対応のような気がします。

```kotlin
                .authenticator { _, response ->
                    response.request().newBuilder()
                            .addHeader("Authorization", Credentials.basic("basic_username", "basic_password"))
                            .build()
                }
```

ログを見てみると認証ヘッダが追加されていることも分かります。

```text:logcat
08-31 15:46:19.212 12643 12665 D OkHttp  : --> GET https://url/with/basic/auth/sample.png
08-31 15:46:19.212 12643 12665 D OkHttp  : Authorization: Basic YmFzaWNfdXNlcm5hbWU6YmFzaWNfcGFzc3dvcmQ=
08-31 15:46:19.212 12643 12665 D OkHttp  : --> END GET
```

しかし...

### 画像をキャッシュさせたい

上記のようなレイアウトの場合は分からなかったのですが、、ListViewに画像を表示させてみると、画像がキャッシュされないことに気づきました。
この場合、どうすればキャッシュが有効になるのかというと...

前出のコードをちょっと修正して、setSingletonInstanceというメソッドをpicassoのインスタンスを引数にして呼び出しておきます。
画像ロード時のPicassoの呼び出しは通常の呼び出し方法に戻します。

```kotlin:MainActivity.kt
package com.example.picassosample

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.squareup.picasso.OkHttp3Downloader
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.Credentials
import okhttp3.OkHttpClient

class MainActivity : AppCompatActivity() {

    private val picasso: Picasso by lazy {

        val client = OkHttpClient.Builder()
                .addInterceptor { chain ->
                    val request = chain.request().newBuilder()
                            .addHeader("Authorization", Credentials.basic("basic_username", "basic_password"))
                            .build()
                    chain.proceed(request)
                }
                // ↓↓ログ出力用
                .addInterceptor(HttpLoggingInterceptor().apply {
                    level = HttpLoggingInterceptor.Level.HEADERS
                })
                // ↑↑ログ出力用
                .build()

        val downloader = OkHttp3Downloader(client)

        Picasso.Builder(this)
                .downloader(downloader)
                .build()
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Picasso.setSingletonInstance(this.picasso)  // <-- ここ

        Picasso.get()                               // <-- ここ
                .load("https://url/to/be/required/sample.png")
                .into(this.imageView)
    }
}
```
このサンプルだと効果がわかりづらいですが、こうしておくと画像がキャッシュされるようになりました。

とりあえずは以上です。