LoginSignup
3
4

More than 5 years have passed since last update.

[Kotlin] AndroidStudioのFragmentに配置したButtonにsetOnClickListenerが設定できなかった解決法

Last updated at Posted at 2019-03-20

これまでJavaでAndroidアプリを作ったことは何度かあったのですが、そろそろKotlinに手を出そうか、とか安易に考えてチャレンジしてみた結果オオハマリしました。
解決してしまえば何のことない。そもそも自分がKotlinの文法すら知らずに挑戦したのが問題だったというところではあるのですが、初心者の方がKotlinでFragmentを使う場合、テンプレートを活用するのにいろいろ迷うのではないかなと思った(のと、自分が後々、忘れて同じ過ちを犯しそうなので…)ので、自分のメモがてら残しておきます。

環境

Windows 10
Android Studio 3.3.2
Kotlin

著者技量

JavaではいくつかAndroidアプリを個人でリリースした経験あり。業務でも本職ではないもののカスタマイズなどでの対応経験はあり。
ただし、Android2~4の時期に対応していたのがメーンで、そもそもFragmentの取り扱いに不慣れだったことも今回オオハマリした一因だったかなと思ってます。
Kotlinについては文法も全く知らず、とにかくやりながら覚えていこう、くらいのつもりで初チャレンジして構築中。

やっていたこと

  1. AndroidStudioのnew Projectを作成で、Bottom Navigation Activityを作成
  2. 出来上がったプロジェクトで File > New > Fragment > Fragment(Balk) を複数追加して、メニューの数分だけ、XMLファイルで画面を用意。
  3. Bottom Navigationのボタンをタップして、画面を切り替えるところまではいくつかのページを参考にすんなり実装。
  4. Fragmentに配置したButtonに対して、setOnClickListenerでイベントを追加しようとするも、対象ボタンの情報が取得できず、延々Exceptionを吐き続ける。<イマココ

という状態で、2晩も詰まってしまいました。

参考サイト

先に自分が大いに勉強になった参考サイトを載せておきます。
はまっているポイントは違っても、Fragmentのテンプレートを使うつもりなら大いに参考になるかと。

はまった経緯

1.テンプレートが追加しただけでは動かない。

Fragmentのテンプレートですが、Blankを追加しただけで、動かないです。えぇ。それはもう画面表示すらできず見事にExceptionでエラーになります。
で、文法がわかってないものなのでその理由がよくわからず。デフォルトで作成される関数の中で、onButtonPressed onAttach onDetachあたりをコメントにしたら、とりあえず画面表示ができたので「やった~。よくわからんが、まぁ、テンプレートだから余計なことしてるんだろう。使わないなら消しとけばいいかな」と、浅く考えてました。

2.ボタンのイベントがセットできない。

で、このあたりのページを参考にしてボタンのイベントを実装しようとしたところ、どうにもうまくいかない。
setOnClickListenerを設定すればいいとのことだったので

        view.findViewById<Button>(R.id.contact_button).setOnClickListener {
            Toast.makeText(context,str,Toast.LENGTH_LONG).show()
        }

こんな感じにしてみたり、ほかのサイトを参考にしたところ

        contact_button.setOnClickListener {
            Toast.makeText(context,str,Toast.LENGTH_LONG).show()
        }

こんな省略形もできるらしいぞ、ということで書き換えたを変えてみましたが状況は好転せず。
Fragmentのライフサイクルを参考に、Fragment.kt内の onAttach onCreate onCreateView onViewCreated など、実装する箇所を色々変えてみてもうまくいかず。
この辺りで、debugしていてどうやらfindViewById(R.id.contact_button) や contact_buttonのところがNullになっていて、そもそもXML内に記述しているButtonの要素がとれていなさそうだぞということには気づいたのですが、これを解決するすべがわからず。

3.そもそもなんでテンプレートの表示で落ちるんだ?

ということでいろいろ試行錯誤する中で、そもそも論のこの疑問に立ち返りまして。
テンプレートで作るものが問答無用で落ちるってそもそもおかしいだろう、何が原因なんだ?というのを調べた結果、

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFragmentInteractionListener) {
            listener = context
        } else {
            throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
        }
    }

ここの判定がelseに入ることで、Exceptionになっていることがわかりました。
で、じゃぁcontextの値は何なのか、とみてみると、MainActivityとなっている。
で、参考サイトなどを見てみるとcontextには親のActivityの情報が入ってくる、とのこと。
でも、ActivityなのにOnFragmentInterationListenerがテンプレートなの??となったところで、あぁ、親Activityに実装させなきゃいけないのか!ということに気づきました。

解決のためにやったこと

1) MainActivityにインターフェースを実装する。


class MainActivity : AppCompatActivity(), OnFragmentInterationListener {
    override fun onFragmentInteraction(uri: Uri) {
        //ここに何らかの処理を書く?TODOと表示されたがよくわからず…
    }
(以下省略)

Kotlinの文法がわからなかったので、この書き方を調べるだけでちょっと苦戦したんですが、こんな感じでMainActivityが継承している親クラスの後ろに , をつけてFragment内に定義されているInterface名を記入。
FragmentのInterface内に定義されているメンバメソッドをoverrideすることで、onAttach内のcontextが、MainActivityクラスのインスタンスでありながら、 context is OnFragmentInteractionListenerの判定がtrueとなり、listenerにcontextが格納されるようになりました。

2) setOnClickListenerはonViewCreated内で設定

多分、いくつかの書き方があるとは思うんですが、最終的にこんな形にしてみました。

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

        view!!.contact_button.setOnClickListener {
            onContactButtonPressed("call Contact BTN!")
        }
        view!!.file_button.setOnClickListener {
            onFileButtonPressed("call File BTN!")
        }
        view!!.sendfax_button.setOnClickListener {
            onSendFaxButtonPressed("call SendFax BTN!")
        }
    }

    fun onContactButtonPressed( str :String ) {
        listener?.onSendFaxFragmentInteraction()
        Toast.makeText(context,str,Toast.LENGTH_LONG).show()
    }

    fun onFileButtonPressed( str :String ) {
        listener?.onSendFaxFragmentInteraction()
        Toast.makeText(context,str,Toast.LENGTH_LONG).show()
    }

    fun onSendFaxButtonPressed( str :String ) {
        listener?.onSendFaxFragmentInteraction()
        Toast.makeText(context,str,Toast.LENGTH_LONG).show()
    }

Fragment内に3つのボタンをセットしているので、それぞれのボタン用のメンバ関数を作成しておき、setOnClickListener内で各関数をコールするようにして、処理の切り分けができました。


既に慣れている方やご存知の方にとってはなんてことないポイントですが、文法も知らないところからテンプレートを活用しようとしたときに、インターフェース実装が必要というポイントに気づけないことで大きな足踏みとなった最大の要因でした。

正直、これから先の実装に向けて、ボタンのイベントの書き方がなどが適切なのかどうかはまだよくわかっていませんが、ひとまず、これでイベントの切り分けはできる状態となりましたので、どなたか私と同じようなことではまってしまう初学者の助けにでもなればと思います。

3
4
4

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