5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

株式会社やどかり & 株式会社ネッコスAdvent Calendar 2021

Day 15

自作APIを使ってKotlinでポケモン図鑑を作成【Retrofit2】【RecyclerView】

Last updated at Posted at 2021-12-14

株式会社やどかりのカイノと申します。
12/1に担当して、2回目で12/15のアドベントカレンダーを担当します。

やどかり&ネッコス Advent Calendar 2021の15日目を担当させていただきます。
明日はイグっちゃんです:fist:

はじめに

前回かいた記事ではLaravelとポケモンAPIを使って図鑑を作りました。
http://13.115.177.152/
上記にAWS上でサーバアップもしました。

今回はそれを使ってKotlinでAndroidスマホ画面に図鑑を表示するまでをやっていこうと思います。

使用言語

Laravel 6.20
kotlin

出来上がりの動画

今回の内容でこのようになりました。
898匹分の図鑑をAndroid端末で確認できています。
タイトルなし.gif

今回やったこと

1.Laravelアプリ側でAPI作成
2.RetrofitでAPIと繋ぎ込み
3.RecyclerViewで1匹ずつ画面に表示

Laravelアプリ側でAPI作成

まずルーティングの設定

routes/web.php
// API用ルーティング
Route::get('/get_pokemons', 'PokemonController@get_pokemons');

コントローラで処理記述
DBには前回の記事で行った内容で保存できているので取ってくるだけです。

app/Http/Controllers/PokemonController.php
<?php
namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Pokemon;

class PokemonController extends Controller
{
    public function get_pokemons ()
    {
        $pokemons = Pokemon::all();
        $pokemons = (array)$pokemons;
        $pokemons = array_values($pokemons)[0];

        echo json_encode($pokemons);
        exit();
    }
}

解説1 : Pokemon:all()でテーブルから全てのPokemonを取得

解説2 : 配列に変換しarray_valuesで採番し直す

解説3 : json_encode()でJSONに変換して出力する

RetrofitでAPIと繋ぎ込み

ここからはKotlin側の実装です。

1.gradleとManifest

build.gradleに以下のライブラリを追加

:build.gradle
    // retrofit
    implementation 'com.squareup.retrofit2:retrofit:2.7.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.0'

今回ネットワークを利用した通信なのでAndroidManifestに以下を記入

AndroidManifest.xml
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

2.interface

今回は HTTP の GET メソッドを使うため@GET をつけました。

API_Interface
interface API_Interface {
    @get:GET("/get_pokemons")
    val pokemons: Call<List<Pokemon>>
}

3.Model

APIから取り出したいデータを格納するデータクラス
まーこんなには今回使わないんですけどLaravelアプリ側で送っている値を一旦全部かきました。

Pokemon.kt
class Pokemon {
    var id: Int? = null
    var p_id: Int? = null
    var jp_name: String? = null
    var en_name: String? = null
    var type1: String? = null
    var type2: String? = null
    var ability1: String? = null
    var ability2: String? = null
    var hidden_ability: String? = null
    var hp: Int? = null
    var attack: Int? = null
    var defense: Int? = null
    var special_attack: Int? = null
    var special_defense: Int? = null
    var speed: Int? = null
    var total_stats: Int? = null
    var front_default: String? = null
    var back_default: String? = null
    var dream_world_front_default: String? = null
    var home_front_default: String? = null
    var official_artwork_front_default: String? = null
    var height: Int? = null
    var weight: Int? = null
}

4.呼び出し

今回はMainActivityでretrofitを使ってリクエストします。

MainActivity.kt
        val retro = Retrofit.Builder()
            .baseUrl("http://13.115.177.152/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val service = retro.create(API_Interface::class.java)
        val pokemons: Call<List<Pokemon>> = service.pokemons

ここまででRetrofitで呼び出しは完了

RecyclerViewで1匹ずつ画面に表示

1.gradle

RecyclerViewを使用するためのライブラリを追加

build.gradle
    // recyclerview
    implementation 'androidx.recyclerview:recyclerview:1.2.1'
    implementation "androidx.recyclerview:recyclerview-selection:1.1.0"

2. 1行分のレイアウトを作成

list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#000000"
    android:padding="4dp">

  <ImageView
      android:id="@+id/front_default"
      android:layout_width="60dp"
      android:layout_height="match_parent" />

  <LinearLayout
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:orientation="vertical"
      android:layout_marginStart="16dp">

    <TextView
        android:id="@+id/p_id"
        android:textColor="#64CE6C"
        android:textSize="14sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="4dp" />

    <TextView
        android:id="@+id/type1"
        android:textColor="#64CE6C"
        android:textSize="14sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/type2"
        android:textColor="#64CE6C"
        android:textSize="14sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/jp_name"
        android:textColor="#64CE6C"
        android:textSize="14sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
  </LinearLayout>
</LinearLayout>

3. 1行分のデータモデルを設定

PokemonRow.kt
data class PokemonRow(
    var p_id: Int?,
    var jp_name: String?,
    var type1: String?,
    var type2: String?,
    var front_default: String?
)

4.ViewHolderとAdapterの定義

CustomAdapter.kt
class CustomAdapter(private val pokemonRowList: ArrayList<PokemonRow>): RecyclerView.Adapter<CustomAdapter.ViewHolder>() {

    // Viewの初期化
    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val p_id: TextView
        val jp_name: TextView
        val type1: TextView
        val type2: TextView
        val front_default: ImageView

        init {
            p_id = view.findViewById(R.id.p_id)
            jp_name = view.findViewById(R.id.jp_name)
            type1 = view.findViewById(R.id.type1)
            type2 = view.findViewById(R.id.type2)
            front_default = view.findViewById(R.id.front_default)
        }
    }

    // レイアウトの設定
    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        val view = LayoutInflater.from(viewGroup.context).inflate(R.layout.list_item, viewGroup, false)
        return ViewHolder(view)
    }

    // Viewの設定
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val pokemon = pokemonRowList[position]

        viewHolder.p_id.text = "No."+pokemon.p_id.toString()
        viewHolder.jp_name.text = pokemon.jp_name
        viewHolder.type1.text = pokemon.type1
        viewHolder.type2.text = pokemon.type2
        var front_default = pokemon.front_default
        Picasso.get()
            .load(front_default)
            .resize(300, 300) //表示サイズ指定
            .centerCrop() //resizeで指定した範囲になるよう中央から切り出し
            .into(viewHolder.front_default) //imageViewに流し込み
    }
}

CustomAdapter.kt内で、ViewHolderは、1行分のレイアウト情報を定義する。
onCreateViewHolderメソッドで設定したレイアウトに対応するViewを初期化。

onBindViewHolderメソッドで1行ごとにこのメソッドは呼び各Viewの詳細設定を行います。

5.Picassoでweb上の画像を表示

Laravelアプリから送られてくるfront_defaultなどの画像用のパラメータは全てURLで定義されているため、これを画像に変換できるPicassoをAdapter内のonBindViewHolderでレイアウト1行ごとに実行。

build.gradle
    //for picasso
    implementation 'com.squareup.picasso:picasso:2.71828'
CustomAdapter.kt
        Picasso.get()
            .load(front_default)
            .resize(300, 300) //表示サイズ指定
            .centerCrop() //resizeで指定した範囲になるよう中央から切り出し
            .into(viewHolder.front_default) //imageViewに流し込み

6.Activityで呼び出し

MainActivity.kt

        pokemons.enqueue(object : Callback<List<Pokemon>> {
            override fun onResponse(call: Call<List<Pokemon>>, response: Response<List<Pokemon>>) {

                if (response.isSuccessful) {
                    response.body()?.let {
                        for (item in it) {
                            val data: Pokemon = Pokemon().also {
                                it.id = item.id
                                it.p_id = item.p_id
                                it.jp_name = item.jp_name
                                it.en_name = item.en_name
                                it.type1 = item.type1
                                it.type2 = item.type2
                                it.ability1 = item.ability1
                                it.ability2 = item.ability2
                                it.hidden_ability = item.hidden_ability
                                it.hp = item.hp
                                it.attack = item.attack
                                it.defense = it.defense
                                it.special_attack = item.special_attack
                                it.special_defense = item.special_defense
                                it.speed = item.speed
                                it.front_default = item.front_default
                                it.back_default = item.back_default
                                it.dream_world_front_default = item.dream_world_front_default
                                it.home_front_default = item.home_front_default
                                it.official_artwork_front_default = item.official_artwork_front_default
                                it.height = item.height
                                it.weight = item.weight
                            }
                            Log.d("kkk","it.pid:"+data.p_id)
                            Log.d("kkk","it.jp_name:"+data.jp_name)
                            Log.d("kkk","it.front_default:"+data.front_default)
                            pokemonList.add(data)
                            val p = PokemonRow(data.p_id, data.jp_name, data.type1, data.type2, data.front_default)
                            mPokemonList.add(p)
                        }

                        Log.d("kkk", "mPokemonList:"+mPokemonList)

                        // RecyclerViewの取得
                        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)

                        // LayoutManagerの設定
                        recyclerView.layoutManager = GridLayoutManager(this@MainActivity, 2)

                        // CustomAdapterの生成と設定
                        mAdapter = CustomAdapter(mPokemonList)
                        recyclerView.adapter = mAdapter
                    }
                } else {
                   // 例外処理 今回は考慮しない
                }
            }
            override fun onFailure(call: Call<List<Pokemon>>, t: Throwable) {
                Log.d("kkk","onFailure:"+t.message)
            }
        })

APIからレスポンスで受け取った値を各パラメータに格納し
GridLayoutManagerで今回は2列表示させる。

結果このようになります

Screenshot_2021-12-14-23-30-29-03_7c9c8903b4ce7bc38b72aa933cfc9259.png

どうでもいいけどドラパルトがドラガパルドになってました。

終わり

RetrofitもRecyclerViewも今年は結構お世話になったので復習の良い機会になりました。
Android関連はのことはまだまだ未熟で今後も勉強続けていきます!

年末年始の暇な時にまた色々と触ってみようと思います。

近頃寒くなってきましたね。みなさん風邪などひかないように!
体調管理しっかりして元気に新年を迎えていきましょーや。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?