はじめに
前回の記事から時を超えました。
あまりにもきりが悪いのがずっと気にかかっていたので、舞い戻りました。
前回のあらすじ
前回までの記事
#1 Kotlin で TODO アプリを開発・リリースするまでの記録
#2 Kotlin で TODO アプリを開発・リリースするまでの記録
#3 Kotlin で TODO アプリを開発・リリースするまでの記録
#4 Kotlin で TODO アプリを開発・リリースするまでの記録
#5 Kotlin で TODO アプリを開発・リリースするまでの記録
#6 Kotlin で TODO アプリを開発・リリースするまでの記録
#7 Kotlin で TODO アプリを開発・リリースするまでの記録
前回のあらすじ
- リストに削除ボタンを設置した(イメージ)
今回の目標
- RecyclerView のデータ追加・削除
Android Studio のショートカット設定
本題に入る前に、我慢ならなかったのでショートカットを変更しました。
Ctrl + Y(やり直す)したら、行が削除される悲劇を二度と繰り返さないように。
EclipseとAndroid Studioのショートカットの違い for Windows
複数種類があるので、慣れたショートカットを選択できそうです。
著者は Visual Studio にしました。
Android Studio 3.5
「Android Studio 3.5」が正式リリース ~「Chrome OS 75」以降を新たに公式サポート
アップデートしちゃいました。
本記事以降はこちらを適用した前提で進めていきます。
RecyclerView のデータ追加・削除
それでは前回に引き続き、こちらの記事をまねしてリスト表示してみます。
Adapter
データの追加メソッドを記載します。
// リストにデータを追加する
fun addListItem (item: Item) {
itemList.add(item)
notifyDataSetChanged() // これを忘れるとRecyclerViewにItemが反映されない
}
// リストのデータを削除する
private fun removeItem(position: Int) {
itemList.removeAt(position)
notifyItemRemoved(position)
notifyDataSetChanged() // これを忘れるとRecyclerViewにItemが反映されない
}
データ削除のメソッドが private になっているのは
おそらくこのメソッド内で完結できる処理だからでしょう。
データクラス
データクラスを定義します。
Item.kt ファイルを MainActivity,kt と同じ階層に新規作成して記述していきます。
import java.util.*
data class Item(
val mId: String = UUID.randomUUID().toString(),
val mName: String
)
ひとまずは識別子(mId)と TODO のテキスト(mName)を用意しました。
データクラスってなに?
はじめてのKotlin:データクラス~プロパティ、コンストラクタ、ゲッター不要~
kotlin(コトリン)にはデータクラスというものがあります。これは処理は行わないけれどもデータだけをもつクラスです。
これにデータを持たせているようです。
一時的なデータ保管だと思われるので
今後データベースに登録する処理も必要となってくると思いますが
ひとまず登録ができる状態にしたいですね。
Adapter
順番が前後しましたが、Adapter の onCreateViewHolder と onBindViewHolder も
修正していきます。
それぞれのメソッドの意味はこちらの記事をご覧ください。
// ViewHolderを作成
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val layoutInflater = LayoutInflater.from(context)
val mView = layoutInflater.inflate(R.layout.list_item, parent, false)
// リストのクリックイベントを作成
mView.setOnClickListener { view ->
mRecyclerView?.let {
itemClickListener.onItemClick(view, it.getChildAdapterPosition(view))
}
}
return RecyclerViewHolder(mView)
}
修正します、と言いつつも onCreateViewHolder は何も変わっていません。
リストのクリックイベントは前の処理の名残で、無くても問題ないです。
// ViewHolderにデータをセット
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
// itemList.mNameをリスト上のテキストボックスにセット
holder.itemTextView.text = itemList[position].mName
// itemList.itemImageButtonにリムーブメソッドをセット
holder.itemImageButton.setOnClickListener {
removeItem(position)
}
}
こちらは全体的に修正しました。
activity_main.xml
現在は list_item.xml にテキストの表示と、削除ボタンを設置している状態です。
追加ボタンが置けないので、activity_main.xml をサンプルに沿った構成に変更します。
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<android.support.design.widget.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4" >
<EditText
android:id="@+id/et_item"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:hint="Item Name"
android:maxLines="1"
android:inputType="text" />
</android.support.design.widget.TextInputLayout>
<ImageButton
android:id="@+id/bt_add_item"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_add_black_24dp"
android:background="#000f"
android:contentDescription="Add" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainRecyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="9"
android:scrollbars="vertical">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
ほとんどコピペなので、例の如く Warning が表示されました。
Warning の解消
ImageButton
Hardcoded text
要するに android:contentDescription="Add"
に対して
「Add」とベタ書きはやらない方がいいよ、という警告です。
[【Android】Hardcoded string “***”, should use @ string resource.] (http://buravo46.hatenablog.com/entry/2015/02/18/153805)
表示する文字列の設定ファイルがきちんと用意されているらしく、そのファイルに文字を記述して> 画面の設定ファイルなどで使うのが推奨されているみたいです。
上記の通り、文字列の設定ファイルに表示する文字を用意します。
<resources>
<string name="app_name">first</string> // 最初から設定されている
<string name="itemImageButton">AddButton</string> // 新たに追加
</resources>
strings.xml で設定した文字列を取得するように書き換え、Warning が解消されました。
<ImageButton
android:id="@+id/bt_add_item"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_add_black_24dp"
android:background="#000f"
android:contentDescription="@string/itemImageButton" />
EditText
Use Autofill
EditText では次のプロパティを設定しておかないと Warnig が表示されるようなので
hint の内容を含め書き換えを行います。
[Android] スマホにEditTextを使って文字を入力する
android:hint
android:autofillHints
android:inputType
を設定しないとwarningが出ます。
<EditText
android:id="@+id/et_item"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:hint="@string/hint"
android:autofillHints="@string/hint"
android:maxLines="1"
android:inputType="text" />
@string/hint は前項にも出てきた strings.xml に追加します。
<resources>
<string name="app_name">first</string>
<string name="itemImageButton">AddButton</string>
<string name="hint">input</string> // 新たに追加
</resources>
そのほか、LinearLayout を二重に指定しないとエラーが出たので注意してください。
MainActivity.kt
RecyclerView がリスト表示する記述は残し
EditText に入力された値を登録する処理を追加します。
具体的には bt_add_item のクリックイベントがそれに該当します。
class MainActivity : AppCompatActivity(), RecyclerViewHolder.ItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainRecyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
// Initialize Adapter
val adapter = RecyclerAdapter(this, this, ArrayList())
bt_add_item.setOnClickListener {
if ( !TextUtils.isEmpty( et_item.text.toString() ) ) {
val task = Item( mName = et_item.text.toString() )
adapter.addListItem(task)
} else {
Snackbar.make(container, "Task is empty", Snackbar.LENGTH_SHORT).show();
}
et_item.setText("")
}
mainRecyclerView.adapter = adapter
// 区切り線の表示
mainRecyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))
}
override fun onItemClick(view: View, position: Int) {
Toast.makeText(applicationContext, "position $position was tapped", Toast.LENGTH_SHORT).show()
}
}
ビルド実行
エラーが出ました。
うまくいかなくても落ち込まないことが大事。
エラーを分析する
4: Run タブに大量のエラーが出ているので、最初の 3 行を切り出して原因を探っていきます。
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.first, PID: 9873
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.first/com.example.first.MainActivity}: android.view.InflateException: Binary XML file line #22: Binary XML file line #22: Error inflating class android.support.design.widget.TextInputLayout
android.support.design.widget.TextInputLayout
がエラーの原因の様子。
いろいろ試してみましたが改善せず、ひとまずandroid.support.design.widget.TextInputLayout
をコメントアウトしたところ起動成功しました。
TODO アプリでもリストでもなんでもなくて、ちょっとおもしろい。
このタグが悪いのはわかるのですが、対処方法がわからないため
Design タブ > Palette > Text > TextInputLayout を
Compornent Tree の該当位置にドラッグアンドドロップで配置させます。
(もとの TextInputLayout はコメントアウトしているため 1 つだけになっています)
Text タブで配置されたタグを確認します。
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="hint" />
</com.google.android.material.textfield.TextInputLayout>
あっ!
<android.support.design.widget.TextInputLayout
↓
<com.google.android.material.textfield.TextInputLayout
<EditText
↓
<com.google.android.material.textfield.TextInputEditText
コピペはやめようってこと。コピペはやめようってこと!
<com.google.android.material.textfield.TextInputLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/et_item"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="@string/hint"
android:inputType="text"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
自分の環境に対して、適切なコンポーネントを使用することが大事。
ということで、コンポーネント名と属性を追加・修正し直しました。
前述にあった EditText は使用しないため android:autofillHints
は記述していません。
それから android.support.design.widget.TextInputLayout
を使用しないということは
#7 Android Support Design Library で「エラー出てるけど動くからいいか」問題も解消されます。
以下の記述を build.gradle から削除しました。
implementation 'com.android.support:design:28.0.0'
もとから記載している Google Material が代わりを担ってくれます。
よかったよかった。
implementation 'com.google.android.material:material:1.0.0'
エラーの解消
container(LinearLayout)
どのタイミングからエラーが発生していたのか気づかなかったのですが
container(LinearLayout) でエラーが発生しています。
エラー内容はこちら。
Missing Constraints in ContrainLayout
Android StudioでConstraintLayoutが自動補完されずにエラーが出る問題の解決
対象オブジェクトを選択して、上のボタン群の中にある「Infer Constraints」をクリックしましょう。
Design タブで container を選択状態で「Infer Constraints」をクリックします。
すぐにエラーが解消されたので、Text タブで補完内容を確認しました。
<LinearLayout
android:id="@+id/container"
android:layout_width="405dp"
android:layout_height="733dp"
android:orientation="vertical"
android:padding="8dp"
tools:layout_editor_absoluteX="6dp">
↓
<LinearLayout
android:id="@+id/container"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
android:padding="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
全体的なサイズを親サイズに合わせて調整してくれました。
Androidの新しいLayout、ConstraintLayoutことはじめ
ConstraintLayoutは自動的にレイアウトの位置をマテリアルデザインに沿った最適な位置に調整してくれます
マテリアルデザインに沿って自動的にやってくれるようです。
テキスト入力のボックス表示ができました。
リストにデータを表示する
テキストを追加し、リストにデータが反映されるか確認します。
テキスト入力
テキストボックスに入力します。
パソコンのキーボードでもエミュレーターのキーボードでも入力可能です。
追加ボタンでリスト表示(3件)
テキストを入力し「+」の追加ボタンでデータを登録します。
「red」「blue」「yellow」の順に行いましたが、上から順に登録・表示されています。
削除ボタンでデータ削除
リスト中央の「blue」を「-」の削除ボタンで削除しました。
画面上からきちんと消えています。
追加ボタンでさらに追加
テキストを入力し「+」の追加ボタンで「green」を登録します。
正しく「red」「yellow」「green」の順で登録・表示されています。
成功です!
まとめ
長かった。空いた時間のほうが長かった。
しばらく振りに #6 を見返して何がしたかったのかを思い出してみると
どうやら、状態を保存する配列を欲していたようですね。
今回データクラスというものがデータを保存してくれているので
これを活用して、幅を広げることができそうです。
成果
- リストデータの表示ができた
- リストデータの登録ができた
- リストデータの削除ができた
参考記事に感謝
たくさんの記事を引用させて頂きました。ありがとうございます。
著者が一部のみを引用したことにより、引用元の意図しない情報として受けられることがないよう、できれば引用元の記事を読んでいただけますようお願いいたします。
またいずれの記事の感想も、著者個人の意見に基づくものですのでご注意ください。
本記事で参考にさせて頂いたこちらの記事に、改めて特別な感謝を。
次の記事
#9 Kotlin で TODO アプリを開発・リリースするまでの記録