Androidアプリ開発未経験の初心者が SQLiteのDBをAndroid StudioでListViewしてみた

パソコンで作ったDBをAndroidアプリに表示したかっただけなのですが、
基礎をわかってる人向けのサンプルコードしか見つからなかったので、
初心者(自分)向けにコメントを書きまくったコードを残しておきます。
(いきなり「R」とか言われても「なんのこっちゃ」って感じだったので。。)

なんかヘルパーとかカーソルとかアダプタとかを、ごにょごにょやるようです。


仕様
 ・表示するDBは、PCで作ったSQLiteのDB
 ・DBに含まれる dabimas テーブルの No 列と 種牡馬名 列を表示
 ・表示レイアウトは android.R.layout.simple_list_item_2


MainActivity.java

package com.example.testapp001;

import androidx.appcompat.app.AppCompatActivity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;

public class MainActivity extends AppCompatActivity {   // Empty Activityを選択するとデフォルトで生成される

  private ListView myListView;                          // 画面に表示するListViewのオブジェクト

  @Override                                             // お約束: ActivityクラスのonCreateメソッドを上書きします宣言
  protected void onCreate(Bundle savedInstanceState) {  // お約束: onCreateはプロセス起動(Create)時の最初の処理
    super.onCreate(savedInstanceState);                 // お約束
    setContentView(R.layout.activity_main);             // お約束: activity_main.xmlの画面配置を渡す

    //DB読み出し
    TestOpenHelper mDbHelper = new TestOpenHelper(this);    // DBのヘルパーを生成 (TestOpenHelper.java参照)
    SQLiteDatabase db = mDbHelper.getWritableDatabase();    // ヘルパーから読み出すDBを取得
    Cursor c = db.rawQuery(
                "SELECT No as _id, 種牡馬名 FROM dabimas;", // SQL文でカーソルを取得 (SQL文: dabimasテーブルから「No」と「種牡馬名」を取得)
                null);                                      // [注意] SimpleCursorAdapterの仕様で「_id」というcolumnを含まなければならないッ!

    //adapter生成
    SimpleCursorAdapter adapter = new SimpleCursorAdapter(
        this,                                               // コンテキスト: お約束
        android.R.layout.simple_list_item_2,                // レイアウト: android標準のテキストを2行表示するレイアウト
        c,                                                  // カーソル: SQLiteのカーソル
        new String[] {"_id", "種牡馬名"},                   // 表示元: SQLiteのcolumn名
        new int[] {android.R.id.text1, android.R.id.text2}, // 表示先: simple_list_item_2のtext1とtext2
        0);                                                 // 謎のflag: お約束

    //ListViewで表示
    myListView = (ListView)findViewById(R.id.list_view);    //activity_main.xmlのlist_viewでオブジェクト生成
    myListView.setAdapter(adapter);                         //生成したオブジェクトにアダプタをセット
  }
}

TestOpenHelper.java

package com.example.testapp001;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TestOpenHelper extends SQLiteOpenHelper {

  private static final String SRC_DB = "TestDB.db"; // コピー元: assetsフォルダ(AndroidStudioProjects\ProjectName\app\src\main\assets)のdbファイル
  private static final String APP_DB = "TestDB";    // コピー先: アプリ内部のdb
  private static final int DB_VERSION = 1;

  private Context context;
  private File dbPath;
  private boolean createDatabase = false;

  public TestOpenHelper(Context context) {
    super(context, APP_DB, null, DB_VERSION);
    this.context = context;                         // 変数に保存
    this.dbPath = context.getDatabasePath(APP_DB);  // 変数に保存
    context.deleteDatabase(APP_DB);                 // 一度APP_DBを作るとアプリ内に残ってしまうので毎回削除(デバッグ用)
  }

  @Override
  public synchronized SQLiteDatabase getWritableDatabase() {  //Main側プログラムから呼ばれるメソッド
    SQLiteDatabase database = super.getWritableDatabase();
    if (createDatabase) {                                     //onCreateがコールされた(=APP_DBが無い)場合
      try {
        database = copyDatabase(database);                    //SRC_DB_NAMEからAPP_DBへコピー
      } catch (IOException e) {
      }
    }
    return database;                                          //APP_DBを返す
  }

  private SQLiteDatabase copyDatabase(SQLiteDatabase database) throws IOException {
    database.close();                                         // 書き換えできるようにdbを閉じる

    InputStream input = context.getAssets().open(SRC_DB);     // assetsフォルダのdbファイル
    OutputStream output = new FileOutputStream(this.dbPath);  // APP_DBのdbファイル
    copy(input, output);                                      // コピー処理

    createDatabase = false;                                   // DB作成フラグOFF
    return super.getWritableDatabase();                       // dbを再度開く
  }

  @Override
  public void onCreate(SQLiteDatabase db) { // APP_DBが無い状態でgetWritableDatabaseがコールされるとここに来る
    super.onOpen(db);                       // これいる??
    this.createDatabase = true;             // DB作成フラグON
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  // DB_VERSIONが上がった時はここに来る
    onUpgrade(db, oldVersion, newVersion);  // これいる??
  }

  // CopyUtilsからのコピペ
  private static int copy(InputStream input, OutputStream output) throws IOException {
    byte[] buffer = new byte[1024 * 4];
    int count = 0;
    int n = 0;
    while (-1 != (n = input.read(buffer))) {
      output.write(buffer, 0, n);
      count += n;
    }
    return count;
  }
}

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"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".MainActivity">

  <ListView
    android:id="@+id/list_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>