kotlinを使うことの危険性。syntetic for block in layout

Photo by Justin Chrn on Unsplash

Android向けの開発でKotlinが登場し、xml-layoutからクラスへのビューバインドに役立つツール登場しました。 これがkotlinx.android.synthetic.Syntheticです。 もうfindViewById()というメソッドを使う必要はありません。 xmlで指定されたidの値を書き込むだけで、クラスでviewを使うことができるかもしれません。 しかし、レイアウトで<include>というタグを使うことには、明らかでない問題がありそうです。

テスト用アプリは、2 つのアイテムからなるリストで、それぞれにテキスト付きのボタンが含まれています。 最初に、アイテム用のレイアウト (item1.xml と item2.xml) を作成します。

ここで、アイテム内のボタンが同じID「button」を持っていることに注目しましょう。 これは重要なことです。

次に、これらのアイテムを <include> タグを使って main_layout に追加します:

次に、これをレイアウトとしてクラスで使用します:

アプリケーションを実行すると、次の結果が得られます。

Identification of problem

ここで、小さな機能を作って、コードでボタンのテキストを変えてみましょう。 最初のボタンには「Changed Text First Item」、2 番目のボタンには「Changed Text Second Item」という新しいテキストが表示されます。 ボタンへのバインディングは、kotlinx.synthetic をエイリアスとして使用します:

import kotlinx.android.synthetic.main.item1.button as button1
import kotlinx.android.synthetic.main.item2.button as button2

これは、ボタン1が item1.xml から、ボタン2が item2.xml からのビューであることは明らかです。 次に OnCreate のテキストを変更します。

アプリを実行して結果を確認します。 予想と違う。

問題の説明

この結果の理由は何なのか、考えてみましょう。 では、ボンネットの中でどのように合成が行われているのか見てみましょう。 これは、Kotlin クラスを Java にデコンパイルします(Show Kotlin Bytecode -> Decompile for AndroidStudio)。 これで、MainActivityは次のようになります:

これで、合成がどのように行われるか理解できます。 Activity は、ビューの ID をキーとし、ビューオブジェクトを値とする HashMap を持っています。 これは、遅延初期化によって満たされます。 この例では、button1 の最初の呼び出しは、id = R.id.button というキーで HashMap の値を探します。 そのような値はまだないので、このメソッドは id をキーとして、item1 のボタン オブジェクトを値として、マップにペアを追加しています。 その結果、最初のボタンのテキストが正常に適用されます。

同じく id = R.id.button を持つ button2 への呼び出しは、このキーのマップ値をチェックし、最初のビュー(最初のアイテムからのボタン)を見つけ、2番目のボタンの値で埋めます。 その結果、最初のボタンは2回テキストを変更し、2番目のボタンは1回も変更しない

問題の解決

最初のように、各ビューで異なるidを使用することができます。 しかし、それが有用でない場合は、findViewById に戻ることができます:

Let’s run the app and see to result:

Now it work correctly!

まとめ

言語の新機能は多くの便利なものを与えてくれますが、問題を避けるためにどのように動作するかを理解する必要があります。 お役に立てれば幸いです。

コメントする