ニート大学生がアプリを作ってみた! その② ~CSVファイルの読み込み~

只今、Android端末用の掲示板アプリを暇つぶしがてら作成しています。
そして、前回はジャンルボタンを押下して、次のアクティビティにどのジャンルが選択されたのかを渡す処理を行いました。
今回はその続きです!

前回の記事をまだご覧になっていない方はぜひ!
目次
アプリ開発に必要なモノ
アプリ開発に移る前にまずは、「アニメ掲示板アプリ開発」に必要なモノを掲示しておきます。
全体を通して必要なモノ
・PC(Mac,Windowsどちらでも可)
・Android Studio(後でインストール)
・レンタルサーバー
今回必要なモノ
・PC(Mac,Windowsどちらでも可)
・Android Studio(後でインストール)
・レンタルサーバー
今回は、データをサーバーに格納→取得する処理を行うため、サーバーが必要になります。
アニメ一覧を表示する
前回、ジャンルボタンをMainActivityに設置し、SelectActivityにジャンル名を送って遷移させる処理を行いました。
今回は、サーバーに格納している"ジャンル名.csv"からボタン押下時に選択したジャンルのアニメ一覧を表示する処理を行います。
遷移先のアクティビティを用意
まずは、java>パッケージの中にSelectActivity.javaとres>layoutの中にactivity_select.xmlを作成します。
まずは外観を整えましょう。
activity_select.xmlは以下の通りです。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/tool"
android:minHeight="?attr/actionBarSize"
app:titleTextColor="@color/toolColor"/>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginHorizontal="20dp"
android:background="#ffffff"
android:dividerHeight="10.0sp"
android:divider="@drawable/background">
</ListView>
</LinearLayout>
前回と比較して、超シンプル。(ˊᗜˋ*)
ツールバーの設置と、ListViewを入れただけです!
見た目はこんな感じ。

ListViewで
android:dividerHeight="10.0sp"
とすることで、各アイテムの間隔を取っています。
続いて、SelectActivity.javaを編集し動的な処理を行います。
SelectActivityの方で、ListViewにジャンルに適合するアニメのタイトルを入れていきます。
↑こんな感じにアニメを表示させたい!
ではでは、SelectActivityのコードを見ていきましょう。
package com.nushiweb.animekeijiban;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
public class SelectActivity extends AppCompatActivity {
private ListView lv;
private GetMyData getMyData;
private String file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_select);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24);
final Intent intentBack = new Intent(this,MainActivity.class);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startActivity(intentBack);
}
});
Intent getI = getIntent();
String genre = getI.getStringExtra("search");
lv = findViewById(R.id.listview);
toolbar.setTitle(genre);
getMyData = new GetMyData();
getMyData.setListener(createListener());
assert genre != null;
switch (genre){
case "SF/ファンタジー":
file = "sf";
break;
case "ロボット/メカ":
file = "robot";
break;
case "アクション/バトル":
file = "action";
break;
case "コメディ/ギャグ":
file = "comedy";
break;
case "恋愛/ラブコメ":
file = "love";
break;
case "日常/ほのぼの":
file = "life";
break;
case "スポーツ/競技":
file = "sports";
break;
case "ホラー/サスペンス/推理":
file = "horror";
break;
case "歴史/戦記":
file = "history";
break;
case "戦争/ミリタリー":
file = "war";
break;
}
getMyData.execute(file);
}
@Override
protected void onDestroy() {
getMyData.setListener(null);
super.onDestroy();
}
private GetMyData.Listener createListener() {
return new GetMyData.Listener() {
@Override
public void onSuccess(final Map<String, String> data) {
ArrayAdapter<String> adapter;
Set<String> keySet = data.keySet(); //key取得
Collection<String> values = data.values(); //value取得
final ArrayList<String> titleList = new ArrayList<>(values);
final ArrayList<String> titleRomList = new ArrayList<>(keySet);
if(data.size() != 0){
adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, titleList);
lv.setAdapter(adapter);
lv.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
Intent i = new Intent(getApplication(),PostActivity.class);
i.putExtra("title", titleList.get(position));
i.putExtra("titleRom", titleRomList.get(position));
startActivity(i);
}
}
);
}
}
};
}
}
なにやら難しそうな処理をしていますね。
とりあえず、基本的なところから押さえていきましょう!
まずは、Intentを使用してMainActivityからデータを受け取ります。
Intent getI = getIntent();
String genre = getI.getStringExtra("search");
続いて、受け取ったデータ(ジャンル)をツールバーのタイトルにしてあげましょう。
toolbar.setTitle(genre);
MainActivityで選択されたジャンルのアニメを表示したいので、switch文でジャンルファイル(.csv)のファイル名を取得します。

変数 fileにファイル名を格納できたら
getMyData.execute(file);
として、ファイル名を後ほど作成するGetMyDataというクラスに渡してあげます。
サーバーにCSVファイルを作成・保存する
GetMyDataクラスの前にまずはCSVファイルをサーバ内に用意してあげましょう。
ジャンル別にファイルを作成し、それぞれにジャンルに適合するアニメタイトルを記述します。
ちなみに、僕はさくらサーバーを使ってます。<(*_ _)>
↓のようにcsvファイルを10個ほど作成します。

中身は↓こんな感じ。

ローマ字タイトルと通常文字のタイトルを書き込みます。
csvなので、文字は","で区切りましょう。
ローマ字タイトルは、次回取り扱う投稿ファイル名になります。
お好きに決めていただいて結構です。
非同期処理でアニメ一覧を取得
先ほどからお話しに出ているGetMyDataについてです。
こちらには非同期的にサーバからファイル情報を取ってくる処理を記述します。(SelectActivityと同じ階層に作成)
SelectActivity.javaに記述した処理を行っている最中に裏でコソコソGetMyDataに動いてもらうって感じですね。
ネットワーク通信を行う際には非同期で行うのがベターらしい。
そして、GetMyDataのコードがこちら。
package com.nushiweb.animekeijiban;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class GetMyData extends <String, Void, Map<String, String>> {
private Listener listener;
@Override
protected Map<String, String> doInBackground(String... params){
String line;
Map<String, String> data = new HashMap<>();
HttpURLConnection http = null;
try{
URL url = new URL("https://フォルダ/"+params[0]+".csv");
http = (HttpURLConnection)url.openConnection();
http.setRequestMethod("GET");
http.connect();
InputStreamReader in = new InputStreamReader(http.getInputStream(), StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(in);
while((line = br.readLine()) != null){
String[] RowData = line.split(",");
//ローマ字タイトル、通常タイトルをセット
data.put(RowData[0],RowData[1]);
}
br.close();
in.close();
http.disconnect();
}catch(Exception e){
e.printStackTrace();
}finally{
assert http != null;
http.disconnect();
}
return data;
}
protected void onPostExecute(Map<String, String> data){
if (listener != null) {
listener.onSuccess(data);
}
}
void setListener(Listener listener) {
this.listener = listener;
}
interface Listener {
void onSuccess(Map<String, String> data);
}
}
非同期処理を行うため、AsyncTaskを継承します。
ほとんどテンプレのままですが、 doInBackground の引数をString…paramsとし、SelectAcitivityからswitch分で取得したファイル名(file)を受け取っています。
また、返り値をMapにすることで、SelectActivityに(ローマ字タイトル,タイトル)を渡すようにしています。
タイトル:ListViewで表示するため
ローマ字タイトル:次回、投稿ページを作成する際に使用
(”ローマ字タイトル.csv”ファイルで各投稿を書き込む&読み取る)
ListViewにアニメタイトルをセット
いよいよ、アニメ一覧表示です!

SelectActivityに戻ります。
private GetMyData.Listener createListener() {
return new GetMyData.Listener() {
@Override
public void onSuccess(final Map<String, String> data) {
ArrayAdapter<String> adapter;
Set<String> keySet = data.keySet(); //key取得
Collection<String> values = data.values(); //value取得
final ArrayList<String> titleList = new ArrayList<>(values);
final ArrayList<String> titleRomList = new ArrayList<>(keySet);
if(data.size() != 0){
adapter = new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, titleList);
lv.setAdapter(adapter);
lv.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
Intent i = new Intent(getApplication(),PostActivity.class);
i.putExtra("title", titleList.get(position));
i.putExtra("titleRom", titleRomList.get(position));
startActivity(i);
}
}
);
}
}
};
}
再掲になりますが、createListenerで先程のMap(“ローマ字タイトル"、"タイトル")を受け取り、 ArrayListにそれぞれ格納、そしてAdapterにセットします。
Adapterに関しては、自分でクラスを作成することも可能ですが、今回はタイトルを表示するだけなので、標準で備わっているArrayAdapterを使わせてもらいます。
そして、ListViewのアイテム(アニメタイトル)がクリックされた時の処理を書いています。
今回はタイトルとローマ字タイトルを次のアクティビティ(投稿ページ)に渡してあげます。
以上でジャンル別のアニメ一覧を表示することができました。(๑•̀ㅂ•́)و✧グッ!

manifestに追記
今回、新しくSelectActivityを作成したのでmanifestのapplicationタグ内に
<activity android:name=".SelectActivity"/>
を追加しておいてください。
また、インターネット通信も行いますので、applicationタグの上に
<uses-permission android:name="android.permission.INTERNET" />
の追加もお忘れなく。
まとめ
今回は、ジャンル別でアニメ一覧を表示する処理を行いました!
サーバ内に格納されているファイルからデータを別スレッド(非同期)で読み込み、メインスレッドに渡してあげる。
たったこれだけのことですが、案外めんどくさかった。。。ε-(;-ω-`A) フゥ…
非同期処理については初心者の方にとっては少し難しかったかもしれませんが、1つ1つを理解せずとも、基本的にはテンプレが用意されているので
「この処理の時は、これを使うのね」
程度の解釈でいいと思います。
次回は、いよいよ投稿ページを作成していきます。
CSVファイルの読み込み+書き込み(PHP経由)を行います!
ではでは!