bookmark_border【SharedPreference】get/putStringSet()の罠挙動【Kotlin】

困ったこと

putStringSet()直後にアプリを再起動すると、getStringSet()の結果が空要素になる。(ならないときもある)

結論

getStringSet()で受け取ったSet<String>に対して要素の操作をしない。

まずいコード

getStringSet()で受け取ったSet<String>に要素を追加して、それをそのままputStringSet()する。

val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf())
set.add("fugafuga")
sharedPreferences.edit().putStringSet("hogehoge", set).commit()
// setの要素数が0
val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf())

改善

getStringSet()で受け取ったSet<String>を直接触らず、toMutableSet()で別Setを作ってそれを操作&putStringSet()する。

val set = sharedPreferences.getStringSet("hogehoge", mutableSetOf()).toMutableSet()
set.add("fugafuga")
sharedPreferences.edit().putStringSet("hogehoge", set).commit()

公式に書いてあった

getStringSet() で取得したSetの要素を操作しないようにとのこと。データの整合性(一貫性)はそもそも保証されない。

Note that you must not modify the set instance returned by this call. The consistency of the stored data is not guaranteed if you do, nor is your ability to modify the instance at all.

SharedPreferences | Android Developer

bookmark_border【okhttp3+Kotlin】Rejecting re-init on previously-failed class java.lang.Class

久々にKotlinでokhttp3を使ったらいきなり落ちたのでメモ。

Rejecting re-init on previously-failed class java.lang.Class: java.lang.NoClassDefFoundError: Failed resolution of: Lorg/conscrypt/ConscryptHostnameVerifier;
val client = OkHttpClient() // ここで落ちる
val request = Request.Builder()
    .url("https://...")
    .build()
client.newCall(request).enqueue(object : okhttp3.Callback {
    @Throws(IOException::class)
    override fun onResponse(call: Call, response: Response) {
        // .....
    }
    override fun onFailure(call: Call, arg1: IOException) {
        // ....
    }
})

対処

okhttp自体のバージョンを下げることで一旦対応。

dependencies {
    // ....
    // implementation 'com.squareup.okhttp3:okhttp:4.4.0'
    implementation 'com.squareup.okhttp3:okhttp:4.0.0'
}

org.conscrypt:conscrypt-android をimplementationするようにという文献もありましたが、本来conscryptはokhttp3にとってoptionでありokhttp側のバグと考えられるので一旦この方法で様子を見ることに。

本家GitHubのIssue

OkHttp 4.3.1 spams log, didn’t find class “org.conscrypt.ConscryptHostnameVerifier” on path #5760

2020/05/28 更新:BugのLabelがついている。まだOpen。

2020/10/14 更新:4.8で解決しそうということでcloseされている。

bookmark_border【Kotlin】Camera2 プレビューのSurfaceViewタップでフォーカスを固定する

プレビューしているSurfaceViewがタップされた座標に応じてフォーカス固定したかったけども、まとまった情報がなかったので個人的なメモ。
例外のハンドリングやCameraCaptureSessionのnullチェックなどは割愛しているので注意。

// Activityの中
private var cameraDevice: CameraDevice? = null
private var cameraSession: CameraCaptureSession? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
// Activityの中
private var cameraDevice: CameraDevice? = null
private var cameraSession: CameraCaptureSession? = null
private var previewRequestBuilder: CaptureRequest.Builder? = null
// 前提:
// ActivityCompat.requestPermissionsで、カメラ使用の許可をユーザから得ている
// カメラがすでにオープンしている。
//   onResume()とかで、上記のpreviewRequestBuilder, cameraDevice, cameraSessionにすでに参照を入れている。
//   CameraCaptureSession.StateCallback#onConfiguredまで行っていて、プレビューが始まっている。
// surfaceViewはプレビュー用のView
surfaceView.setOnTouchListener { _: View, motionEvent: MotionEvent ->
    if (motionEvent.action == MotionEvent.ACTION_DOWN) {
        // タップされた場所から20pxの範囲に対してフォーカスさせる
        // 画面のorientationに応じて別途座標の反転などが必要
        val rectangle = MeteringRectangle(motionEvent.x.toInt(), motionEvent.y.toInt(),
            20, 20, MeteringRectangle.METERING_WEIGHT_MAX)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO)
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(rectangle))
        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
        // 露出の調整やフラッシュが必要なときは適宜previewRequestBuilder#setする
        // builder.set(CaptureRequest.CONTROL_AE_REGIONS, arrayOf(rectangle))
        // builder.set(CaptureRequest.CONTROL_AE_XXXXXXX, XXXXXXX)
        cameraSession!!.setRepeatingRequest(previewRequestBuilder.build(), null, null)
        // CallbackやHandlerを指定する場合
        /*
        cameraSession!!.setRepeatingRequest(previewRequestBuilder.build(), object :     CameraCaptureSession.CaptureCallback() {
            // コールバックが必要ならここに処理を記述
        }, handler)
        */
    }
    true
}

bookmark_border【Kotlin】GnssStatusの取得【Android】

API Level 24 (Android 7.0)で追加された、GnssStatusの取得方法をメモ。
manifest

android.permission.ACCESS_FINE_LOCATION

MainActivity

override fun onResume() {
    super.onResume()
        // LocationManagerの取得
        locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
        // ユーザが位置情報のアクセスを許可しているか
        if (ContextCompat.checkSelfPermission(this@MainActivity,
                      android.Manifest.permission.ACCESS_FINE_LOCATION)
                == PackageManager.PERMISSION_GRANTED) {
           //GnssStatusCallbackの登録
           locationManager.registerGnssStatusCallback(gnssCallback)
        } else {
            // 位置情報の取得が許可されていない
            // TODO: requestPermissionsでユーザの許可をもらう
        }
}
private val gnssCallback: GnssStatus.Callback = object:GnssStatus.Callback(){
    override fun onSatelliteStatusChanged(status: GnssStatus?) {
        status?.let {
            val satelliteCount = it.satelliteCount
            // .....
        }
    }
}
override fun onPause() {
    super.onPause()
    gnssCallback.let {
        locationManager.unregisterGnssStatusCallback(it)
    }
}