質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

Q&A

解決済

2回答

2116閲覧

DBと連携したListViewを更新したい

tokumei000

総合スコア17

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Kotlin

Kotlinは、ジェットブレインズ社のアンドリー・ブレスラフ、ドミトリー・ジェメロフが開発した、 静的型付けのオブジェクト指向プログラミング言語です。

0グッド

0クリップ

投稿2020/02/25 12:15

前提・実現したいこと

DBに保存したアイテム名を一覧で表示させるアプリを作っています。
FABを押した時に現れるダイアログからアイテムを追加できます。

現れたダイアログからアイテムを追加して元の画面が表示された時に一覧を更新できるようにしたいのですが、実装方法がわからないため質問させていただきました。

発生している問題・エラーメッセージ

ListViewに表示する際は以下の手順で行っています。

  1. DBにアクセスし全アイテム名を取得してリストに格納する
  2. アイテム名を格納したリストとリストビューをArrayAdapterで紐付ける

そのため画面を更新するには以下の手順で行えばいいと考えました。

  1. 一旦アイテム名を格納したリストを空にする
  2. DBにアクセスし全アイテム名を取得してリストに格納する
  3. 画面の更新(adapter.notifyDataSetChanged())

これをダイアログを定義しているAddDialog.kt内のPositiveButtonの処理を記述している部分に書こうと思ったのですが、どうやってMainActivity内のリストやアダプタを呼び出せばいいかわかりませんでした。

また、他の方法として、ダイアログが消えて元の画面が表示されたことを検知できればMainActivity内に記述できるかもと思い色々模索してみたのですが、検知する方法がわかりませんでした。

知りたいことをまとめると以下の通りです
・AddDialog.kt内でMainActivity内のリストやアダプタを参照する方法(そもそもできるのかどうかも含めて)
・ダイアログが消えて元の画面が表示されたことを検知する方法(そもそもできるのかどうかも含めて)
・上記2点が無理な場合どのようにして画面を更新すればよいか

よろしくおねがいします。

該当のソースコード

MainActivity.kt

kotlin

1class MainActivity : AppCompatActivity() { 2 3 private val _helper = DatabaseHelper(this@MainActivity) 4 5 override fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 setContentView(R.layout.activity_main) 8 9 val fab: View = findViewById(R.id.fab) 10 fab.setOnClickListener(object: View.OnClickListener{ 11 override fun onClick(v: View?) { 12 val dialog = AddDialog() 13 dialog.show(supportFragmentManager, "AddDialog") 14 } 15 }) 16 17 val lv = findViewById<ListView>(R.id.lv) 18 19 var itemsList : MutableList<String> = mutableListOf() 20 val db = _helper.writableDatabase 21 val sql = "SELECT * FROM items" 22 var cursor = db.rawQuery(sql, null) 23 24 val idxItems_name = cursor.getColumnIndex("items_name") 25 26 while(cursor.moveToNext()){ 27 itemsList.add(cursor.getString(idxItems_name)) 28 } 29 30 val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList) 31 lv.adapter = adapter 32 } 33 34 override fun onDestroy() { 35 _helper.close() 36 super.onDestroy() 37 } 38} 39

AddDialog.kt

kotlin

1class AddDialog: DialogFragment(){ 2 3 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 4 5 val editText = EditText(activity) 6 val _helper = DatabaseHelper(activity as Context) 7 8 val builder = AlertDialog.Builder(activity) 9 builder.setTitle(R.string.add_dialog_title) 10 builder.setView(editText) 11 12 builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{ 13 override fun onClick(dialog: DialogInterface?, which: Int) { 14 when(which){ 15 DialogInterface.BUTTON_POSITIVE->{ 16 val addItem = editText.text.toString() 17 if(addItem.length != 0) { 18 19 val db = _helper.writableDatabase 20 21 val sqlCheck = "SELECT * FROM items WHERE items_name = ?" 22 val parms = arrayOf(addItem) 23 val cursor = db.rawQuery(sqlCheck, parms) 24 25 //存在チェック 26 if(cursor.moveToNext() == true){ 27 Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show() 28 } 29 else{ 30 val sqlInsert = "INSERT INTO items (items_name) VALUES (?)" 31 var stmt = db.compileStatement(sqlInsert) 32 stmt.bindString(1, addItem) 33 stmt.executeInsert() 34 35 Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show() 36 } 37 } 38 else{ 39 Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show() 40 } 41 42 } 43 } 44 } 45 }) 46 47 builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{ 48 override fun onClick(dialog: DialogInterface?, which: Int) { 49 Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show() 50 } 51 }) 52 53 val dialog = builder.create() 54 return dialog 55 } 56}

アプリ画面

起動時
FAB押した時

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

デレゲートパターンを使う場合

Kotlin

1class MainActivity : AppCompatActivity() { 2 3 private val _helper = DatabaseHelper(this@MainActivity) 4 5 override fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 setContentView(R.layout.activity_main) 8 9 val fab: View = findViewById(R.id.fab) 10 fab.setOnClickListener(object: View.OnClickListener{ 11 override fun onClick(v: View?) { 12 val dialog = AddDialog() 13 // 追加があるとリストが再び初期化される 14 dialog.onAdded = this@MainActivity::refresh 15 dialog.show(supportFragmentManager, "AddDialog") 16 } 17 }) 18 19 val lv = findViewById<ListView>(R.id.lv) 20 21 // リスト初期化 22 refresh() 23 } 24 25  // 繰り返し使うので関数として切り出した。 26 private fun refresh() { 27 var itemsList : MutableList<String> = mutableListOf() 28 val db = _helper.writableDatabase 29 val sql = "SELECT * FROM items" 30 var cursor = db.rawQuery(sql, null) 31 32 val idxItems_name = cursor.getColumnIndex("items_name") 33 34 while(cursor.moveToNext()){ 35 itemsList.add(cursor.getString(idxItems_name)) 36 } 37 38 val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList) 39 lv.adapter = adapter 40 } 41 42 override fun onDestroy() { 43 _helper.close() 44 super.onDestroy() 45 } 46}

Kotlin

1class AddDialog: DialogFragment(){ 2 3 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 4 5 // 追加 6 var onAdded: (() -> Unit)? = null 7 8 val editText = EditText(activity) 9 val _helper = DatabaseHelper(activity as Context) 10 11 val builder = AlertDialog.Builder(activity) 12 builder.setTitle(R.string.add_dialog_title) 13 builder.setView(editText) 14 15 builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{ 16 override fun onClick(dialog: DialogInterface?, which: Int) { 17 when(which){ 18 DialogInterface.BUTTON_POSITIVE->{ 19 val addItem = editText.text.toString() 20 if(addItem.length != 0) { 21 22 val db = _helper.writableDatabase 23 24 val sqlCheck = "SELECT * FROM items WHERE items_name = ?" 25 val parms = arrayOf(addItem) 26 val cursor = db.rawQuery(sqlCheck, parms) 27 28 //存在チェック 29 if(cursor.moveToNext() == true){ 30 Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show() 31 } 32 else{ 33 val sqlInsert = "INSERT INTO items (items_name) VALUES (?)" 34 var stmt = db.compileStatement(sqlInsert) 35 stmt.bindString(1, addItem) 36 stmt.executeInsert() 37 38 Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show() 39 40 onAdded?.invoke() 41 } 42 } 43 else{ 44 Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show() 45 } 46 47 } 48 } 49 } 50 }) 51 52 builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{ 53 override fun onClick(dialog: DialogInterface?, which: Int) { 54 Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show() 55 } 56 }) 57 58 val dialog = builder.create() 59 return dialog 60 } 61}

ポイントしては、

  • AddDialogにデリゲート(コールバック)を追加。
    var onAdded: (() -> Unit)? = null
  • 追加したデリゲートと同じ型の関数をMainActivity側で代入する。
    dialog.onAdded = this@MainActivity::refresh
  • Addが発生したとき、デリゲートを実行する。
    onAdded?.invoke()

という感じです。

デレゲートとは

デレゲート(delegate)とは委譲(実行をお願いする)という意味があり、このケースだと「MainActivityが自分の持つrefreshという関数を実行してくれるようAddDialogにお願いする」という意味になります。
AddDialogのonAddedという変数にMainActivity::refreshという関数の参照を保持しており、実際の呼び出しinvokeが行われるのはAddDialog内ですが、実行されるのはMainActivity側の関数なので、MainActivity内のリソースを用いて行われます。

デレゲートを使わないパターン

ちょっと特殊ですがデレゲート使わないパターンも載せておきます。
デレゲートパターンでも実際にやっていることはこれと同じになります。

Kotlin

1class MainActivity : AppCompatActivity() { 2 3 private val _helper = DatabaseHelper(this@MainActivity) 4 5 override fun onCreate(savedInstanceState: Bundle?) { 6 super.onCreate(savedInstanceState) 7 setContentView(R.layout.activity_main) 8 9 val fab: View = findViewById(R.id.fab) 10 fab.setOnClickListener(object: View.OnClickListener{ 11 override fun onClick(v: View?) { 12 // AddDialogを初期化するときMainActivity本体を渡してしまう。 13 val dialog = AddDialog(this@MainActivity) 14 // 追加があるとリストが再び初期化される 15 // デレゲートパターンは使わない 16 // dialog.onAdded = this@MainActivity::refresh 17 dialog.show(supportFragmentManager, "AddDialog") 18 } 19 }) 20 21 val lv = findViewById<ListView>(R.id.lv) 22 23 // リスト初期化 24 refresh() 25 } 26 27  // 繰り返し使うので関数として切り出した。 28 // AddDialog内で呼び出したいのでprivateを外してクラス外側からアクセスできるようにする。 29 fun refresh() { 30 var itemsList : MutableList<String> = mutableListOf() 31 val db = _helper.writableDatabase 32 val sql = "SELECT * FROM items" 33 var cursor = db.rawQuery(sql, null) 34 35 val idxItems_name = cursor.getColumnIndex("items_name") 36 37 while(cursor.moveToNext()){ 38 itemsList.add(cursor.getString(idxItems_name)) 39 } 40 41 val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList) 42 lv.adapter = adapter 43 } 44 45 override fun onDestroy() { 46 _helper.close() 47 super.onDestroy() 48 } 49}

Kotlin

1// AddDialogのプロパティとしてMainActivityを持たせてしまう。 2class AddDialog(val mainActivity: MainActivity): DialogFragment(){ 3 4 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 5 6 // デレゲートを使わないパターン 7 // var onAdded: (() -> Unit)? = null 8 9 val editText = EditText(activity) 10 val _helper = DatabaseHelper(activity as Context) 11 12 val builder = AlertDialog.Builder(activity) 13 builder.setTitle(R.string.add_dialog_title) 14 builder.setView(editText) 15 16 builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{ 17 override fun onClick(dialog: DialogInterface?, which: Int) { 18 when(which){ 19 DialogInterface.BUTTON_POSITIVE->{ 20 val addItem = editText.text.toString() 21 if(addItem.length != 0) { 22 23 val db = _helper.writableDatabase 24 25 val sqlCheck = "SELECT * FROM items WHERE items_name = ?" 26 val parms = arrayOf(addItem) 27 val cursor = db.rawQuery(sqlCheck, parms) 28 29 //存在チェック 30 if(cursor.moveToNext() == true){ 31 Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show() 32 } 33 else{ 34 val sqlInsert = "INSERT INTO items (items_name) VALUES (?)" 35 var stmt = db.compileStatement(sqlInsert) 36 stmt.bindString(1, addItem) 37 stmt.executeInsert() 38 39 Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show() 40 41 // ここで直接mainActivityに所属するrefreshを実行する。 42 mainActivity.refresh() 43 } 44 } 45 else{ 46 Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show() 47 } 48 49 } 50 } 51 } 52 }) 53 54 builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{ 55 override fun onClick(dialog: DialogInterface?, which: Int) { 56 Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show() 57 } 58 }) 59 60 val dialog = builder.create() 61 return dialog 62 } 63}

かなり乱暴なやり方ですが、これでも動きます。

このやり方の悪い点

AddDialogを他のアプリで使いまわしたいと思ったとき、このMainActivity以外とは使えないことになってしまいます。
そのために古来よりインターフェースという素晴らしいパターンがあるのですが(OnClickListenerなどがそれ。論じると長くなるのでやめておきます)、最近の言語では単一の処理だけを委譲(お願い)したい場合は、圧倒的にデレゲートパターンを使います。

投稿2020/02/25 12:38

編集2020/02/26 23:50
quadii.shii

総合スコア257

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

quadii.shii

2020/02/25 12:39

ちなみにこのやり方だとダイアログは閉じずにAddが発生したとき、後ろ側に表示されているMainActivityのリストビューが更新されるという挙動になると思います。(動作検証はしていません。)
quadii.shii

2020/02/25 12:41

あ、refresh()関数内のlvがつながらないですね。そこは修正してくださいませ。
quadii.shii

2020/02/25 12:45

というか`val lv = findViewById<ListView>(R.id.lv)`は必要ないと思われます。 AndroidStudioを使っていれば直接レイアウトXML内のlvにアクセスできるようにインポートを自動追加してくれるはずです。
tokumei000

2020/02/27 11:28 編集

参考にして実現したい動作を実装させることができました、ありがとうございます。 デリゲート(コールバック)と呼ばれるものをよく知らなかったので勉強になりました。 1つ疑問があるのですが、AddDialogがonAdded?.invoke()を実行した時はMainActivit上でrefresh()が実行されるのか、それともAddDialogがrefresh()を参照して実行してるのでしょうか?(どっちでも無かったらすいません) 後者な気がするのですが、その場合AddDialogはrefresh()内のlvが何かわからない気がするのですが(lvはrefresh()の外で定義or上記で仰られたようにインポートすることになると思うので)どうなんでしょうか?
quadii.shii

2020/02/26 23:52

正解は後者の方になると思います。 ご質問の答えになるかどうかですが、デレゲートパターンの説明を追記いたしました。
tokumei000

2020/02/27 11:28

デレゲートについての追記、デレゲートを使わない実装方法読ませていただきました。 デレゲートに関する理解が深まりました、ありがとうございます。
joseryo

2020/12/23 14:43

結局、notifydatasetchanged()は使わないのでしょうか。私も同様のことで網羅的に調べたら、notifydatasetchanged()がたくさん出てきて、でも結局どのような時に使えるのかわかりません。今回の場合をnotifyで解決することはできるのでしょうか
guest

0

kotlin でどう書くのか分かりませんのでコードは示せませんが, データベースに合わせて表示するための CursorAdapter というのがあります.
その辺りを検索されては如何でしょうか.

投稿2020/02/25 12:20

jimbe

総合スコア12632

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問