LoginSignup
7
4

More than 5 years have passed since last update.

Kotlinにおける実装のあるinterfaceの仕組み

Posted at

ことの発端

以前、Android6(Marshmallow)のランタイムパーミッションを処理しやすくするために、ヘルパーを作ってみたときに、Kotlinだったら、もっと簡単に書けるんじゃないかと思って、こんなコードを書いてみました。

PermissionHelper.kt

interface PermissionHelper: ActivityCompat.OnRequestPermissionsResultCallback {

    fun execute(activity: Activity) {
        if (ContextCompat.checkSelfPermission(activity, PERMISSION) != PackageManager.PERMISSION_GRANTED) {
            // 以前に許諾して、今後表示しないとしていた場合は、ここにはこない
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, PERMISSION) && message != null) {
                // ユーザに許諾してもらうために、なんで必要なのかを説明する
                val builder = AlertDialog.Builder(activity)
                builder.setMessage(message)
                builder.setPositiveButton(if (caption == null) "OK" else caption) { dialog, which ->
                    //  許諾要求
                    requestPermission(activity)
                }
                builder.show()
            } else {
                //  許諾要求
                requestPermission(activity)
            }
        } else {
            // 許諾されているので、やりたいことをやる
            onAllowed()
        }
    }

    private fun requestPermission(activity: Activity) {
        val permissions = arrayOf(PERMISSION)
        ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE)
    }

    val message: String?
    val caption: String?
    val REQUEST_CODE: Int
    val PERMISSION: String

    fun onAllowed()
    fun onDenied() {}

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        if (requestCode == REQUEST_CODE) {
            if (permissions.size > 0 && permissions[0] == PERMISSION && grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 許諾されたので、やりたいことをやる
                onAllowed()
            } else {
                onDenied()
            }
        }
    }
}

MainActivity.kt
class MainActivity : AppCompatActivity(), PermissionHelper {

    override val message: String? get() = null
    override val caption: String? get() = null
    override val REQUEST_CODE: Int get() = 123
    override val PERMISSION: String get() = Manifest.permission.WRITE_EXTERNAL_STORAGE

    val takePhoto1: Button by bindView<Button>(R.id.take_photo_1)

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

        takePhoto1.setOnClickListener {
            execute(this)
        }
    }

    override fun onAllowed() {
        throw UnsupportedOperationException()
    }
}

ところが、onRequestPermissionsResult()がどーたらこーたらでエラーになってしまって、PermissionHelperで実装してるはずなのにー、それって何処行っちゃったの?
という疑問がわいたので、もっと単純な構成で、確認してみました。

コンパイルしてデコンパイル

Foo.kt
interface Foo {

    fun foo(): String
}
Bar.kt
interface Bar: Foo {

    fun bar(): String;

    override fun foo(): String {
        return bar();
    }
}
Buzz.kt
class Buzz: Bar {

    override fun bar(): String {
        return "Buzz"
    }
}

を用意して、コンパイルした後、できあがったclassファイルをデコンパイルしてみます。

Foo.java
public interface Foo {

    public abstract String foo();
}
Bar.java
public interface Bar extends Foo {

    public static final class DefaultImpls {

        public static String foo(Bar $this) {
            return $this.bar();
        }
    }

    public abstract String bar();

    public abstract String foo();
}
Buzz.java
public final class Buzz implements Bar {

    public String bar() {
        return "Buzz";
    }

    public Buzz() {
    }

    public String foo() {
        return Bar.DefaultImpls.foo(this);
    }
}

Barで実装したはずの、foo()は内部クラスのDefaultImplsにちょっと形を変えて潜り込ませて、foo()自体は、abstractのまま残しています。そして、Buzzでfoo()を実装して、その中で、潜り込ませたfoo()を呼び出しています。

第1引数でthisを渡すあたり、pythonを彷彿とさせますが、まぁ、C++なんかでも使われているオブジェクトのメソッドを(メソッドではないという意味の)関数で実現するオーソドックスな方法です。ちなみに、このパターンは、拡張関数でも使用されています。

まとめ

  • interfaceでの実装は、内部クラスのstaticなメソッドとして隠蔽される
  • 継承元の仮想メソッドは仮想のまま引き継がれる
  • interfaceを実装するクラスで、仮想メソッドが実装され、その際に、interfaceの内部クラスのメソッドが呼び出される

ということですね。

蛇足

Kotlinの仕組みについて理解が深まったのはいいのですが、発端になった問題は解決していません。

MainActivityに、PermissionHelperの内部クラスを使った形で、onRequestPermissionsResult()メソッドができないのは、おかしいんじゃないの?
と、頭を抱えていたのですが、エラーメッセージをよく見てみると、

because it inherits many implementations of it

と言う文字列が。

onRequestPermissionsResult()は、Fragmentからの呼び出しランタイムパーミッションの処理を呼び出したときのために、FragmentActivityで、既に実装されていたのでした(サポートライブラリの闇は深いね)。

なので、

MainActivity.kt
class MainActivity : AppCompatActivity(), PermissionHelper {

    :

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super<PermissionHelper>.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
}

を追加してやれば、コンパイル可能になります。
だけど、これじゃ当初の目的(ボイラープレートなコードをなくす)は果たしてないんだよね。 orz

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4