現状のAndroidアプリケーションは,ダミーの書籍データを表示しているだけで,キーワードによるデータベースの検索やデータベースに格納されている書籍情報の表示ができているわけではない。ここでは,アプリ上で入力されたキーワードをサーバに渡し,サーバから返される検索結果をスマホの画面に表示するように拡張していく。
先に構築したWebアプリケーションでは,サーバサイドからHTML形式のデータが応答として返され,それをWebブラウザで表示するという方式であった。このHTML文書には,キーワードに基づくデータベースの検索結果が含まれているので,同じサーバサイドプログラムをAndroidアプリから利用することも考えられるが,HTML形式は情報の表示方法を記述しているもので,本の情報自体を記述するにはうまく構造化がされていない。したがって,HTML形式ではなく,JSON形式でデータベースの検索結果を返すようにサーバサイドのPythonプログラムを新しく構築する。
JSON (JavaScript Object Notation)は,軽量なデータ記述言語の一つであり,人間にとっても読み書きが容易で,簡単なプログラムで読み書きが行えるデータ形式である。Pythonからは,表現すべきデータを辞書のリストで表現し,print文で出力すると,簡単にJSON形式のデータが得られる。
例えば,以下のようなPythonプログラムを用意する。
persons = []
person1 = {"ID": 1,\
"NAME": "立命太郎",\
"ADDRESS": "京都",\
"AGE": 20}
persons.append(person1)
person2 = {"ID": 2,\
"NAME": "立命次郎",\
"ADDRESS": "草津",\
"AGE": 19}
persons.append(person2)
person3 = {"ID": 3,\
"NAME": "立命三郎",\
"ADDRESS": "大津",\
"AGE": 21}
persons.append(person3)
print(persons)
これを実行すると,以下のようなJSON形式の出力が得られる(分かりやすくするために改行を入れている)。
[
{'ID': 1, 'NAME': '立命太郎', 'ADDRESS': '京都', 'AGE': 20},
{'ID': 2, 'NAME': '立命次郎', 'ADDRESS': '草津', 'AGE': 19},
{'ID': 3, 'NAME': '立命三郎', 'ADDRESS': '大津', 'AGE': 21}
]
これを見れば分かるように,3人分の情報が配列として表現され,一人ひとりの情報は「キー:バリュー」の形式で表現されている。
先に構築した書籍検索のWebアプリケーションに基づいて,クライアントから検索キーワードを受け取り,そのキーワードに基づいてデータベース検索を行った後,検索結果をJSON形式で返すようなPythonプログラム(booksearch_json.pyというファイル名を付ける)を作成し,動作を確認せよ。なお,書籍情報は以下のような形式で記述するものとする。
[
{'ID': 192, 'TITLE': '初めてのPython 第3版', 'AUTHOR': 'Mark Lutz', 'PUBLISHER': 'オライリージャパン', 'PRICE': 4830, 'ISBN': '4873113938'},
{'ID': 297, 'TITLE': 'エキスパートPythonプログラミング', 'AUTHOR': 'Tarek Ziade', 'PUBLISHER': 'アスキー・メディアワークス', 'PRICE': 3780, 'ISBN': '4048686291'},
{'ID': 327, 'TITLE': 'Pythonプロフェッショナルプログラミング', 'AUTHOR': 'ビープラウド', 'PUBLISHER': '秀和システム', 'PRICE': 2940, 'ISBN': '4798032948'}
]
なお,JSON形式のデータを応答する際のContent-typeは以下のようにして指定する。
print("Content-type: text/json; charset=utf-8\n")
動作確認はWebブラウザから以下のようなURLでこのPythonプログラムにアクセスすることで行うことができる("query"をキーワードを表すパラメータとしている場合)。
http://localhost:8000/cgi-bin/booksearch_json.py?query=python
実際のAndroidアプリケーションでは複数の画面間(Activity間)で遷移が行われる。この場合,現在表示されている画面から次の画面にデータを渡すことが必要な場合がある。例えば,サーバから検索結果を取得して表示できるようにするには,入力されたキーワードや書籍のタイトル情報などを画面間で渡し,前の画面で使われていた情報を次の画面でも使えるようにする必要がある。下図は,今回構築する書籍管理アプリ全体のデータフローを表したものである。
画面間の遷移には,Intentによって「キー:バリュー」の形式でデータ渡しを行うことができる。キーワード入力画面から検索結果画面へ入力されたキーワードを渡した上でResultActivityを開くプログラムは以下のようになる。
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では,以下のようなプログラムで渡されたデータを受け取ることができる。通常,このプログラムは遷移先ActivityのonCreate()メソッド内に記述する。
Intent intent = getIntent();
String query = intent.getStringExtra("QUERY");
まず,Androidアプリからインターネット接続を許可するために,AndroidManifest.xmlというファイル(下図の左上付近に見える)に以下の行を追加する(</manifest>タグの上)。
<uses-permission android:name="android.permission.INTERNET" />
上のデータフロー図に示しているように,サーバとの通信は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://192.168.2.127:8000/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 Hideyuki Takada