読者です 読者をやめる 読者になる 読者になる

そーす

福岡在住のプログラマ。SwiftとかKotlinとかJavascriptとかSketchとか触ってます。

KotlinのInterfaceでSwiftのProtocolExtensionぽいことをやる

f:id:saburesan:20160906091652j:plain

kotlinのInterfaceではJavaのinterfaceの機能に加え、フィールドの定義とデフォルト実装ができるようになっています。

この機能を使ってSwiftのProtocolExtensionっぽいことをやってみました。

実装内容ですが、例えば画像をギャラリーやカメラアプリをIntentで立ち上げて取得したい場合の処理などを複数のActivityに持たせたい場合を考えます。

Javaであればそれらの機能を持ったActivityのサブクラスを作り継承させるか、それらの機能をもったUtilクラスを作るなどが考えられます。

この方法はもろもろの理由からあまりイケてないと思います。

これをKotlinのinterfaceで実装してみます。

interface MediaImportable {
    var imageUri: Uri?
    val REQUEST_CAMERA_CAPTURE: Int get() = 1000
    val REQUEST_IMPORT_GALLERY: Int get() = 1001

    fun startCameraCaptureActivity() {
        this as? AppCompatActivity ?: return
        val photoName = System.currentTimeMillis().toString() + ".jpg"
        val contentValues = ContentValues()
        contentValues.put(MediaStore.Images.Media.TITLE, photoName)
        contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
        startActivityForResult(intent, REQUEST_CAMERA_CAPTURE)
    }

    fun startGalleryImportActivity() {
        this as? AppCompatActivity ?: return
        val intent = if (Build.VERSION.SDK_INT < 19) {
            val intent = Intent(Intent.ACTION_GET_CONTENT)
            intent.setType("image/*")
        } else {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
            intent.addCategory(Intent.CATEGORY_OPENABLE)
            intent.setType("image/jpeg")
        }
        startActivityForResult(intent, REQUEST_IMPORT_GALLERY)
    }

    fun scanResultImage(activityResultData: Intent?): Uri? {
        this as? AppCompatActivity ?: return null
        return (activityResultData?.data ?: imageUri)?.let {
            MediaScannerConnection.scanFile(this, arrayOf<String>(it.path), arrayOf("image/jpeg")) { s, uri -> }
            imageUri = null
            it
        }
    }

    fun isMediaImportResult(requestCode: Int): Boolean {
        return requestCode == REQUEST_CAMERA_CAPTURE || requestCode == REQUEST_IMPORT_GALLERY
    }
}

インターフェース名はSwiftの~ableに沿って付けました。

imageUri: Uri?

uri保持用のフィールドです。フィールド定義では宣言のみか、getterで常に同じ値を返すプロパティの設定しか出来ません。

REQUEST_CAMERA_CAPTURE REQUEST_IMPORT_GALLERY

リクエストコードのフィールドです。これは定数でよいのでgetterプロパティの宣言をしています。 もちろん実装先で値をセットしても良いのですが、こうすることでInterfaceの実装先で宣言の必要がなくなります。

startCameraCaptureActivity() , startGalleryImportActivity

メソッドを定義してデフォルト実装を追加しています。 また、 this as? AppCompatActivity ?: return thisをAppCompatActivityにスマートキャストしています。 こうすることで以降の実装はインターフェースがAppCompatActivityに実装されている前提で書くことができるので便利です。

使ってみる

class MainActivity: AppCompatActivity(), MediaImportable {
 override var imageUri: Uri? = null
 lateinit var imageView: ImageView

 override fun onClickedCameraButton() {
        startCameraCaptureActivity()
 }

 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode != Activity.RESULT_OK) return

        if (isMediaImportResult(requestCode = requestCode)) {
            scanResultImage(activityResultData = data)?.let { imageView.setImageUri(it) }
        }
  }
}

Kotlinだとデフォルト実装を1つしか持てないですが、SwiftだとProtocolExtensionをwhereで無限に実装を分けれれるのでSwiftの方が便利デスね。