どうも、Shinです。
今回はAndroid Studioで簡単なメモ帳アプリを作ってみようという企画です。
なぜ、メモ帳アプリを作ってみようと思ったのかというと
- メモ帳アプリを自作できたらカッコイイと思ったから
- 実際に生活の中で使いたいから
- データベースの基本が学べるかと思ったから
画面遷移の基本であるIntentは前回勉強したので、その部分は何も参考にせずにコードを自力で書いていきたいと思います。
参考にさせて頂いたサイトはこちら→https://high-programmer.com/2017/09/04/android-studio-memo-app-4/(コードは全てこのサイトと同じです。勉強のため、参考にさせて頂きました)。
完成品・コードから
MainActivity画面はこちら↓
ListViewとButton(新規作成)しか置いてません。なんとも殺風景なレイアウトですが、今回はレイアウト の勉強ではないので、スルーです。
SubActivity画面はこちら↓
EditTextとButton(保存)とButton(戻る)です。レイアウトはガン無視です。
保存ボタンを押すと、Main画面に戻ってListViewに保存されていくという流れです↓
これがコードで実現されるというわけです。自力でこれが作れたら強いですよね。カッコイイ。
<MainActivity>
package com.example.memo_app2;
import androidx.appcompat.app.AppCompatActivity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.content.Intent;
import android.database.Cursor;
import android.widget.SimpleAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.TwoLineListItem;
import java.util.ArrayList;
import java.util.HashMap;
public class MainActivity extends AppCompatActivity {
Button newbutton;
MemoOpenHelper helper = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//データベースから値を取得する
if(helper == null){
helper = new MemoOpenHelper(MainActivity.this);
}
final ArrayList<HashMap<String, String>> memoList = new ArrayList<>();
//データベースを取得する
SQLiteDatabase db = helper.getWritableDatabase();
try {
Cursor c = db.rawQuery("select uuid, body from MEMO_TABLE order by id", null);
boolean next = c.moveToFirst();
while(next){
HashMap<String,String> data = new HashMap<>();
String uuid = c.getString(0);
String body = c.getString(1);
if(body.length() > 10) {
body = body.substring(0,11) + "...";
}
data.put("body",body);
data.put("id", uuid);
memoList.add(data);
next = c.moveToNext();
}
}finally {
db.close();
}
//Adapterの作成
final SimpleAdapter simpleAdapter = new SimpleAdapter
(this, memoList, android.R.layout.simple_list_item_2,new String[]{"body","id"},new int[]{android.R.id.text1,android.R.id.text2});
ListView listView = (ListView) findViewById(R.id.memoList);
listView.setAdapter(simpleAdapter);
//List項目をクリックしたときの処理
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
TwoLineListItem two = (TwoLineListItem)view;
TextView idTextView = two.getText2();
String idStr = (String)idTextView.getText();
intent.putExtra("id", idStr);
startActivity(intent);
}
});
//ListViewを長押ししたときの処理
listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
TwoLineListItem two =(TwoLineListItem)view;
TextView idTextView = (TextView)two.getText2();
String idStr = (String)idTextView.getText();
//データベースから削除
SQLiteDatabase db = helper.getWritableDatabase();
try{
db.execSQL("DELETE FROM MEMO_TABLE WHERE uuid = '"+ idStr + "'");
}finally {
db.close();
}
//画面から削除
memoList.remove(position);
simpleAdapter.notifyDataSetChanged();
return true;
}
});
//新規作成ボタンを実装
newbutton = findViewById(R.id.newbutton);
newbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
intent.putExtra("id", "");
startActivity(intent);
}
});
}
}
Mainでは、データベースから値を取得して、データベースに保存されているMEMO_TABLEの情報を取得しています。また、ArrayListメソッドでリスト表示を実現させています。取得した情報は、ListViewに表示させます。
ListViewを扱うときはAdapterメソッドは必須なので覚えておきましょう。また、ListViewを押したとき・長押ししたときの処理も書いています。
<SubActivity.java>
package com.example.memo_app2;
import androidx.appcompat.app.AppCompatActivity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.view.View;
import android.widget.Button;
import android.os.Bundle;
import android.widget.EditText;
import android.content.Intent;
import android.widget.TextView;
import java.util.UUID;
public class SubActivity extends AppCompatActivity {
//helperデータベースにnullを入れる
MemoOpenHelper helper = null;
Button savebutton;
Button backbutton;
EditText body;
String id = "id";
boolean newFlag = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
//作ったデータベースから値を取得する
if(helper == null){
helper = new MemoOpenHelper(SubActivity.this);
}
//MainActivityからインテントを取得。
Intent intent = this.getIntent();
id = intent.getStringExtra("id");
//画面に表示
if(id.equals("")){
newFlag = true ;
}else{
//編集の場合、データベースから値を取得して表示
//データベースを取得する
SQLiteDatabase db = helper.getWritableDatabase();
try{
Cursor c = db.rawQuery("select body from MEMO_TABLE where uuid = '"+ id +"'", null);
boolean next = c.moveToFirst();
while(next){
String dispBody = c.getString(0);
EditText body = findViewById(R.id.editText);
body.setText(dispBody, TextView.BufferType.NORMAL);
next = c.moveToNext();
}
}finally {
db.close();
}
}
//保存ボタンを押したときの処理
savebutton = findViewById(R.id.savebutton);
savebutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//メモ内容を取得する
body = findViewById(R.id.editText);
String bodyStr = body.getText().toString();
//データベースに保存する
SQLiteDatabase db = helper.getWritableDatabase();
try {
if (newFlag) {
id = UUID.randomUUID().toString();
//INSERT
db.execSQL("insert into MEMO_TABLE(uuid, body) VALUES('" + id + "', '" + bodyStr + "')");
} else {
//UPDATA
db.execSQL("update MEMO_TABLE set body = '" + bodyStr + "' where uuid = '" + id + "'");
}
} finally {
db.close();
}
//データベースに保存後、Mainに戻る。
Intent intent = new Intent(SubActivity.this, MainActivity.class);
startActivity(intent);
}
});
//Mainに戻る
backbutton = findViewById(R.id.backbutton);
backbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
}
SubActivityでは、メモした内容(editText)をデータベースに保存するコードを書いています。データベースに情報を保存するにはexecSQLメソッドでINSERTやUPDATE文を書く必要があります。
MySQLやSQLiteの基礎知識がないと理解できない部分でした。私はSQLiteに触れるのが今回で初めてだったので、ここで勉強をはじめました。未だに0からコードを書けと言われても書くことはできません。勉強中です。
<MemoOpenHelper.java>
package com.example.memo_app2;
import android.database.sqlite.SQLiteDatabase;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
public class MemoOpenHelper extends SQLiteOpenHelper {
static final private String DBName = "MEMO_DB";
static final private int VERSION = 1;
public MemoOpenHelper(Context context){
super(context, DBName, null, VERSION);
}
//データベースが作成されたときに実行される
public void onCreate(SQLiteDatabase db){
//テーブルを作成する
db.execSQL("CREATE TABLE MEMO_TABLE (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " + "uuid TEXT, " + "body TEXT)");
}
//データベースをバージョンアップしたとき、テーブルを削除し、新しくデータベースを作成する。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
db.execSQL("DROP TABLE IF EXISTS MEMO_TABLE");
onCreate(db);
}
}
MemoOpenHelper.javaでは、データベースを管理するファイルになります。今回のメモ帳アプリ企画で1番学ぶべきところだと思います。
ここに書かれいてるコードはデータベースを作成するときの定型文みたいな感じだと思います。
db.execSQL("CREATE TABLE MEMO_TABLE (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "uuid TEXT, " + "body TEXT)"); }
の部分でデータベースに内にテーブルを作成しています。id,uuid,bodyはそれぞれ、列のIDということが分かりました。idは0番から順に数字が割り振られるようです。それがAUTOINCREMENTの役割です。
また、データベースがバージョンアップされたとき、テーブルが破棄されて新しくデータベースが作成されます。
本企画で学んだこと
正直、今回書いたコード全てを理解できたわけではありません。初心者ながら、学べたところだけを列挙して説明します。
android:gravity=”top|left”
これはActivitysub.xmlでのレイアウト関係の話です。
メモ帳だと画面の左上から書くのが一般的なので、それを書くにはどうすればいいのか悩んでいたところ「android:gravity=”top|left”」を書けば左上からメモできることが判明。
<EditText>内に記述しました。
戻るボタン機能は「finish()」メソッドを使えばいい
MainActivity画面に戻る「戻る」ボタンのコード↓
//Mainに戻る backbutton = findViewById(R.id.backbutton); backbutton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { finish(); } });
1行目はbackbuttonのIDを取得しています。また、押したときの処理を実装するためにsetOnClickListenerメソッドを利用しています。これは最初で学んでいたので何も見ずに記述できました。
処理の部分であるfinish()メソッド。
このメソッドは、最後にスタックされたActivityの画面に戻るという意味らしいですね。finish()で現在のアクティビティを終了させることで、メモリ不足によるエラーが発生しないようにしています。
データベース作成ならSQLiteOpenHelperクラスを使う
Androidアプリ作成で簡易なデータベースを利用するなら「SQliteOpenHelper」を利用します。本来ならばデータベース管理用のサーバーを用意する必要があるそうですが、これならその必要はありません。
今回はMemoOpenHelperというSQLiteOpenHelperクラスを継承した子クラスを作成しました。
データベースを削除・作成・編集したりするには、execSQLメソッドを書く必要があります。ここらに関しては初めて学んだので、勉強になりました。
final修飾子について
今回の内容と直接は関係しませんが、final修飾子について理解を深めることができました。たとえば、
static final private String DBName = "MEMO_DB";
「static final private」という意味は、インスタンスを複数扱うのを前提にしたクラスで、メモリを抑えることができて、かつそのクラス内でしか、そのインスタンスにアクセスすることができませんよ!って意味らしいですね。
変数やメソッドの前にfinalという修飾子をつけることで、誤って変数を変更してしまったりすることを防ぐことができます。コードの「安全性」のための記述だそうです。
初心者にとっては「なんだこれ?」という感じですが、チームでコーディングしていない人にとっては、その重要性が分かりにくいかもしれませんね。(データベース名をこれ以降変更する必要がないのでfinal修飾子が使われています。)
UUIDとは
UUIDとは、重複しないIDのことを指します。
randomUUID()は、重複しないIDをランダムに生成することができるということです。
if (newFlag) { id = UUID.randomUUID().toString(); db.execSQL("insert into MEMO_TABLE(uuid, body) VALUES('" + id + "', '" + bodyStr + "')");
このif文は、もし新規作成ならばidという変数にランダムかつ重複しない、新しく生成されたIDを格納します。という意味になります。重複しないということがポイントです。
生成したIDは、新しくMEMO_TABLEに挿入されます。それがexecSQL文に書かれている内容です。
falgについて
Flagとは、簡単に説明すると「ON(ture)かOFF(false)の状態かを判別するための変数」です。
boolean newFlag = false;
最初に変数newFlagにfalseを代入している意味は、基本はOFFの状態ですよという意味。つまり、旗が立っていない状態です。
後にif文でif(条件分){newFlag = true}として記述することで、はじめてフラグ(旗)が立った。という意味になります。つまり、このケースではメモの新規作成が行われるよ!という意図になります。
感想・気づいたこと
メモ帳アプリは比較的高難易度のアプリ作成であった。
データベース(SQLite)の実装をはじめて試みたが、これが中々ボリューミーな内容であった。また、同時にListViewにデータを格納する実装をしたので、かなり混乱した。
Adaperメソッドなどを理解するにはスタックやらなにやらも理解しないといけないので、かなり苦戦した。
正直、今回のメモ帳アプリでデータベースを完全に理解したとはまだまだ言えない。MySQLという別単元をガッツリ勉強しないとおそらく理解はできないだろう。
私はいまだに「Progate」なる教材を利用したことがない。なぜならネット上にはたくさんの情報は落ちているからだ。自分でググれば答えが落ちている。
次は何を作ろう?
ゲームが好きなので、ブロック崩しあたりを作ってみるのも面白そうだ。