Android SQLiteデータベースを作成する(SQL文をリソースファイルに定義する)

Android SQLiteデータベースを作成する では、onCreate()メソッドで実行するSQL文をハードコーディングしていました。
1~2つのテーブルならアリかもしれませんが、テーブル数が多いのでsql文をリソースファイルに定義したいと思います。


assets\sql\createフォルダにCREATE TABLE文を定義したsqlを配置します。
assets\sql\dropフォルダにDROP TABLE文を定義したsqlを配置します。


assets\sql\create\categories.sql
初期データを追加するInsert文も定義しています。sql文の区切りは「/」にしました。
CREATE TABLE Categories
(
 CategoryID   integer PRIMARY KEY,
 CategoryName  text NOT NULL,
 Description   text
) 
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(1,"飲料","")
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(2,"香辛料","")
/
INSERT INTO Categories
(CategoryID,CategoryName,Description)
values
(3,"加工食品","")
assets\sql\create\products.sql
CREATE TABLE Products
(
 ProductsID   integer PRIMARY KEY,
 ProductsName  text NOT NULL,
 CategoryID   integer NOT NULL
) 
assets\sql\drop\categories.sql
DROP TABLE Categories
assets\sql\drop\products.sql
DROP TABLE Products

MainActivity.java
package my.study.android;  

import android.app.Activity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
  
public class MainActivity extends Activity {   
      
    @Override  
    protected void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);  
        
        //データベースヘルパーのインスタンスを作成する(まだデータベースはできない)   
        DatabaseHelper dbHelper = new DatabaseHelper(this);   
        //データベースオブジェクトを取得する(データベースにアクセスすると作成される。)   
        SQLiteDatabase db = dbHelper.getWritableDatabase();    
        //データベースを閉じる   
        db.close();       
    }   
}

DatabaseHelper.java
package my.study.android;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

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

public class DatabaseHelper extends SQLiteOpenHelper {
 
    /* データベース名 */
    private final static String DB_NAME = "androidstudydb";
    /* データベースのバージョン */
    private final static int DB_VER = 2;    
    /* コンテキスト */
    private Context mContext; 
    
    /**
     * コンストラクタ
     */
    public DatabaseHelper(Context context) {
        super(context, DB_NAME, null, DB_VER);
        mContext = context;
    }

    /**
     * データベースが作成された時に呼ばれます。
     * assets/sql/create内に定義されているsqlを実行します。
     */
    @Override
    public void onCreate(SQLiteDatabase db) {     
        try {
            execSql(db,"sql/create");
        } catch (IOException e) {
            e.printStackTrace();
        }    
    }

    /**
     * データベースをバージョンアップした時に呼ばれます。
     * assets/sql/drop内に定義されているsqlを実行します。
     * その後onCreate()メソッドを呼び出します。
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {   
        try {
            execSql(db,"sql/drop");
        } catch (IOException e) {
            e.printStackTrace();
        }
        onCreate(db);
    }
    
    /**
     * 引数に指定したassetsフォルダ内のsqlを実行します。
     * @param db データベース
     * @param assetsDir assetsフォルダ内のフォルダのパス
     * @throws IOException
     */
    private void execSql(SQLiteDatabase db,String assetsDir) throws IOException {
        AssetManager as = mContext.getResources().getAssets();    
        try {
            String files[] = as.list(assetsDir);
            for (int i = 0; i < files.length; i++) {    
                String str = readFile(as.open(assetsDir + "/" + files[i]));
                for (String sql: str.split("/")){
                    db.execSQL(sql);
                } 
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * ファイルから文字列を読み込みます。
     * @param is
     * @return ファイルの文字列
     * @throws IOException
     */
    private String readFile(InputStream is) throws IOException{
        BufferedReader br = null;
        try {
            br = new BufferedReader(new InputStreamReader(is,"SJIS"));

            StringBuilder sb = new StringBuilder();    
            String str;      
            while((str = br.readLine()) != null){      
                sb.append(str +"\n");     
            }    
            return sb.toString();
        } finally {
            if (br != null) br.close();
        }
    }

}



【関連項目】
Android SQLiteデータベースを作成する
Android SQLiteのGUIツール
Android データベースにデータを書き込む(Insert、Update、Delete)
Android データベースのデータを読み込む(Select)
Android SQLiteデータベースを作成する(SQL文をリソースファイルに定義する)
Android ファイル読み込み(res/rawフォルダ、assetsフォルダ)

Android apkに書き込み可能なファイルを含める

apkに任意のファイルを含めてインストールし、プログラムから書き込み可能に出来ないかと調べてみました。

やりたい事は、apkにテキストファイルを含めて、インストール時にfilesフォルダに配置したかったのですが、
日本Androidの会 apkにファイルを含めるによると、直接filesフォルダに展開することはムリっぽいです。
かわりにres/rawにテキストファイルを含めて、初回起動時にfilesフォルダにコピーする方法が紹介されていました。


res/raw/test.txtをfilesフォルダにコピーします。
初回起動時はfiles/test.txtが存在しない場合としました。
package my.study.android;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //レイアウトの読み込み
        this.setContentView(R.layout.main);
        //filesフォルダにtest.txtが存在しなければ、res/raw/test.txtをコピーする。
        File file = this.getFileStreamPath("test.txt");
        if ( file.exists() == false ) {
            Resources res = this.getResources();
            try {
                fileCopy(res.openRawResource(R.raw.test),openFileOutput(file.getName(),MODE_PRIVATE));
            } catch (IOException e) {
                e.printStackTrace();
            }   
        } 
    }
 
    private void fileCopy(InputStream is , OutputStream os )throws IOException{
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            br = new BufferedReader(new InputStreamReader(is));    
            bw = new BufferedWriter(new OutputStreamWriter(os));   

            String str;      
            while((str = br.readLine()) != null){      
                bw.append(str +"\n");     
            } 
            bw.flush();   
        } finally {   
            if (br != null) br.close();
            if (bw != null)bw.close();       
        }  
    }

}

Android コーディング規約

Androidのコーティング規約です。
コントリビュータのためのAndroidコードスタイルガイドライン 日本語訳

一部抜粋です。
1.コメント/Javadoc: コメント/Javadocを書いてください。そこでは標準のスタイルを使ってください。
2.簡潔なメソッド: 巨大なメソッドを書いてはいけません。
3.フィールド: フィールドはファイルの先頭か、フィールドを使うメソッドの直前に書いてください。
4.ローカル変数: ローカル変数のスコープは限定するようにしてください。
5.インポート: android関連; サードパーティ製(アルファベット順); java(x) という順序でインポートしてください。
6.インデント: インデントにはスペース4つを使ってください。タブを使ってはいけません。
7.行の長さ: 1行は100カラムまでにしてください。
8.フィールド名: 非パブリック、非スタティックフィールドは m で始めてください。
9.中括弧: 開き中括弧で行を始めてはいけません。
10.アノテーション: 標準のアノテーションを使ってください。
11.頭字語(アクロニム)は単語: 名前では頭字語を単語として扱ってください。例えば、XmlHttpRequest、getUrl() など。
12.TODOのスタイル: 「TODO: こんな風に書いてください」
13.一貫性: まわりをよく見てください!
14.ログ記録: ログを記録するときには注意してください。ログにもコストがかかるのです。

Android ファイルの存在チェック

Androidではファイル名(パスを含まない)だけで、ファイルの書き込み、読み込みができます。
Android ファイルの入出力(filesフォルダ)

ファイルの存在チェックもパスはいらないだろうと思っていたら、こちらはパスがいるのですね・・・
String filepath = this.getFilesDir().getAbsolutePath() + "/" +  "test.txt";
File file = new File(filepath);
boolean isExists = file.exists();
又は
File file = this.getFileStreamPath("test.txt");
boolean isExists = file.exists();

Android リソースの種類と定義場所

resフォルダには色々なリソースファイルを配置しますが、リソースの種類により配置場所が決まっています。

名前説明
res/anim/xxx.xmlフレームアニメーションやTweenedアニメーションを定義したXMLファイル
res/drawableイメージファイル(.png、.9.png、.jpg)
デフォルトでdrawable-hdpi、drawable-ldpi、drawable-mdpiのディレクトリが作成されており、各ディレクトリに同じ名前のイメージファイルを配置する事で解像度によって使用するイメージを自動で切り替える事ができます。
res/layout/xxx.xml画面レイアウトを定義したXMLファイル
res/menu/xxx.xmlメニュー(オプションメニュー、コンテキストメニュー、サブメニュー)を定義したXMLファイル
res/values/arrays.xml配列を定義したXMLファイル
res/values/attrs.xmlカスタムレイアウトを定義したXMLファイル
res/values/colors.xml色を定義したXMLファイル
res/values/dimens.xml寸法を定義したXMLファイル
res/values/ids.xmlIDを定義したXMLファイル
res/values/strings.xml文字列を定義したXMLファイル
res/values-en/strings.xml、res/values-fr/strings.xml、res/values-ja/strings.xmlなどと言語コードをつけることで、ロケールにより自動で文字列を切り替える事ができます。
res/values/styles.xmlスタイルを定義したXMLファイル
res/xml任意のXMLファイル
res/raw任意のファイル(音楽データなどコンパイルせずにアプリケーションに含めたいファイルなど)

Android アプリ設定画面を作成する PreferenceScreen(別アクティビティ)

Android アプリ設定画面を作成する PreferenceScreen ではPreferenceScreenをクリックすると、同じアクティビティの別画面を開きました。

今回はPreferenceScreenをクリックすると、別のアクティビティを開きます。
Intentを使って別のアクティビティを開く方法は下記を見てください。
Android 別のアクティビティを開く(戻り値を受け取らない)
Android 別のアクティビティを開く(戻り値を受け取る)

まず、PreferenceScreenをクリックしたときに、開くアクティビティを作成します。
作り方はコチラAndroid プロジェクトに新しいアクティビティを追加する

Activityを継承し、TextViewを1つ配置しただけの「SubActivity」を用意しました。
res/layout/sub.xml(SubActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
<TextView 
 android:text="@+id/TextView01" 
 android:layout_width="wrap_content" 
 android:layout_height="wrap_content" 
 android:id="@+id/label">
</TextView>

</LinearLayout>
SubActivity.java
package my.study.android;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class SubActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sub);  
        //受け取った値をTextViewに表示する
        TextView label = (TextView)findViewById(R.id.label);
        Intent intent = getIntent();
        Bundle extra = intent.getExtras();
        String str = null;
        if (extra != null){
            str = extra.getString("arg1") + "\n"; 
            if (extra.containsKey("arg2")){
                str += extra.getString("arg2");
            }
        }
        label.setText(str);
        //遷移元に値を返す。
        Intent result = new Intent();
        result.putExtra("result", "From SubActivity Result");
        setResult(RESULT_OK,result);   
    }

}


PreferenceActivityを継承したMainActivityから、PreferenceScreenをクリックしたときに別のアクティビティを開く方法はいくつかあります。

まずはPreferenceScreenのxml定義にintentを記述する方法です。
intentタグのtargetPackageにパッケージ名を指定し、targetClassに遷移するアクティビティを指定します。
extraタグで遷移するアクティビティに値を渡す事ができます。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen
        android:key="@string/prefScreen1"
        android:summary="xmlでIntentを飛ばす"
        android:title="PreferenceScreen1" >
         <intent
          android:action="android.intent.action.MAIN"
          android:targetPackage="my.study.android"
          android:targetClass="my.study.android.SubActivity" >
      <extra android:name="arg1" android:value="prefscreen1: arg1 = from xml" />
     </intent>
    </PreferenceScreen>

</PreferenceScreen>
遷移するアクティビティに渡す値をコードから追加する場合は、PreferenceScreen#getIntent()メソッドでIntentオブジェクトを取得し
Intent#putExtra()メソッドで値を追加します。
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;

public class MainActivity extends PreferenceActivity {
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
        //PreferenceScreen1
        CharSequence csScreenPref1 = getText(R.string.prefScreen1);
        PreferenceScreen screenPref1 = (PreferenceScreen)findPreference(csScreenPref1);
        Intent intent1 = screenPref1.getIntent();
        intent1.putExtra("arg2", "prefscreen1: arg2 = from code");
    }
}

次はPreferenceScreenにコードでintentを記述し設定する方法です。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen 
        android:summary="コードでIntentを設定する"
        android:title="PreferenceScreen2"
        android:key="@string/prefScreen2">
    </PreferenceScreen>

</PreferenceScreen>
intentオブジェクトを作成し、PreferenceScreen#setIntent()メソッドでPreferenceScreenに設定します。
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;

public class MainActivity extends PreferenceActivity {
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
  
        //PreferenceScreen2
        CharSequence csScreenPref2 = getText(R.string.prefScreen2);
        PreferenceScreen screenPref2 = (PreferenceScreen)findPreference(csScreenPref2);
        Intent intent2 = new Intent(this,SubActivity.class);
        intent2.putExtra("arg1", "prefscreen2: arg1 = from code");
        screenPref2.setIntent(intent2);
    }
 
}

この2つの方法は遷移先のアクティビティに値を渡すことができますが、遷移先のアクティビティから値を受け取ることができません。
遷移先のアクティビティから戻ってきたときはonResume()が発生します。

次はPreferenceScreenのoOnPreferenceClickListenerをハンドルし、startActivityForResultでアクティビティを開く方法です。
この方法はonActivityResult()が発生し、遷移先のアクティビティから値を受け取ることができます。
もちろんonResume()も発生します。
res/xml/mainpref.xml(MainActivityのレイアウト)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

    <PreferenceScreen 
        android:summary="onClickでIActivityを開く"
        android:title="PreferenceScreen3"
        android:key="@string/prefScreen3">
    </PreferenceScreen>

</PreferenceScreen>
MainActivity.java
package my.study.android;  

import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.Toast;


public class MainActivity extends PreferenceActivity {
 
    private static final int SUBACTIVITY = 1; 
 
    private PreferenceScreen _screenPref3;
 
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.mainpref);
        //PreferenceScreen3
        CharSequence csScreenPref3 = getText(R.string.prefScreen3);
        _screenPref3 = (PreferenceScreen)findPreference(csScreenPref3);
        _screenPref3.setOnPreferenceClickListener(new OnPreferenceClickListener(){
            @Override
            public boolean onPreferenceClick(Preference pref) {
                return screenPref3_onPreferenceClick(pref);
            }});
  
    }
 
    private boolean screenPref3_onPreferenceClick(Preference pref){
        Intent intent = new Intent(this,SubActivity.class);   
        intent.putExtra("arg1", "prefscreen3: arg1 = from code");   
        startActivityForResult(intent, SUBACTIVITY);   
        return true;
    }
 
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        Toast.makeText(this, "onActivityResult", Toast.LENGTH_SHORT).show();
  
        if (requestCode == SUBACTIVITY){  
            String summary = null;
            if (resultCode == RESULT_OK){   
                Bundle extras = intent.getExtras(); 
                if (extras != null){   
                    summary = "result:" + extras.getString("result");        
                }   
            }
            _screenPref3.setSummary(summary);
        }   
    } 
}




 
MainActivity

PreferenceScree1をクリック後
PreferenceScree2をクリック後

PreferenceScree3をクリック後


MainActivityに戻る




【関連項目】
Android プリファレンスの編集
Android アプリ設定画面を作成する
Android アプリ設定画面を作成する CheckBoxPreference
Android アプリ設定画面を作成する EditTextPreference
Android アプリ設定画面を作成するRingtonePreference
Android アプリ設定画面を作成するListPreference
Android アプリ設定画面を作成する 依存関係を設定する
Android アプリ設定画面を作成する PreferenceScreen
Android アプリ設定画面を作成する PreferenceScreen(別アクティビティ)

Android アプリ設定画面を作成する PreferenceScreen

PreferenceScreenを入れ子にすると、項目を選択したときに次の設定画面が開きます。


pref.xml
PreferenceScreenに入れ子でPreferenceScreenを配置します。
さらに入れ子でCheckBoxPreferenceを配置します。

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
  xmlns:android="http://schemas.android.com/apk/res/android">

 <PreferenceScreen 
  android:summary="PreferenceScreen要約" 
  android:title="PreferenceScreenタイトル" 
  android:key="@string/pref_screen">
  
  <CheckBoxPreference 
   android:key="@string/checkbox" 
   android:summary="要約" 
   android:title="タイトル">
  </CheckBoxPreference>
  
 </PreferenceScreen>

</PreferenceScreen>

子のCheckBoxPreferenceのチェック状態を変更したとき、子のCheckBoxPreferenceの要約を変更するのはAndroid アプリ設定画面を作成する CheckBoxPreference と同じです。

子のCheckBoxPreferenceのチェック状態を変更したとき、親のPreferenceScreenの要約を変更するには、いくつか方法があります。
子画面(PreferenceScreenをクリックして開く画面)は、新しいアクティビティではないため、本体の戻るボタンで親画面に戻ってもonResume()などは発生しません。
そこでPreferenceScreenのOnPreferenceClickListenerをハンドルし、子画面のオブジェクトをgetDialog()メソッドで取得します。
取得したDialogオブジェクトのOnDismissListenerをハンドルし、子画面が破棄されたタイミングで親画面の要約を変更します。

package my.study.android;  

import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnDismissListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
import android.preference.Preference.OnPreferenceClickListener;
import android.widget.ListView;


public class MainActivity extends PreferenceActivity {
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //xmlレイアウトの読み込み
        this.addPreferencesFromResource(R.xml.pref);
        //親のPreferenceScreen
        CharSequence csScreenPrefParent = getText(R.string.prefscreen_Parent);
        PreferenceScreen screenPref = (PreferenceScreen)findPreference(csScreenPrefParent);
        //OnPreferenceClickListenerをハンドルする
        screenPref.setOnPreferenceClickListener(new OnPreferenceClickListener(){
            @Override
            public boolean onPreferenceClick(Preference preference) {
                return screenPreference_OnPreferenceClick(preference);
            }});
    }
 
    private boolean screenPreference_OnPreferenceClick(Preference preference){
        PreferenceScreen screenPref = (PreferenceScreen) preference; 
        //子画面オブジェクトを取得する
        Dialog d = screenPref.getDialog();
        //子画面のOnDismissListenerをハンドルする 
        d.setOnDismissListener(new OnDismissListener(){
            @Override
            public void onDismiss(DialogInterface dialog) {
                Dialog_OnDismiss(dialog);
            }});
        return true;
    } 
  
    private void Dialog_OnDismiss(DialogInterface dialog){
        //子のCheckPreferenceのチェック状態を取得する
        CharSequence csCheckPrefChild = getText(R.string.prefchk_child);
        CheckBoxPreference pref_checkChild = (CheckBoxPreference)findPreference(csCheckPrefChild);
        String summary = null;
        if (pref_checkChild.isChecked() == true) {
            summary = "Checked:True";
        } else {
            summary = "Checked:False";
        }
        //親のPreferenceScreenの要約を変更する
        CharSequence csScreenPrefParent = getText(R.string.prefscreen_Parent  );
        PreferenceScreen screenPref = (PreferenceScreen)findPreference(csScreenPrefParent);
        screenPref.setSummary(summary);
        //再描画
        ListView v = this.getListView();
        v.invalidateViews();
    }
 
}

【関連項目】
Android プリファレンスの編集
Android アプリ設定画面を作成する
Android アプリ設定画面を作成する CheckBoxPreference
Android アプリ設定画面を作成する EditTextPreference
Android アプリ設定画面を作成するRingtonePreference
Android アプリ設定画面を作成するListPreference
Android アプリ設定画面を作成する 依存関係を設定する
Android アプリ設定画面を作成する PreferenceScreen
Androidアプリ設定画面を作成する PreferenceScreen(別アクティビティ)