これまでJavaでAndroidアプリを作ったことは何度かあったのですが、そろそろKotlinに手を出そうか、とか安易に考えてチャレンジしてみた結果オオハマリしました。
解決してしまえば何のことない。そもそも自分がKotlinの文法すら知らずに挑戦したのが問題だったというところではあるのですが、初心者の方がKotlinでFragmentを使う場合、テンプレートを活用するのにいろいろ迷うのではないかなと思った(のと、自分が後々、忘れて同じ過ちを犯しそうなので…)ので、自分のメモがてら残しておきます。
環境
Windows 10
Android Studio 3.3.2
Kotlin
著者技量
JavaではいくつかAndroidアプリを個人でリリースした経験あり。業務でも本職ではないもののカスタマイズなどでの対応経験はあり。
ただし、Android2~4の時期に対応していたのがメーンで、そもそもFragmentの取り扱いに不慣れだったことも今回オオハマリした一因だったかなと思ってます。
Kotlinについては文法も全く知らず、とにかくやりながら覚えていこう、くらいのつもりで初チャレンジして構築中。
やっていたこと
- AndroidStudioのnew Projectを作成で、Bottom Navigation Activityを作成
- 出来上がったプロジェクトで File > New > Fragment > Fragment(Balk) を複数追加して、メニューの数分だけ、XMLファイルで画面を用意。
- Bottom Navigationのボタンをタップして、画面を切り替えるところまではいくつかのページを参考にすんなり実装。
-
Fragmentに配置したButtonに対して、setOnClickListenerでイベントを追加しようとするも、対象ボタンの情報が取得できず、延々Exceptionを吐き続ける。
<イマココ
という状態で、2晩も詰まってしまいました。
参考サイト
先に自分が大いに勉強になった参考サイトを載せておきます。
はまっているポイントは違っても、Fragmentのテンプレートを使うつもりなら大いに参考になるかと。
-
kotlinでAndroidStudioが自動的に生成するFragmentのテンプレートを調べてみる(androidアプリ開発 kotlin部)
テンプレートファイルの、詳細な解説が記述されています。 テンプレートファイルにそもそも何が書かれているのかよくわからん。という人はここから見るとよいかと。私はここを読んだのが結構後になってからでしたが、これで解決の糸口がつかめました。 -
クラスと継承(Kotlinリファレンス)
Kotlinの文法解説ページ。 文法くらい事前にある程度知ってから挑戦しようぜ、といわれるかもしれませんが。結局これが原因だったようなものでした。基本って大切。
はまった経緯
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内で各関数をコールするようにして、処理の切り分けができました。
既に慣れている方やご存知の方にとってはなんてことないポイントですが、文法も知らないところからテンプレートを活用しようとしたときに、インターフェース実装が必要というポイントに気づけないことで大きな足踏みとなった最大の要因でした。
正直、これから先の実装に向けて、ボタンのイベントの書き方がなどが適切なのかどうかはまだよくわかっていませんが、ひとまず、これでイベントの切り分けはできる状態となりましたので、どなたか私と同じようなことではまってしまう初学者の助けにでもなればと思います。