【Android】SQLiteデータベースをListViewに一覧表示する方法|登録・編集・削除の流れを紹介

SQLiteデータをListViewに一覧表示し、登録・編集・削除する流れをまとめたアイキャッチ画像

Android Studioで、SQLiteデータベースに保存したデータを ListViewに一覧表示 し、そのデータを 登録・編集・削除 する方法を紹介します。

今回作成するのは、SQLiteデータベースの内容を一覧表示するメイン画面と、データを登録・編集する画面を使ったシンプルなアプリです。

一覧画面では、次の操作ができます。

  • 右下の +ボタン から新規登録画面を開く
  • 一覧の行をタップして編集画面を開く
  • 一覧右側の ×(削除)ボタン からデータを削除する

実際の動きは、次のようなイメージです。

Android listview

このアプリを題材に、データベースの情報をListViewに表示する方法と、ListViewの各行に配置した削除ボタンからデータを削除する方法を見ていきます。

目次

この記事で作るもの

今回作成する画面は、大きく分けて2つです。

  • 一覧画面:SQLiteデータベースの内容をListViewに表示する画面
  • 登録・編集画面:データの新規登録や編集を行う画面

一覧画面の行をタップすると、登録・編集画面が開きます。

右下の+ボタンをタップした場合は、新規登録用の画面として開きます。

Androidアプリの作り方

処理全体の流れを図にすると、次のようになります。

MainActivityからTextActivityを開き、INSERT・UPDATE・DELETEを行って一覧を再読み込みする流れをまとめた図

Android Studioのインストールや基本的な画面の見方、SQLiteに1件保存する基本形については、先にこちらの記事を読んでおくと進めやすいです。

構成

このアプリの主な構成は、次のようになります。

MainActivity、TextActivity、MainListAdapter、SampDatabaseHelper、SQLiteデータベースの関係を整理した構成図

実際の構成図では、メイン画面、テキスト画面、アダプター、データベースヘルパー、リソースが次のようにつながっています。

メイン画面、テキスト画面、MainListAdapter、SampDatabaseHelper、SQLiteデータベースのつながりを示した構成図

主な役割は次のとおりです。

  • MainActivity:一覧画面を表示する
  • activity_main.xml:一覧画面のレイアウトを定義する
  • row_main.xml:ListViewの1行分のレイアウトを定義する
  • MainListAdapter:データベースの検索結果をListViewに表示する
  • TextActivity:登録・編集画面を表示する
  • activity_text.xml:登録・編集画面のレイアウトを定義する
  • SampDatabaseHelper:SQLiteデータベースの作成や操作を管理する
  • DBContract:テーブル名やカラム名をまとめる
  • strings.xml / drawable:文字列やアイコンなどを管理する

ListViewに表示するだけでなく、行タップで編集画面を開いたり、削除ボタンでデータベースの該当データを削除したりするため、いくつかのファイルが連携します。

プロジェクト作成

新しいプロジェクトは、前回の記事の「プロジェクト作成」の章と同じ流れで作成できます。

Android Studioをインストール|プロジェクト作成の章へ

今回のプロジェクト名は SampListView とします。

リソース準備

まずは、アプリ内で参照する文字列やアイコンを準備します。

strings.xml

app/res/values/strings.xml を開いて、次の内容に編集します。

<resources>
    <string name="app_name">SampListView</string>
    <string name="title">タイトル</string>
    <string name="contents">内容</string>
    <string name="reg">登録</string>
    <string name="del">削除</string>
    <string name="cancel">キャンセル</string>
</resources>

ここで定義した name を指定して、プログラムやレイアウトから参照します。

前回と同じファイル

次のファイルは、SQLiteに保存する基本編と同じ考え方で使います。

  • frame_border.xml:タイトルなどの入力欄に枠線を表示する
  • DBContract:データベースのテーブル名や項目名を定義する
  • SampDatabaseHelper:データベースヘルパークラス

まだ作成していない場合は、こちらの記事の該当部分を参考にしてください。

アイコンを追加する

このアプリでは、一覧画面で使う +アイコン×アイコン を追加します。

Vector Assetを追加する

res/drawable/ を右クリックして、[New] → [Vector Asset] をクリックします。

削除用のアイコンを追加する場合は、「Clip Art」をクリックします。

Vector Assetで削除用アイコンを追加する画面

Android Studioに用意されているアイコンの一覧が表示されるので、追加したいアイコンを選択します。

Android StudioのSelect Icon画面でアイコンを選択している画面

削除アイコンは、色を赤に変更して、[Next] → [Finish] の順に進めます。

プラスアイコンも同じように追加します。

Vector Assetでプラスアイコンを追加する画面

Gradleのファイルに設定を追加する

アイコンを使えるようにするため、Gradle Scripts/build.gradle(Module)defaultConfig に次の1行を追加します。

・・・
defaultConfig {
vectorDrawables.useSupportLibrary = true

データベース作成

データベースの作成は、前回の記事と同じです。

  • DBContract でテーブル名やカラム名を定義する
  • SampDatabaseHelper でSQLiteOpenHelperを継承する
  • onCreate() でテーブルを作成する

詳細は、こちらの記事の「データベース作成」の部分を参考にしてください。

画面レイアウト

今回は、一覧表示用の画面と、登録・編集用の画面を作ります。

  • activity_main.xml:一覧画面
  • row_main.xml:ListViewの1行分
  • activity_text.xml:登録・編集画面

まずは全体のイメージを見ておきます。

ListView、row_main.xml、FloatingActionButtonの役割を整理した一覧画面のレイアウト図

activity_main.xml

app/res/layout/activity_main.xml に、メイン画面のレイアウトを設定します。

実際の一覧画面は、次のような見た目です。

activity_main

activity_main.xml の内容はこちらです。

activity_main.xml(クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
 
    <ListView
        android:id="@+id/mainList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
 
    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab_reg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:focusable="true"
        android:clickable="true"
        android:contentDescription="@string/reg"
        android:onClick="fab_reg_onClick"
        app:srcCompat="@drawable/ic_baseline_add_24"
        app:tint="@color/white"
        app:backgroundTint="@color/purple_200"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

この画面では、ConstraintLayout の中に、ListViewFloatingActionButton を配置しています。

ConstraintLayout

ConstraintLayout は、配置したウィジェットの上下左右などに 制約 を設定して、位置を決めるレイアウトです。

たとえば、次の図のように、TextViewとEditTextの位置関係を制約で指定できます。

ConstraintLayoutでTextViewとEditTextの位置関係を指定するイメージ図

このソースでは、ListViewを画面全体に配置し、FloatingActionButtonを右下に配置しています。

補足図で見ると、次のようなイメージです。

ConstraintLayoutでListViewとFloatingActionButtonを配置する考え方をまとめた図

layout_width="0dp" のように指定すると、制約の間いっぱいにウィジェットを広げることができます。

FloatingActionButton

FloatingActionButtonは、右下に表示している丸い +ボタン のことです。

アプリでメインとなる操作を、このように円形のボタンで表示します。

SampListViewの画面右下にFloatingActionButtonが表示されている画面

このソースでは、次のように指定することで、クリックされたときに MainActivityfab_reg_onClick() メソッドを呼び出しています。

android:onClick="fab_reg_onClick"

このメソッドで、登録画面を表示します。

ListView

ListView は、データをリスト表示できるウィジェットです。

1行分の見た目は、row_main.xml で定義します。

activity_main.xmlのListViewとrow_main.xmlの1行分レイアウトの関係を示した図

row_main.xml

row_main.xml には、ListViewの1行分のレイアウトを定義します。

row_main.xml(クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:descendantFocusability="blocksDescendants">
 
    <TextView
        android:id="@+id/title"
        android:layout_width="360dp"
        android:layout_height="70dp"
        android:gravity="center_vertical"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="24sp" />
 
    <ImageButton
        android:id="@+id/button_delete"
        android:layout_width="50dp"
        android:layout_height="70dp"
        android:background="#00000000"
        android:contentDescription="@string/del"
        android:gravity="center_horizontal|center_vertical"
        android:onClick="btnDel_onClick"
        app:srcCompat="@drawable/ic_baseline_close_24" />
 
</LinearLayout>

この1行分のレイアウトには、次の2つを配置しています。

  • TextView:タイトルを表示する
  • ImageButton:削除ボタンを表示する

descendantFocusability

ListViewの中にImageButtonを置くと、ListViewの OnItemClickListener と、ボタン側のクリック処理が競合しやすくなります。

その状態を避けるため、親の LinearLayout に次の属性を設定しています。

android:descendantFocusability="blocksDescendants"

これにより、行タップと削除ボタンのクリック処理を分けて扱いやすくなります。

activity_text.xml

次に、登録・編集用の画面を作ります。

実際の画面は、次のような見た目です。

SampListViewの登録・編集画面。タイトル入力欄、内容入力欄、登録ボタン、キャンセルボタンが表示されている画面

activity_text.xml の内容はこちらです。

activity_text.xml (クリックして表示)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <TextView
        android:id="@+id/textTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:text="@string/title"
        android:textSize="18sp"
        app:layout_constraintBaseline_toBaselineOf="@+id/editTitle"
        app:layout_constraintEnd_toStartOf="@+id/editTitle"
        app:layout_constraintStart_toStartOf="parent" />
 
    <EditText
        android:id="@+id/editTitle"
        android:layout_width="0dp"
        android:layout_height="40dp"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:background="@drawable/frame_border"
        android:hint="@string/title"
        android:importantForAutofill="no"
        android:inputType="text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textTitle"
        app:layout_constraintTop_toTopOf="parent" />
 
    <TextView
        android:id="@+id/textContents"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="40dp"
        android:text="@string/contents"
        android:textSize="18sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textTitle" />
 
    <EditText
        android:id="@+id/editContents"
        android:layout_width="0dp"
        android:layout_height="360dp"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:background="@drawable/frame_border"
        android:gravity="start|top"
        android:importantForAutofill="no"
        android:inputType="textMultiLine"
        android:labelFor="@id/textContents"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="1.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textContents" />
 
    <Button
        android:id="@+id/btn_reg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginStart="100dp"
        android:layout_marginTop="16dp"
        android:onClick="btnReg_onClick"
        android:text="@string/reg"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/btn_cancel"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editContents" />
 
    <Button
        android:id="@+id/btn_cancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:layout_marginEnd="100dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:onClick="btnCancel_onClick"
        android:text="@string/cancel"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/btn_reg"
        app:layout_constraintTop_toBottomOf="@+id/editContents" />
 
</androidx.constraintlayout.widget.ConstraintLayout>

次の指定を入れることで、タイトルのラベルと入力欄のベースラインを合わせています。

app:layout_constraintBaseline_toBaselineOf=”@+id/editTitle”

マニフェストファイル

登録・編集画面である TextActivity を起動できるように、AndroidManifest.xml に次の記述を追加します。

<activity android:name=".TextActivity">
</activity>

アダプター(Adapter)

アダプターは、データとウィジェットを関連付けるための橋渡しをするオブジェクトです。

今回のアプリでは、SimpleCursorAdapter を継承して、MainListAdapter というアダプターを作成します。

SimpleCursorAdapter は、データベースの検索結果を保持する Cursor を渡すことで、ListViewの1行分のレイアウトにデータを関連付けてくれるアダプターです。

また、SimpleCursorAdapter に渡す Cursor には、_id という名前の列が含まれている必要があります。

今回のコードでは、MainListAdapter の中で、リストの何行目かという位置情報を、各行の削除ボタンにタグとして設定しています。

MainListAdapter

MainListAdapter (クリックして表示)
package com.ma_chanblog.samplistview;
 
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.SimpleCursorAdapter;
 
public class MainListAdapter extends SimpleCursorAdapter {
 
    // コンストラクタ
    public MainListAdapter(Context context, int layout, Cursor c, String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
    }
 
 
    // 指定データのビューを取得
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view = super.getView(position, convertView, parent);
 
        // 削除ボタン オブジェクトを取得
        ImageButton btnDel = (ImageButton) view.findViewById(R.id.button_delete);
 
        // ボタンにリスト内の位置を設定
        btnDel.setTag(position);
 
        return view;
    }
 
}

削除ボタンを押したときに、この tag から位置を取り出して、該当する行の _id を取得します。

アクティビティ

ここからは、一覧画面の MainActivity と、登録・編集画面の TextActivity を見ていきます。

MainActivity

MainActivity は、一覧画面の表示、行タップでの編集画面起動、削除ボタンでの削除処理、+ボタンでの新規登録画面起動を担当します。

MainActivity(クリックして表示)
package com.ma_chanblog.samplistview;

import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.AdapterView;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import androidx.appcompat.app.AppCompatActivity;
import static com.ma_chanblog.samplistview.DBContract.DBEntry;

public class MainActivity extends AppCompatActivity {
    private SampDatabaseHelper helper = null;
    MainListAdapter sc_adapter;

    // アクティビティの初期化処理
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    // アクティビティの再開処理
    @Override
    protected void onResume() {
        super.onResume();
        // データを一覧表示
        onShow();
    }

    // データを一覧表示
    protected void onShow() {
        // データベースヘルパーを準備
        helper = new SampDatabaseHelper(this);
        // データベースを検索する項目を定義
        String[] cols = {DBEntry._ID, DBEntry.COLUMN_NAME_TITLE, DBEntry.COLUMN_NAME_CONTENTS };

        // 読み込みモードでデータベースをオープン
        // ※SimpleCursorAdapterでデータを使うため、ここではtry-with-resourcesを使わずに開いたままにします
        SQLiteDatabase db = helper.getReadableDatabase();

        // データベースを検索
        Cursor cursor = db.query(DBEntry.TABLE_NAME, cols, null, null, null, null, null, null);
        // 検索結果から取得する項目を定義
        String[] from = {DBEntry.COLUMN_NAME_TITLE};
        // データを設定するレイアウトのフィールドを定義
        int[] to = {R.id.title};
        // ListViewの1行分のレイアウト(row_main.xml)と検索結果を関連付け
        sc_adapter = new MainListAdapter(
                this, R.layout.row_main, cursor, from, to,0);
        // activity_main.xmlに定義したListViewオブジェクトを取得
        ListView list = findViewById(R.id.mainList);
        // ListViewにアダプターを設定
        list.setAdapter(sc_adapter);
        // リストの項目をクリックしたときの処理
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            public void onItemClick(AdapterView av, View view, int position, long id) {
                // クリックされた行のデータを取得
                Cursor cursor = (Cursor)av.getItemAtPosition(position);
                // テキスト登録画面 Activity へのインテントを作成
                Intent intent = new Intent(MainActivity.this, com.ma_chanblog.samplistview.TextActivity.class);
                intent.putExtra(DBEntry._ID, cursor.getInt(0));
                intent.putExtra(DBEntry.COLUMN_NAME_TITLE, cursor.getString(1));
                intent.putExtra(DBEntry.COLUMN_NAME_CONTENTS, cursor.getString(2));
                // アクティビティを起動
                startActivity(intent);
            }
        });
    }

    // 削除ボタン タップ時に呼び出されるメソッド
    public void btnDel_onClick(View view){
        // MainListAdapterで設定されたリスト内の位置を取得
        int pos = (Integer)view.getTag();
        // アダプターから、_idの値を取得
        int id = ((Cursor) sc_adapter.getItem(pos)).getInt(0);

        // データの削除は一瞬で終わるため、ここはtry-with-resourcesで安全にクローズします
        try (SQLiteDatabase db = helper.getWritableDatabase()) {
            db.delete(DBEntry.TABLE_NAME, DBEntry._ID+" = ?", new String[] {String.valueOf(id)});
        }
        // データを一覧表示(画面を更新)
        onShow();
    }

    // 「+」フローティング操作ボタン タップ時に呼び出されるメソッド
    public void fab_reg_onClick(View view) {
        // テキスト登録画面 Activity へのインテントを作成
        Intent intent = new Intent(MainActivity.this, com.ma_chanblog.samplistview.TextActivity.class);
        // アクティビティを起動
        startActivity(intent);
    }

    // アクティビティが破棄されるときに呼び出されるメソッド
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 開きっぱなしだったデータベースヘルパーを安全に閉じます
        if (helper != null) {
            helper.close();
        }
    }
}

onResume()

これまで使用してきたonCreate() メソッドは、アクティビティが開始されたときに、初期化処理として実行されるメソッドです。

今回は、メインの一覧画面から登録・編集画面を呼び出し、またメインの画面が復帰する流れになるため、フォアグラウンドになるときに呼び出されるonResume()メソッドでも、画面の再表示を行っています。

onCreate()やonResume()のようなアクティビティのライフサイクルについては、公式サイトで紹介されています。

onShow()

今回のコードでは、データを一覧表示する onShow() メソッドの中で、自動でデータベースを閉じる構文(try-with-resources)を使用していません。

SimpleCursorAdapter は取得した Cursor を利用して画面を表示するため、一覧表示中にデータベースや Cursor を閉じてしまうと、実装内容によってはエラーの原因になる場合があります。

そのため、今回はアプリの画面が完全に閉じられるタイミング(ライフサイクルの最後である onDestroy() メソッド内)で helper.close() を呼び出し、データベースをまとめてクローズする構成にしています。

(※なお、一瞬で処理が終わる「削除ボタン」の処理内では、その都度安全にクローズして問題ありません)

インテント(Intent)

インテントは、他のアクティビティに情報を受け渡すための入れ物(非同期メッセージ)です。

今回のアプリでは、MainActivityでリストがクリックされたときに、クリックされた行のIDやタイトルなどの情報をインテントにつめてTextActivity に送っています。

テキストアクティビティ

登録・編集用の画面を表示するテキストアクティビティです。

TextActivity(クリックして表示)
package com.ma_chanblog.samplistview;
 
import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import static com.ma_chanblog.samplistview.DBContract.DBEntry;
 
public class TextActivity extends AppCompatActivity {
    private int id = 0;
    private EditText editTitle = null;
    private EditText editContents = null;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text);
 
        // ビューオブジェクトを取得
        editTitle = findViewById(R.id.editTitle);
        editContents = findViewById(R.id.editContents);
 
        // インテントを取得
        Intent intent = getIntent();
 
        //intentのデータを取得(データがない場合、第2引数の 0 が返る)
        id = intent.getIntExtra(DBEntry._ID,0);
        String title = intent.getStringExtra(DBEntry.COLUMN_NAME_TITLE);
        String contents = intent.getStringExtra(DBEntry.COLUMN_NAME_CONTENTS);
 
        // データ更新の場合
        if (id > 0){
            editTitle.setText(title);
            editContents.setText(contents);
        }
    }
 
    // 「登録」ボタン タップ時に呼び出されるメソッド
    public void btnReg_onClick(View view) {
 
        // ヘルパーを準備
        SampDatabaseHelper helper = new SampDatabaseHelper(this);
 
        // 入力欄に入力されたタイトルとコンテンツを取得
        String title    = editTitle.getText().toString();
        String contents = editContents.getText().toString();
 
        // 書き込みモードでデータベースをオープン
        try (SQLiteDatabase db = helper.getWritableDatabase()) {
 
            // 入力されたタイトルとコンテンツをContentValuesに設定
            // ContentValuesは、項目名と値をセットで保存できるオブジェクト
            ContentValues cv = new ContentValues();
            cv.put(DBEntry.COLUMN_NAME_TITLE, title);
            cv.put(DBEntry.COLUMN_NAME_CONTENTS, contents);
 
            if(id == 0) {
                // データ新規登録
                db.insert(DBEntry.TABLE_NAME, null, cv);
            } else {
                // データ更新
                db.update(DBEntry.TABLE_NAME, cv, DBEntry._ID + " = ?", new String[] {String.valueOf(id)});
            }
        }
 
        // TextActivityを終了
        finish();
    }
 
    // 「キャンセル」ボタン タップ時に呼び出されるメソッド
    public void btnCancel_onClick(View view) {
 
        // TextActivityを終了
        finish();
    }
 
}

onResume()

onResume() は、アクティビティが前面に戻ってきたときに呼ばれます。

今回は、一覧画面から登録・編集画面を開き、戻ってきたときに最新のデータを表示したいので、onResume()onShow() を呼んでいます。

@Override
protected void onResume() {
    super.onResume();
    onShow();
}

これにより、登録・編集が終わって一覧画面に戻ったタイミングで、データベースから再取得して表示し直せます。

Intent

Intent は、別のアクティビティに情報を渡すために使います。

今回のアプリでは、一覧の行をタップしたときに、その行の _id、タイトル、内容を TextActivity に渡しています。

intent.putExtra(DBEntry._ID, cursor.getInt(0));
intent.putExtra(DBEntry.COLUMN_NAME_TITLE, cursor.getString(1));
intent.putExtra(DBEntry.COLUMN_NAME_CONTENTS, cursor.getString(2));

この情報を受け取ることで、編集画面側で既存データを表示できます。

削除ボタンの処理

削除ボタンが押されたときの流れは次のようになります。

ListViewの削除ボタンからpositionを取り出し、_idを求めてSQLiteから削除するまでの流れをまとめた図
  1. 削除ボタンの tag からpositionを取得する
  2. positionを使って、アダプターから該当行のCursorを取得する
  3. Cursorから _id を取り出す
  4. _id を条件にして delete() を実行する
  5. onShow() を呼んで一覧を再表示する

TextActivity

TextActivity は、登録・編集画面を表示して、データの新規登録や更新を行うアクティビティです。

TextActivity(クリックして表示)
package com.ma_chanblog.samplistview;

import android.content.ContentValues;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

import static com.ma_chanblog.samplistview.DBContract.DBEntry;

public class TextActivity extends AppCompatActivity {

    private int id = 0;
    private EditText editTitle = null;
    private EditText editContents = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text);

        // ビューオブジェクトを取得
        editTitle = findViewById(R.id.editTitle);
        editContents = findViewById(R.id.editContents);

        // インテントを取得
        Intent intent = getIntent();

        // intentのデータを取得(データがない場合、第2引数の0が返る)
        id = intent.getIntExtra(DBEntry._ID, 0);
        String title = intent.getStringExtra(DBEntry.COLUMN_NAME_TITLE);
        String contents = intent.getStringExtra(DBEntry.COLUMN_NAME_CONTENTS);

        // データ更新の場合
        if (id > 0) {
            editTitle.setText(title);
            editContents.setText(contents);
        }
    }

    // 「登録」ボタン タップ時に呼び出されるメソッド
    public void btnReg_onClick(View view) {

        // ヘルパーを準備
        SampDatabaseHelper helper = new SampDatabaseHelper(this);

        // 入力欄に入力されたタイトルとコンテンツを取得
        String title = editTitle.getText().toString();
        String contents = editContents.getText().toString();

        // 書き込みモードでデータベースをオープン
        try (SQLiteDatabase db = helper.getWritableDatabase()) {

            // 入力されたタイトルとコンテンツをContentValuesに設定
            // ContentValuesは、項目名と値をセットで保存できるオブジェクト
            ContentValues cv = new ContentValues();
            cv.put(DBEntry.COLUMN_NAME_TITLE, title);
            cv.put(DBEntry.COLUMN_NAME_CONTENTS, contents);

            if (id == 0) {
                // データ新規登録
                db.insert(DBEntry.TABLE_NAME, null, cv);
            } else {
                // データ更新
                db.update(DBEntry.TABLE_NAME, cv, DBEntry._ID + " = ?", new String[] {String.valueOf(id)});
            }
        }

        // TextActivityを終了
        finish();
    }

    // 「キャンセル」ボタン タップ時に呼び出されるメソッド
    public void btnCancel_onClick(View view) {

        // TextActivityを終了
        finish();
    }
}

新規登録と更新の切り替え

TextActivity では、Intentから受け取った id の値で、新規登録か更新かを切り替えています。

if (id == 0) {
    db.insert(DBEntry.TABLE_NAME, null, cv);
} else {
    db.update(DBEntry.TABLE_NAME, cv, DBEntry._ID + " = ?", new String[] {String.valueOf(id)});
}
  • id == 0 のとき:新規登録
  • id > 0 のとき:既存データの更新

という流れです。

処理が終わったら finish() でTextActivityを閉じます。
その後、MainActivityに戻ると onResume() が呼ばれ、一覧が再表示されます。

画面遷移と登録・編集・削除の流れ

ここまでの流れをまとめると、次のようになります。

MainActivityからTextActivityを開き、INSERT・UPDATE・DELETEを行って一覧を再読み込みする流れをまとめた図

新規登録の流れ

  1. MainActivityで+ボタンを押す
  2. TextActivityを開く
  3. タイトルと内容を入力する
  4. 登録ボタンを押す
  5. SQLiteに INSERT する
  6. finish() で画面を閉じる
  7. MainActivityに戻り、一覧を再表示する

編集の流れ

  1. MainActivityで一覧の行をタップする
  2. Intentで _id、タイトル、内容をTextActivityに渡す
  3. TextActivityに既存データを表示する
  4. 内容を変更して登録ボタンを押す
  5. SQLiteに UPDATE する
  6. MainActivityに戻り、一覧を再表示する

削除の流れ

  1. MainActivityで削除ボタンを押す
  2. 削除ボタンの tag からpositionを取得する
  3. positionから該当行の _id を取得する
  4. SQLiteに DELETE を実行する
  5. 一覧を再表示する

確認しておきたいところ

SimpleCursorAdapterでエラーになる

SimpleCursorAdapter に渡す Cursor_id 列が含まれているか確認します。

今回のコードでは、次のように _ID を検索項目に入れています。

String[] cols = {DBEntry._ID, DBEntry.COLUMN_NAME_TITLE, DBEntry.COLUMN_NAME_CONTENTS };

一覧タップと削除ボタンの動きがうまく分かれない

row_main.xml の親レイアウトに、次の指定が入っているか確認します。

android:descendantFocusability="blocksDescendants"

ListViewの行タップと、ImageButtonのクリック処理が競合しやすいので、この指定が役に立ちます。

登録・編集後に一覧が更新されない

MainActivityonResume()onShow() を呼んでいるか確認します。

@Override
protected void onResume() {
    super.onResume();
    onShow();
}

登録・編集画面から戻ったタイミングで再表示するには、この流れが必要です。

まとめ

今回は、SQLiteデータベースの内容を ListViewに一覧表示 し、登録・編集・削除 を行う方法を紹介しました。

ポイントをまとめると、次のとおりです。

  • ListView でデータベースの内容を一覧表示する
  • row_main.xml で1行分の見た目を定義する
  • SimpleCursorAdapter を継承した MainListAdapter で表示をつなぐ
  • 行タップでは Intent を使って編集画面にデータを渡す
  • 削除ボタンでは _id を使ってSQLiteから該当データを削除する
  • 登録・編集後は onResume() で一覧を再表示する

ListViewとSQLiteを組み合わせると、保存したデータを一覧で扱えるようになります。

このあとRecyclerViewを使った一覧表示に進むと、さらに柔軟なリスト操作も試しやすくなります。

こちらは、関連記事です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次