現状のAndroidアプリケーションは,ダミーの書籍データを表示しているだけで,キーワードによるデータベースの検索やデータベースに格納されている書籍情報の表示ができているわけではない。ここでは,アプリ上で入力されたキーワードをサーバに渡し,サーバから返される検索結果をスマホの画面に表示するように拡張していく。
先に構築したWebアプリケーションでは,サーバサイドからHTML形式のデータが応答として返され,それをWebブラウザで表示するという方式であった。このHTML文書には,キーワードに基づくデータベースの検索結果が含まれているので,同じサーバサイドプログラムをAndroidアプリから利用することも考えられるが,HTML形式は情報の表示方法を記述しているもので,本の情報自体を記述するにはうまく構造化がされていない。したがって,スマートフォンアプリケーションでは,クライアントとサーバがデータをやり取りするための方式として,JSON形式と呼ばれるものが多くのシステムで使われている。
例えば,JSON形式で今回の書籍情報を表現すると,以下のようになる。
[
{'ID': 1, 'TITLE': 'Androidの基本', 'AUTHOR': '立命太郎', 'PUBLISHER': '立命出版', 'PRICE': 1000, 'ISBN': '1234567890'},
{'ID': 2, 'TITLE': 'Androidの応用', 'AUTHOR': '立命次郎', 'PUBLISHER': '立命書籍', 'PRICE': 1200, 'ISBN': '2345678901'},
{'ID': 3, 'TITLE': 'Androidのススメ', 'AUTHOR': '立命三郎', 'PUBLISHER': '立命プレス', 'PRICE': 1500, 'ISBN': '3456789012'}
]
これを見れば分かるように,3つの書籍の情報が配列として表現され,それぞれの書籍の情報は「キー:バリュー」の形式で表現されている。JSON (JavaScript Object Notation)は,軽量なデータ記述言語の一つであり,人間にとっても読み書きが容易で,簡単なプログラムで読み書きが行えるデータ形式である。
Pythonからは,以下のプログラムに示すように,表現すべきデータを辞書のリストで表現し,print文で出力すると,簡単にJSON形式のデータが得られる。
books = []
book1 = {"ID": 1,\
"TITLE": "Androidの基本",\
"AUTHOR": "立命太郎",\
"PUBLISHER": "立命出版",\
"PRICE": 1000,\
"ISBN": "1234567890"}
books.append(book1)
book2 = {"ID": 2,\
"TITLE": "Androidの応用",\
"AUTHOR": "立命次郎",\
"PUBLISHER": "立命書籍",\
"PRICE": 1200,\
"ISBN": "2345678901"}
books.append(book2)
book3 = {"ID": 3,\
"TITLE": "Androidのススメ",\
"AUTHOR": "立命三郎",\
"PUBLISHER": "立命プレス",\
"PRICE": 1500,\
"ISBN": "3456789012"}
books.append(book3)
print(books)
以下のURLにアクセスすることにより,キーワード(この場合は"python")で指定された書籍の情報がJSON形式で返ってくることをWebブラウザで確認せよ。この時,例えばキーワードを"Java"にしてみたりするなど,キーワードによって返されるデータが変わることも確認すること。
http://www.cm.is.ritsumei.ac.jp/class/saproglab/server/cgi-bin/booksearch_json.py?query=python
実際のAndroidアプリケーションでは複数の画面間(Activity間)で遷移が行われる。この場合,現在表示されている画面から次の画面にデータを渡すことが必要な場合がある。例えば,サーバから検索結果を取得して表示できるようにするには,入力されたキーワードや書籍のタイトル情報などを画面間で渡し,前の画面で使われていた情報を次の画面でも使えるようにする必要がある。下図は,今回構築する書籍管理アプリ全体のデータフローを表したものである。
画面間の遷移には,Intentによって「キー:バリュー」の形式でデータ渡しを行うことができる。キーワード入力画面から検索結果画面へ入力されたキーワードを渡した上でResultActivityを開くように,MainActivityのonSearchButtonClicked()メソッドを以下のように書き換える。
Intent intent = new Intent(this, ResultActivity.class);
EditText keywordText = findViewById(R.id.keywordText);
intent.putExtra("QUERY", keywordText.getText().toString());
startActivity(intent);
このプログラムの2行目では,キーワード入力のためのユーザインタフェースコンポーネント(EditText)への参照をIDによって取得し,入力されている文字列を"QUERY"というキーのバリューとしてIntentに登録している。
遷移先のActivityでは,以下のようなプログラムで渡されたデータを受け取ることができる。今回は,これをResultActivityのonCreate()メソッド内に記述する。
Intent intent = getIntent();
String query = intent.getStringExtra("QUERY");
まず,Androidアプリからインターネット接続を許可するために,AndroidManifest.xmlというファイル(下図の左上付近に見える)に以下の行を追加する(<manifest>タグの要素として)。
<uses-permission android:name="android.permission.INTERNET" />
また,HTTPSではなく,HTTPでの通信を許可するために,以下の属性を<application>タグに追加する(今回は簡単化のために暗号化無しでの通信を許可するが,実際のアプリケーションでは推奨されない)。
android:usesCleartextTraffic="true"
上のデータフロー図に示しているように,サーバとの通信はResultActivityから行う。本来であれば,ResultActivityクラスにサーバとの通信処理を書けばよさそうであるが,サーバとの通信処理は,非同期処理(アプリの画面処理とは別のスレッド)で行う必要があるため,新しいクラスが必要である。新しいクラスを作成するには,下図のように,プロジェクトツリー上の"java"フォルダ内の一番上のパッケージ(この場合は,com.example.htakada.bookdatabase)を右クリックし,"New"→"Java Class"を選択する。新しいクラスの名前は"SearchTask"とする。
これによってSearchTask.javaというファイルが新しくできるので,その中身を以下のように書き換える(冒頭のpackage文はそのまま残す)。
import android.os.AsyncTask;
public class SearchTask extends AsyncTask<String, Void, String> {
private Listener listener;
protected String doInBackground(String... params) {
// サーバとの通信処理を記述する
}
protected void onPostExecute(String result) {
super.onPostExecute(result);
// サーバとの通信が終了したら,画面を更新する
if (listener != null) {
listener.onSuccess(result);
}
}
// 画面更新処理を登録するためのメソッド
void setListener(Listener listener) {
this.listener = listener;
}
// 画面更新処理を呼び出すためのインタフェース
interface Listener {
void onSuccess(String result);
}
}
サーバとの通信処理(入力されたキーワードをサーバに渡し,検索結果をJSON形式で受け取る処理)は,doInBackground()メソッド内に記述する。このメソッドの返り値は,受け取ったJSON形式の検索結果の文字列(String)である。
doInBackground()メソッド内に記述するサーバとの通信処理を行うプログラムを,以下に順に示す。必要に応じてimport文を追加しないと,コンパイルできないので注意すること。
String result = "[]";
try {
URL url = new URL("http://www.cm.is.ritsumei.ac.jp/class/saproglab/server/cgi-bin/booksearch_json.py");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
urlConnection.setDoOutput(true);
OutputStream out = urlConnection.getOutputStream();
String query = "query=" + params[0]; // query=Androidなど
try {
out.write(query.getBytes("UTF-8"));
out.flush();
} catch(Exception e) {
e.printStackTrace();
} finally {
out.close();
}
InputStream in = urlConnection.getInputStream();
try {
StringBuffer buffer = new StringBuffer();
BufferedReader reader = new BufferedReader(new InputStreamReader(in,"UTF-8"));
String str;
while((str = reader.readLine()) != null) {
buffer.append(str);
}
result = buffer.toString();
} catch(Exception e) {
e.printStackTrace();
} finally {
in.close();
}
} catch(Exception e) {
e.printStackTrace();
} finally {
urlConnection.disconnect();
}
} catch(Exception e) {
e.printStackTrace();
}
return result;
この時点ではまだ動作確認は行えないが,コンパイルエラーが起こっていないかを確認しておくこと。
サーバとの通信処理と,サーバからの応答に基づく検索結果の表示処理は,ResultActivityクラスのonCreate()メソッドの中で行う。onCreate()メソッドは,対応するActivityが作成されたときに起動されるメソッドである。以下,これに関するプログラムを順に説明する。
private class BookInfo {
int ID;
String title;
String author;
String publisher;
int price;
String isbn;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_result);
// キーワード入力画面から入力されたキーワードを受け取る
Intent intent = getIntent();
String query = intent.getStringExtra("QUERY");
// ListViewに表示するデータを格納するためのArrayList
final ArrayList<HashMap<String, String>> listData = new ArrayList<>();
// 非同期で実行するサーバとの通信処理を行うクラスのインスタンスを作成
SearchTask task = new SearchTask();
// サーバから応答が返ってきたときの処理を記述
task.setListener(new SearchTask.Listener() {
@Override
public void onSuccess(String result) {
//
// サーバから受け取ったJSON形式の検索結果を処理をここに記載
//
// ListViewに表示するためのAdapterを生成
SimpleAdapter adapter = new SimpleAdapter(ResultActivity.this,
listData, // ListViewに表示するデータ
android.R.layout.simple_list_item_2, // ListViewで使用するレイアウト(2つのテキスト)
new String[]{"title","author"}, // 表示するHashMapのキー
new int[]{android.R.id.text1, android.R.id.text2} // データを表示するid
);
// ListViewの初期化処理
ListView listView = (ListView) findViewById(R.id.listView);
listView.setAdapter(adapter);
// ListView中の要素がタップされたときの処理を記述
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Intent intent = new Intent(ResultActivity.this, BookInfoActivity.class);
startActivity(intent);
}
});
}
});
// サーバとの通信を非同期で起動
task.execute(query);
}
もっとも重要な処理は,上記のonSuccess()メソッド内にある「サーバから受け取ったJSON形式の検索結果を処理」の部分である。この部分のプログラムを以下に示す。
final ArrayList<BookInfo> bookList = new ArrayList<BookInfo>();
try {
JSONArray jsonArray = new JSONArray(result);
for(int i=0;i<jsonArray.length();i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
// ここにJSON形式からデータを取り出す処理を記述
}
final BookInfo bookInfo = new BookInfo();
bookInfo.ID = jsonObject.getInt("ID");
bookInfo.title = jsonObject.getString("TITLE");
bookInfo.author = jsonObject.getString("AUTHOR");
bookInfo.publisher = jsonObject.getString("PUBLISHER");
bookInfo.price = jsonObject.getInt("PRICE");
bookInfo.isbn = jsonObject.getString("ISBN");
bookList.add(bookInfo);
listData.add(new HashMap() {
{put("title", bookInfo.title);}
{put("author", bookInfo.author);}
});
} catch(JSONException e) {
e.printStackTrace();
}
以上により,キーワード入力画面で入力した文字列に基づいてサーバ上で検索を行い,その結果がアプリの画面上に表示されるはずである。
最後に,検索結果画面上でタップされた書籍の情報を書籍情報画面へ渡し,表示するプログラムを作成する。
BookInfo bookInfo = bookList.get(position);
intent.putExtra("id", bookInfo.ID);
intent.putExtra("title", bookInfo.title);
intent.putExtra("author", bookInfo.author);
intent.putExtra("publisher", bookInfo.publisher);
intent.putExtra("price", bookInfo.price);
intent.putExtra("isbn", bookInfo.isbn);
// Intentからの情報の取り出し
Intent intent = getIntent();
int id = intent.getIntExtra("id", -1);
String title = intent.getStringExtra("title");
String author = intent.getStringExtra("author");
String publisher = intent.getStringExtra("publisher");
int price = intent.getIntExtra("price", 0);
String isbn = intent.getStringExtra("isbn");
// ユーザインタフェースコンポーネントへの参照の取得
TextView titleView = (TextView) findViewById(R.id.titleView);
TextView authorView = (TextView) findViewById(R.id.authorView);
TextView publisherView = (TextView) findViewById(R.id.publisherView);
TextView priceView = (TextView) findViewById(R.id.priceView);
TextView isbnView = (TextView) findViewById(R.id.isbnView);
// 書籍情報の表示
titleView.setText(title);
authorView.setText(author);
publisherView.setText(publisher);
priceView.setText(new Integer(price).toString());
isbnView.setText(isbn);
以上のプログラムにより,検索結果画面で要素をタップすると書籍情報画面へ遷移し,書籍の詳細情報(タイトル,著者,出版社,価格,ISBN)が表示されることを確認せよ。
Copyright © 2018-2020 Hideyuki Takada