スポンサーリンク

Objectに追加されたextention propertyをreflectionで取得したい

スポンサーリンク

何を言っているか分からないかもしれませんが、やりたかったことはMaterial Designとして提供されているSync, Accessible等のImageVectorを名前で指定してインスタンスを取得したかったのです。(Material Designの指定を外部設定ファイルに委譲したかったので)

Icons.Filled.Syncを取得したい場合を考えます。以下はIconsのStructureです。

Objectのインスタンスのreflectionによる取得方法

まず色んなページで記載されていることですが、KotlinにおいてObject宣言されている、即ちSingletonなインスタンスを取得するためにはKClass#objectInstanceを使用します。

例えばIconsのインスタンスを名前から取得したい場合には以下のように記載します。

val icons = Class.forName("androidx.compose.material.icons.Icons").kotlin.objectInstance

createInstance()はsingleton objectではprivate化されているので利用できません。

Icons.Filledのreflectionによる取得方法

それではObject内のObjectであるFilledはどうやって取得するの?と言われると、FilledはIconsのinnerClass(nestedClass)という扱いですので、以下のようにして取得できます。

val filled = Icons::class.nestedClasses.find { it.simpleName == "Filled"}?.objectInstance

Icons.Filled.Syncの reflectionによる取得方法

まずSyncはIcons.Filledのextention propertyという形で定義されています

public val Icons.Filled.Sync: ImageVector

したがって、まずはextention propertyのreflection方法を学習する必要があります。extention propertyがkotlinでどう扱われているかについてはこのStackOverFlowの回答が非常に分かりやすかったです。

即ち、extention property(function)は本来の目的Classに追加されるのではなく、それを定義したクラスであたかもそう利用できるように目的classをreceiverとしたstatic methodを作成して、内部的にそれを利用しているにすぎません。

今回の場合、extension propertyであるSyncはSync.ktというファイルで定義されていますので、SyncKt.classというclassファイル内にstatic methodで定義されます。

SyncKt.class

package androidx.compose.material.icons.filled

private var _sync: androidx.compose.ui.graphics.vector.ImageVector? /* compiled code */
public val androidx.compose.material.icons.Icons.Filled.Sync: androidx.compose.ui.graphics.vector.ImageVector /* compiled code */

javaにdecompileすると意味が分かります
SyncKt.decompiled.java

public final class SyncKt {
   @Nullable
   private static ImageVector _sync;

   @NotNull
   public static final ImageVector getSync(@NotNull Filled $this$Sync) {/*****/}

したがって、Reflectionでextention propertyを取得するためにはこのgetSyncを利用すればよいということがわかります。最終的に以下のようにして取得可能です。

val filled = Icons::class.nestedClasses.find { it.simpleName == "Filled"}?.objectInstance
val syncClz = Class.forName("androidx.compose.material.icons.filled.SyncKt")
val getSync =  syncClz.getMethod("getSync", Icons.Filled.javaClass)
val sync = getSync.invoke(sync, filled).toString()

Appendix

関係ありそうな関数を適当に出力したものです。備忘録的に書いておくだけですので、あまり関係はないです。

入力

Icons::class.memberProperties.forEach {
    Log.d("memberProperties", it.toString())
}
Icons::class.members.forEach{
    Log.d("members", it.toString())
}
Icons::class.sealedSubclasses.forEach{
    Log.d("sealedSubclasses", it.toString())
}
Icons::class.nestedClasses.forEach{
    Log.d("nestedClasses", it.toString())
}
val filled = Icons::class.nestedClasses.find { it.simpleName == "Filled"}?.objectInstance
Log.d("Icons.Filled", filled.toString())

val sync = Class.forName("androidx.compose.material.icons.filled.SyncKt")
val getSync = sync.getMethod("getSync", Icons.Filled.javaClass)
Log.d("Icons.Filled.Sync", getSync.invoke(sync, filled).toString())

出力

DEBUG: memberProperties: val androidx.compose.material.icons.Icons.Default: androidx.compose.material.icons.Icons.Filled
 DEBUG: members: val androidx.compose.material.icons.Icons.Default: androidx.compose.material.icons.Icons.Filled
 DEBUG: members: fun androidx.compose.material.icons.Icons.equals(kotlin.Any?): kotlin.Boolean
 DEBUG: members: fun androidx.compose.material.icons.Icons.hashCode(): kotlin.Int
 DEBUG: members: fun androidx.compose.material.icons.Icons.toString(): kotlin.String
 DEBUG: nestedClasses: class androidx.compose.material.icons.Icons$Filled
 DEBUG: nestedClasses: class androidx.compose.material.icons.Icons$Outlined
 DEBUG: nestedClasses: class androidx.compose.material.icons.Icons$Rounded
 DEBUG: nestedClasses: class androidx.compose.material.icons.Icons$Sharp
 DEBUG: nestedClasses: class androidx.compose.material.icons.Icons$TwoTone
 DEBUG: Icons.Filled: androidx.compose.material.icons.Icons$Filled@6702274f
 DEBUG: Icons.Filled.Sync: androidx.compose.ui.graphics.vector.ImageVector@83992eb3
タイトルとURLをコピーしました