LoginSignup
4
7

More than 3 years have passed since last update.

KotlinでFragmentからActivityへのコールバックでのスマートキャストについて

Last updated at Posted at 2018-06-16

はじめに

既存のAndroidアプリをJavaからKotlinへの移行中に、FragmentにListener(interface)を実装し、UI操作がされたタイミングでイベントを呼ぶコードを書く際にハマったのでその時のメモ。

環境

  • AndroidStudio: 3.0.1
  • Kotlin: 1.2.10

しようとしていたこと

Fragment側でActivityから呼ぶ Listenerを定義し、onAttachメソッドが呼ばれた時に引数のcontextがinterfaceを実装していたらcontextをListenerにキャストし、ボタンを押されたらActivityのイベントメソッドを呼ぶ以下のJavaコードをKotlinで書き直そうとした。

MyFragment.java
public class MyFragment extends Fragment {

    public interface FragmentListener {
            void onClickButton();
    }

    private FragmentListener mListener;

    @Override
    public void onAttach(Context context){
            //...

            if (context instanceof FragmentListener){
                    mListener = (FragmentListener) context;
            }
            // 上記を簡易に書くなら
       // mListener = context as? FragmentListener
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState){
            //...

            view.findViewById(R.id.Button).setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v){
                      if (mListener != null){
                          mListener.onClickButton();
                      }
                  }
            });
    }

    //...
}
MyFragment.kt

class MyFragment : Fragment() {

    interface FragmentListener {
        fun onClickButton()
    }

    private var mListener: FragmentListener? = null

    override fun onAttach(context: Context){
        //...

        if (context is FragmentListener){
            mListener = context
        }
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        //...

        view!!.findViewById<Button>(R.id.Button).setOnClickListener {
            if (mListener != null){
                mListener.onClickButton()
            }
        }
    }

    //...
}

とすると、mListener.onClickButton()を呼んだところで

Smart cast to 'MyFragment.FragmentListener’ is impossible, because 'mListener’ is a mutable property that could have been changed by this time

とエラーが出る。

これは、mListenervarで宣言したために別スレッドからの書き換えが発生する可能性があるためFragmentListenerへのスマートキャストができないという内容のエラー。

しかしvalで宣言してしまうとcontextが代入できない。

解決策

そのため、今回はスマートキャストをせず、スコープ関数letを使うことで解決した。

変更前
if (mListener != null){
    mListener.onClickButton()
}
変更後
mListener?.let { it.onClickButton() }

mListenernullなら?.呼び出しでlet{}を実行せず、nullでないならlet{}内の処理が実行される。

また、この場合以下のような記述もできる


mListener?.let(FragmentListener::onClickButton)
// より簡易に書くなら
mListener?.onClickButton()

参考資料

Kotlin Reference: Type Checks and Casts: 'is' and 'as'

Kotlin スコープ関数 用途まとめ

【Android】アプリエンジニアのためのKotlin入門(基本文法編)

アクティビティへのイベント コールバックを作成する - Android デベロッパー

4
7
6

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
4
7