AndroidでRtrofitの接続先をSharedPreferencesに持つ場合

# Retrofitの接続先をSharedPreferenceに持つ場合

AndroidでHTTPで通信する場合、Retrofitを使う機会が多いと思います。
Retrofitの接続先のbaseUrlをSharedPreferenceに持つ場合があるともいます。
Androidアプリの中で設定画面を作って、SharedPreferenceのbaseUrlを更新しても、Retrofitの接続先には反映されません。
アプリを一度終了して、再度起動すると反映されます。

```kotlin
class NetworkRepository(baseUrl: String, connTimeout: Long, readTimeout: Long) {

    private var service: NetworkService
    /**
     * 初期化
     */
    init {
        logging.setLevel(HttpLoggingInterceptor.Level.BASIC)
        val client = OkHttpClient.Builder()
            .readTimeout(connTimeout, TimeUnit.SECONDS)
            .connectTimeout(readTimeout, TimeUnit.SECONDS)
            .addInterceptor(logging)
            .build()
        service = Retrofit.Builder()
            .baseUrl(baseUrl) // ★ ここが変わってくれない
            .client(client)
            .addConverterFactory(ScalarsConverterFactory.create())
            .build()
            .create(NetworkService::class.java)
    }

```
イマイチですね。

SharedPreferenceのbaseUrlを更新したら、即時にRetrofitの接続先には反映して欲しいですね。

# SharedPreferences#registerOnSharedPreferenceChangeListener は何故かうまく動かない
[SharedPreferences.OnSharedPreferenceChangeListener](https://developer.android.com/guide/topics/ui/settings/use-saved-values?hl=ja#onsharedpreferencechangelistener)を使うと、SharedPreferencesが何らか更新されたタイミングでlistenerを起動できると書かれていますが。

しかし、何故か公式ホームページの
```kotlin
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(...)
```
はうまく、動きませんでした。listenerが全く呼ばれません。

# SharedPreferences.OnSharedPreferenceChangeListener を使う
SharedPreferences.OnSharedPreferenceChangeListener
をimplimentする方式だとうまく行きました。

MainActivityで、SharedPreferences.OnSharedPreferenceChangeListenerをimplimentして、onSharedPreferenceChangedを実装します。

```kotlin:MainActivity.kt
class MainActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferenceChangeListener {

・・・

    override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String?) {
            if (key == "BASE_URL"
                || key == "CONN_TIMEOUT"
                || key == "READ_TIMEOUT") {
                // baseURLまたはタイムアウトの変更
                val baseUrl = preferences.getString("BASE_URL", "") ?: ""
                val connectionTimeout = preferences.getString("CONN_TIMEOUT", "30")
                val readTimeout = preferences.getString("READ_TIMEOUT", "30")
                viewModel.networkRepository = NetworkRepository(baseUrl, connectionTimeout!!.toLong(), readTimeout!!.toLong())
            }
    }

・・・
   override fun onResume() {
        super.onResume()
         PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this)
    }

    override fun onPause() {
        super.onPause()
        PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this)
    }
```
SharedPreferencesのbaseUrlかコネクションタイムアウトか、リードタイムアウトのいずれかが変更されれば、NetworkRepository のインスタンスを作り直して、viewModelに再設定してやります。

AndroidのMVVMモデルだと、
Activity、またはFragment → viewModel → 各種repositoryの順に呼ばれますので、このような形になります。

上記の例はActivityにリスナを登録/解除する例で、同様にFragmentに対しても可能ですが、リスナを登録/解除するライフサイクルのタイミングが異なります。

```kotlin
・・・
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener(this)
    }
・・・
    override fun onDestroy() {
        super.onDestroy()
        PreferenceManager.getDefaultSharedPreferences(requireContext()).unregisterOnSharedPreferenceChangeListener(this)
    }
・・・
```
Fragmentの場合はonCreate()、onDestroy()になります。