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

2021-11-14

主
こんにちは、主です!

只今、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)のファイル名を取得します。

主
SF/ファンタジーボタンを選択したら、変数の中に"sf"が代入されます。

変数 fileにファイル名を格納できたら

getMyData.execute(file);

として、ファイル名を後ほど作成するGetMyDataというクラスに渡してあげます。

 

サーバーにCSVファイルを作成・保存する

GetMyDataクラスの前にまずはCSVファイルをサーバ内に用意してあげましょう。

ジャンル別にファイルを作成し、それぞれにジャンルに適合するアニメタイトルを記述します。

 

ちなみに、僕はさくらサーバーを使ってます。<(*_ _)>

 

↓のようにcsvファイルを10個ほど作成します。

中身は↓こんな感じ。

sf.csv

 

ローマ字タイトルと通常文字のタイトルを書き込みます。

csvなので、文字は","で区切りましょう。

 

ローマ字タイトルは、次回取り扱う投稿ファイル名になります。

 

お好きに決めていただいて結構です。

 

非同期処理でアニメ一覧を取得

先ほどからお話しに出ているGetMyDataについてです。

こちらには非同期的にサーバからファイル情報を取ってくる処理を記述します。(SelectActivityと同じ階層に作成)

 

非同期処理とは?

あるタスクを実行している最中に、その処理を止めることなく別のタスクを実行できる方式

Rworksさんより拝借

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経由)を行います!

 

ではでは!