メモ帳エクササイズ1

2009/4/7 まだ途中ですが、長いので一旦アップします。原文では、step13まであります。
2009/4/22 随分久しぶりの更新となってしまいましたが、Step6〜8を追加しました。

原文: http://developer.android.com/guide/tutorials/notepad/notepad-ex1.html

                                                                                                              • -

このエクササイズでは、メモを新規追加できる簡単なメモリストを作成します。ただし、メモの編集はここではまだできません。実際に手を動かしながら、以下の項目を学習していきます。

  • ListActivitiesの基礎とメニューオプションの作成や処理
  • メモの保存に必要なSQLite databaseの使い方
  • SimpleCursorAdapterを使ってデータベースのカーソルからListViewに対してデータをバインドする方法
  • リストのViewを配置する方法、メニューに項目を追加する方法、メニュー選択時の処理方法等の画面レイアウトの基礎

ステップ1
EclipseでNotepadv1プロジェクトを開きましょう。

Notepadv1プロジェクトが出発点です。まずは、Hello Worldチュートリアルでも見かけたお決まりの手順を実行します。
1. ファイル > 新規 > Androidプロジェクトで新規Androidプロジェクトを作成します。
2. 新規Androidプロジェクトダイアログで、外部ソースからプロジェクトを作成を選択します。
3. 参照をクリックし、(準備段階でダウンロードした)NotepadCodeLabをコピーした場所まで移動します。その後、Notepadv1を選択し、選択をクリックしてください。
4. プロジェクト名にNotepadv1、ロケーションに選択したパスが入力されているか確認してください。
5. 完了をクリックしてください。Notepadv1プロジェクトが開き、Eclipseのパッケージエクスプローラ上に表示されます。

AndroidManifest.xmlでエラーが発生した場合や、Android zipファイルに関する問題が発生した場合は、プロジェクトを右クリックして、Androidツール > プロジェクトプロパティを修正を選択してください(プロジェクトがライブラリファイルの間違った場所を参照していた場合は、修正してくれます)。

ステップ2
NotesDbAdapterクラスを見てみましょう。このクラスは、メモデータを保持したり更新したりすることができるSQLiteデータベースへのデータアクセス処理をカプセル化しています。

クラスの最上部には定数が宣言されていて、アプリケーションがデータベースの適切なフィールド名からデータを取得する際に使われます。また、データベース作成用文字列も定義されており、データベースが存在しない場合には新規のデータベースを作成します。

今回はデータベース名を”data”とし、テーブルは”notes”というテーブル1つだけです。”notes”テーブルには3つのフィールド”_id”, “title”, “body”があります。”_id”は通常、データベースを参照または更新する際に一意に特定できるものでなければなりません。他の2つのフィールドはデータを保存する単純なテキストフィールドです。

NoteDbAdapterのコンストラクタにはContextが渡されており、Androidオペレーティングシステムの一部分と何らかのやり取りをすることができるようになっています。これは、Androidシステムを利用したいクラスに一般的に用いられる手法です。ActivityクラスはContextクラスの派生クラスなので、Contextが必要な時に単にActivityからthisを渡せばいいだけです。

open()メソッドは内部クラスDatabaseHelperのインスタンスを生成しています。DatabaseHelperはSQLiteOpenHelperのサブクラスです。データベースを作成またはオープンするのにgetWriteableDatabase()を呼んでいます。

close()は単にデータベースをクローズし、コネクションに関連するリソースを解放します。

createNote()は渡された新規メモのタイトルとボディをもとに、データベースにメモデータを作成します。新規メモの作成が成功すると、メソッドは新規作成したメモの_id値を返します。

deleteNote()は渡されたrowIdからメモを特定し、データベースから削除します。

fetchAllNotes()はデータベースの全メモのカーソルを返すクエリを発行します。query()メソッドの呼び出しをもう少し深く見てみましょう。最初のフィールドは問い合わせを行うデータベースのテーブル名です(この例では、定数DATABASE_TABLEの値として”notes”が渡されています)。次のフィールドは、取得したい列のリストです。今回の場合は、_id, title, bodyを受け取りたいので、String配列で列名を指定しています。残りのフィールドは、順にselection, selectionArgs, groupBy, having, orderByです。nullを渡すと全データがグルーピングなしのデフォルト順で返ってきます。詳細は、「SQLiteデータベース」を参照してください。


問い合わせの結果は、行の集合ではなくカーソルで返されます。これにより、Androidはリソースを効率的に使用できます。メモリ上に大量のデータを直接展開する代わりに、カーソルが必要に応じてデータを取得・解放するため、行数が多いテーブルをより効率的に処理できるようになります。

fetchNote()は、fetchAllNotes()と類似していますが、指定したrowIdに一致するメモを1つだけを返します。fetchNote()で使用しているquery()メソッドは、fetchAllNotes()で使用しているものと若干異なります。(trueをセットしている)最初の引数は結果を重複なしで返すかどうかを指定するものです。selection(4つ目の引数)では、”where _id =”の後に続くrowidに一致するデータだけを検索するよう指定しています。最終的には、1行だけを指し示すカーソルが返されます。


データへのアクセスと編集
このエクササイズでは、データを格納するのにSQLiteデータベースを使用しています。あなたのアプリケーションだけがデータへアクセスしたり、データを編集したりする場合には、SQLiteでも構いません。ただ、他のActivityにアクセスさせたり、データを編集させたりしたい場合には、ContentProviderを利用してデータを吐き出さなければなりません。
詳細は「ContentProvier」や「データストレージ」の章に記述されています。SDKのsamples/フォルダのメモ帳サンプルには、ContentProviderの作成方法例も記述されています。

ステップ3
res/layoutのnotepad_list.xmlを開いて、中を見てみましょう(XMLを表示するため、場合によっては下のxmlタブを選択する必要があるかもしれません)。

現時点では、ほとんど何も記述されていないレイアウト定義ファイルが表示されるはずです。レイアウトファイルについて学習すべきことを以下に挙げます。

  • 全てのAndroidレイアウトファイルは、以下のヘッダ行から始めなければなりません
<?xml version="1.0" encoding="utf-8"?>
  • その次の行はレイアウトの指定である場合が多いです(必ずという訳ではありません)。今回の例では、LinearLayoutを使用します。
  • AndroidXMLネームスペースは、最上位のコンポーネント内、もしくはXMLレイアウト内で必ず定義しなければなりません。例えば以下の記述を追加することで、ファイルの以降の行でandroid:タグを使用することができます。xmlns:android="http://schemas.android.com/apk/res/android"


レイアウトとActivity
ほとんどのActivityクラスは、自分に関連するレイアウトを保持しています。レイアウトは、Activityがユーザに見せる”顔”なのです。今回の場合のレイアウトは、画面全体に表示され、メモリストを表示します。
ただし、Activityに対して画面全体に表示するレイアウトしか設定できない訳ではありません。フローティングレイアウト(例えば、ダイアログやアラートなど)を設定することもできますし、レイアウトを全く使わなくても構いません(使用するレイアウトを指定しなかった場合、Activityはユーザに表示されません)。


ステップ4
リストを保持するレイアウトを作成してみましょう。以下のようにLinearLayout要素の内部にコードを追加してください。

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

  <ListView android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
  <TextView android:id="@android:id/empty"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/no_notes"/>

</LinearLayout>
  • ListViewとTextViewのid文字列の@記号は、XMLパーサが以降のid文字列をIDリソースとして解析すべきであることを意味しています。
  • ListViewとTextViewは同時には表示されず、一度に表示されるものはどちらか一方だけです。ListViewはノートリストを表示される際に使われます。一方、TextViewはノートが1つも存在しない場合に表示されます(res/values/strings.xmlで文字列リソースにデフォルト値として”No Notes Yet!”が定義されています)。
  • idであるlistとemptyは、Androidプラットフォームのよって提供されるため、android:のプレフィックスが必要です(e.g. @android:id/list)
  • idにemptyとあるViewはListAdapterにListViewのデータが存在しない場合に、自動的に表示されます。データが存在しない時、ListAdapterはデフォルトでidがemptyのViewを検索しますが、ListViewのsetEmptyView(View)メソッドを利用して、データが存在しない場合のデフォルトViewを変更することもできます。

皆さんのプロジェクトにあるRクラスがプロジェクトで定義したリソースであるのに対して、android.Rクラスはプラットフォームで提供されている定義済みリソースです。android.Rクラスにあるリソースは、”android:”のプレフィックスを利用することで、XMLファイルで使用することができます。

ステップ5
ListViewでノートリストを作成するために、各行のViewを定義しましょう。

1. res/layoutの下にnotes_row.xmlという名前の新規ファイルを作成します。
2. 以下を追加してください(注記:XMLヘッダが再び登場します。最初のノードではAndroidXMLネームスペースを定義しています。)

<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text1"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>


上記のViewは各行のタイトルを表示するもので、テキストフィールドを1つだけ保持しています。

今回の例では、text1という名前の新規idを作成します。idの@の後にある+は、もしそのidが存在しなければ、リソースとして自動的に作成されることを意味しています。
3. ファイルを保存します。

プロジェクトのR.javaクラスを開いて、中を見てみましょう。notes_rowとtext1の新規定義があるはずです。R.javaに定義があれば、ソースコードからリソースを参照することができます。


リソースとRクラス
Eclipseプロジェクトのres/以下のフォルダは、リソース置き場です。res/以下のフォルダやファイルは特殊な構造になっています。
このフォルダで定義されたリソースとファイルは、Rクラスの項目と対応しており、アプリケーションから簡単にアクセスすることができます。Rクラスは、eclipseプラグインによって、res/フォルダの内容をもとに自動生成されます(コマンドラインを使用している場合は、aaptツールによって生成されます)。それだけではなく、アプリケーションの一部としてバンドルされデプロイも実行されます。

ステップ6
次に、Notepadv1クラスのソースコードを開いてみましょう。以下のステップでは、このクラスを修正し、メモを表示するリストアダプタにしていきます。また、新規メモの追加にも対応します。

最初に、Notepadv1をListActivityと呼ばれるActivityのサブクラスから派生したクラスにします。このクラスはリストを処理する機能を保持し、例えば、画面上にリスト項目番号を表示したり、リスト内の項目を移動したり、選択したりできるようにします。

Notepadv1クラスの既存のコードを見てみましょう。メモ番号を作成するmNoteNumberという、現時点では使用されていないprivateフィールドがあります。

onCreate, onCreateOptionsMenu, onOptionsItemSelectedの3つのオーバーライドメソッドも用意されています。これから、このメソッドを実装していきます。

  • onCreate()メソッドはActivityが開始された際に呼び出されます。Activityのmainメソッドのようなものです。実行時にリソースやアクティビティの状態を設定するのに使われます。
  • onCreateOptionsMenu()メソッドは、アクティビティをメニューに配置する際に使われます。ユーザがメニューボタンを押した際に表示される選択可能なリスト(新規メモ作成など)を保持します。
  • onOptionsItemSelected()メソッドは、メニューから発生するイベントを処理するのに使われます(ユーザが「メモの作成」項目を選択した場合など)

ステップ7
ActivityからListActivityへNotepadv1の継承元を変更しましょう。

public class Notepadv1 extends ListActivity


注記: Eclipseを使ってListActivityをNotepadv1にインポートする場合、上記の変更を反映後、WindowsLinuxの場合はctrl-shift-O、Macの場合はcmd-shift-Oでインポートを実行することができます。

ステップ8
onCreate()メソッドを実装しましょう。

(画面の上端に表示される)Activityのタイトルをセットし、XMLで作成したnotepad_listレイアウトを使って、メモデータにアクセスするNotesDbAdapterインスタンスを生成し、メモタイトルと一緒にリストを配置します。

1. onCreateメソッドでは、savedInstanceStateを引数としてsuper.onCreate()を呼び出します。
2. setContentView()を呼び、R.layout.notepad_listを渡します。
3. クラスのトップで、型がNotesDbAdapter のprivateクラス変数mDbHelperを宣言します。
4. onCreate()メソッドに戻り、NotesDbAdapterインスタンスを生成して、mDbHelperフィールドに代入します(thisをDBHelperのコンストラクタに渡します)。
5. mDbHelperのopen()メソッドを呼び出し、データベースを開きます(または作成します)。
6. 最後に、新規メソッドfillData()を呼び、データを取得して、helperを使ってListViewに配置します。このメソッドはまだ定義していません。

onCreate()はこのようになります。

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.notepad_list);
        mDbHelper = new NotesDbAdapter(this);
        mDbHelper.open();
        fillData();
    }


mDbHelperフィールドも忘れずに宣言してください(mNoteNumber宣言の下に適切に)。

private NotesDbAdapter mDbHelper;