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

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

ただいまの
回答率

88.64%

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

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 221

tokumei000

score 12

前提・実現したいこと

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

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

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

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

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

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

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

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

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

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

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

該当のソースコード

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val _helper = DatabaseHelper(this@MainActivity)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val fab: View = findViewById(R.id.fab)
        fab.setOnClickListener(object: View.OnClickListener{
            override fun onClick(v: View?) {
                val dialog = AddDialog()
                dialog.show(supportFragmentManager, "AddDialog")
            }
        })

        val lv = findViewById<ListView>(R.id.lv)

        var itemsList : MutableList<String> = mutableListOf()
        val db = _helper.writableDatabase
        val sql = "SELECT * FROM items"
        var cursor = db.rawQuery(sql, null)

        val idxItems_name = cursor.getColumnIndex("items_name")

        while(cursor.moveToNext()){
            itemsList.add(cursor.getString(idxItems_name))
        }

        val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList)
        lv.adapter = adapter
    }

    override fun onDestroy() {
        _helper.close()
        super.onDestroy()
    }
}

AddDialog.kt

class AddDialog: DialogFragment(){

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        val editText = EditText(activity)
        val _helper = DatabaseHelper(activity as Context)

        val builder = AlertDialog.Builder(activity)
        builder.setTitle(R.string.add_dialog_title)
        builder.setView(editText)

        builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                when(which){
                    DialogInterface.BUTTON_POSITIVE->{
                        val addItem =  editText.text.toString()
                        if(addItem.length != 0) {

                            val db = _helper.writableDatabase

                            val sqlCheck = "SELECT * FROM items WHERE items_name = ?"
                            val parms = arrayOf(addItem)
                            val cursor = db.rawQuery(sqlCheck, parms)

                            //存在チェック
                            if(cursor.moveToNext() == true){
                                Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show()
                            }
                            else{
                                val sqlInsert = "INSERT INTO items (items_name) VALUES (?)"
                                var stmt = db.compileStatement(sqlInsert)
                                stmt.bindString(1, addItem)
                                stmt.executeInsert()

                                Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show()
                            }
                        }
                        else{
                            Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show()
                        }

                    }
                }
            }
        })

        builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show()
            }
        })

        val dialog = builder.create()
        return dialog
    }
}

アプリ画面

起動時
FAB押した時

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

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

class MainActivity : AppCompatActivity() {

    private val _helper = DatabaseHelper(this@MainActivity)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val fab: View = findViewById(R.id.fab)
        fab.setOnClickListener(object: View.OnClickListener{
            override fun onClick(v: View?) {
                val dialog = AddDialog()
                // 追加があるとリストが再び初期化される
                dialog.onAdded = this@MainActivity::refresh
                dialog.show(supportFragmentManager, "AddDialog")
            }
        })

        val lv = findViewById<ListView>(R.id.lv)

        // リスト初期化
        refresh()
    }

  // 繰り返し使うので関数として切り出した。
    private fun refresh() {
        var itemsList : MutableList<String> = mutableListOf()
        val db = _helper.writableDatabase
        val sql = "SELECT * FROM items"
        var cursor = db.rawQuery(sql, null)

        val idxItems_name = cursor.getColumnIndex("items_name")

        while(cursor.moveToNext()){
            itemsList.add(cursor.getString(idxItems_name))
        }

        val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList)
        lv.adapter = adapter
    }

    override fun onDestroy() {
        _helper.close()
        super.onDestroy()
    }
}
class AddDialog: DialogFragment(){

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        // 追加
        var onAdded: (() -> Unit)? = null

        val editText = EditText(activity)
        val _helper = DatabaseHelper(activity as Context)

        val builder = AlertDialog.Builder(activity)
        builder.setTitle(R.string.add_dialog_title)
        builder.setView(editText)

        builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                when(which){
                    DialogInterface.BUTTON_POSITIVE->{
                        val addItem =  editText.text.toString()
                        if(addItem.length != 0) {

                            val db = _helper.writableDatabase

                            val sqlCheck = "SELECT * FROM items WHERE items_name = ?"
                            val parms = arrayOf(addItem)
                            val cursor = db.rawQuery(sqlCheck, parms)

                            //存在チェック
                            if(cursor.moveToNext() == true){
                                Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show()
                            }
                            else{
                                val sqlInsert = "INSERT INTO items (items_name) VALUES (?)"
                                var stmt = db.compileStatement(sqlInsert)
                                stmt.bindString(1, addItem)
                                stmt.executeInsert()

                                Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show()

                                onAdded?.invoke()
                            }
                        }
                        else{
                            Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show()
                        }

                    }
                }
            }
        })

        builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show()
            }
        })

        val dialog = builder.create()
        return dialog
    }
}

ポイントしては、

  • 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内のリソースを用いて行われます。

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

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

class MainActivity : AppCompatActivity() {

    private val _helper = DatabaseHelper(this@MainActivity)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val fab: View = findViewById(R.id.fab)
        fab.setOnClickListener(object: View.OnClickListener{
            override fun onClick(v: View?) {
                // AddDialogを初期化するときMainActivity本体を渡してしまう。
                val dialog = AddDialog(this@MainActivity)
                // 追加があるとリストが再び初期化される
                // デレゲートパターンは使わない
                // dialog.onAdded = this@MainActivity::refresh
                dialog.show(supportFragmentManager, "AddDialog")
            }
        })

        val lv = findViewById<ListView>(R.id.lv)

        // リスト初期化
        refresh()
    }

  // 繰り返し使うので関数として切り出した。
    // AddDialog内で呼び出したいのでprivateを外してクラス外側からアクセスできるようにする。 
    fun refresh() {
        var itemsList : MutableList<String> = mutableListOf()
        val db = _helper.writableDatabase
        val sql = "SELECT * FROM items"
        var cursor = db.rawQuery(sql, null)

        val idxItems_name = cursor.getColumnIndex("items_name")

        while(cursor.moveToNext()){
            itemsList.add(cursor.getString(idxItems_name))
        }

        val adapter = ArrayAdapter<String>(applicationContext, android.R.layout.simple_list_item_1, itemsList)
        lv.adapter = adapter
    }

    override fun onDestroy() {
        _helper.close()
        super.onDestroy()
    }
}
// AddDialogのプロパティとしてMainActivityを持たせてしまう。
class AddDialog(val mainActivity: MainActivity): DialogFragment(){

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        // デレゲートを使わないパターン
        // var onAdded: (() -> Unit)? = null

        val editText = EditText(activity)
        val _helper = DatabaseHelper(activity as Context)

        val builder = AlertDialog.Builder(activity)
        builder.setTitle(R.string.add_dialog_title)
        builder.setView(editText)

        builder.setPositiveButton("追加", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                when(which){
                    DialogInterface.BUTTON_POSITIVE->{
                        val addItem =  editText.text.toString()
                        if(addItem.length != 0) {

                            val db = _helper.writableDatabase

                            val sqlCheck = "SELECT * FROM items WHERE items_name = ?"
                            val parms = arrayOf(addItem)
                            val cursor = db.rawQuery(sqlCheck, parms)

                            //存在チェック
                            if(cursor.moveToNext() == true){
                                Toast.makeText(activity, "すでに存在します", Toast.LENGTH_SHORT).show()
                            }
                            else{
                                val sqlInsert = "INSERT INTO items (items_name) VALUES (?)"
                                var stmt = db.compileStatement(sqlInsert)
                                stmt.bindString(1, addItem)
                                stmt.executeInsert()

                                Toast.makeText(activity, "追加しました", Toast.LENGTH_SHORT).show()

                                // ここで直接mainActivityに所属するrefreshを実行する。
                                mainActivity.refresh()
                            }
                        }
                        else{
                            Toast.makeText(activity, "アイテムを入力してください", Toast.LENGTH_SHORT).show()
                        }

                    }
                }
            }
        })

        builder.setNegativeButton("キャンセル", object :DialogInterface.OnClickListener{
            override fun onClick(dialog: DialogInterface?, which: Int) {
                Toast.makeText(activity, "キャンセルしました", Toast.LENGTH_SHORT).show()
            }
        })

        val dialog = builder.create()
        return dialog
    }
}

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

このやり方の悪い点

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/02/26 22:45 編集

    参考にして実現したい動作を実装させることができました、ありがとうございます。
    デリゲート(コールバック)と呼ばれるものをよく知らなかったので勉強になりました。

    1つ疑問があるのですが、AddDialogがonAdded?.invoke()を実行した時はMainActivit上でrefresh()が実行されるのか、それともAddDialogがrefresh()を参照して実行してるのでしょうか?(どっちでも無かったらすいません)

    後者な気がするのですが、その場合AddDialogはrefresh()内のlvが何かわからない気がするのですが(lvはrefresh()の外で定義or上記で仰られたようにインポートすることになると思うので)どうなんでしょうか?

    キャンセル

  • 2020/02/27 08:52

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

    キャンセル

  • 2020/02/27 20:28

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

    キャンセル

0

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

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

  • ただいまの回答率 88.64%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る