採用はこちら!

Shinonome Tech Blog

株式会社Shinonomeの技術ブログ
4 min read

[Flutter]RetrofitでポケモンAPIを叩く

案件で使われていたRetrofitというパッケージでポケモンの名前を取得=>表示してみました。

はじめに

こんにちは!mobileコースのko_chaです。

先日PokeAPIなるものを発見したのでこれを叩いてみよう、という記事です。ポケモンに関する情報をいっぱい返してくれるAPIだそうです(僕はポケモンを一作もやったことがなくデータを見てもよく分かりませんが、、)

公式ドキュメント

まずは公式ドキュメントから。

Documentation - PokéAPI

ポケモン、わからないよ...

Type=タイプってことだけは分かりましたので、Typeを指定して返ってくるポケモンたちの名前を表示させてみたいと思います。

httpを使っても良いですが、せっかくなのでretrofitパッケージを使ってみます。

これまた公式ドキュメント。

retrofit | Dart Package
retrofit.dart is an dio client generator using source_gen and inspired by Chopper and Retrofit.

loggerは使いませんでしたので削除。しかし初知りでしたがすごいですねこれ...

そろそろprintを使ったデバッグから卒業したいところ。AndroidStudioのデバッグもうまく使えていませんしね、

この通りに追加しまして

dependencies:
  retrofit:
  dio:
  logger: //僕は削除しました。

dev_dependencies:
  retrofit_generator:
  build_runner:
  json_serializable:

Define and Generate your APIの部分を改造していきます。

import 'package:json_annotation/json_annotation.dart';
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';

//build_runner生成される.gファイルの名前。'これが書かれているファイル名.g.dart'
part 'example.g.dart';

//urlの共通部分
@RestApi(baseUrl: "https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/")
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

  @GET("/tasks")
  Future<List<Task>> getTasks();
}

//モデル。返ってくるjsonデータをFlutterで使いやすい型に変換する。これを簡単にやってくれるのがJsonSerializable
@JsonSerializable()
class Task {
  String? id;
  String? name;
  String? avatar;
  String? createdAt;

  Task({this.id, this.name, this.avatar, this.createdAt});

  factory Task.fromJson(Map<String, dynamic> json) => _$TaskFromJson(json);
  Map<String, dynamic> toJson() => _$TaskToJson(this);
}
Define and Generate your API

RestClient部分を作る

まずは通信の核となる部分。

エラーが出ていても大丈夫です。下記をターミナルで実行することで、いい感じに必要なファイルを作ってくれます。

flutter pub run build_runner build --delete-conflicting-outputs

baseUrlを書き換え、今回はタイプを指定したいので、url末尾を変えられるようにしました。({type}の部分)これも公式の下の方にのっていますのでご安心ください。

@RestApi(baseUrl: "https://pokeapi.co/api/v2")
abstract class RestClient {
  factory RestClient(Dio dio, {String baseUrl}) = _RestClient;
  @GET("/type/{type}")
  Future<PokeType> getNamesWithType(@Path("type") String type);
}

modelを作る

Typeの中にTypePokemonのリスト"pokemon"があって、その中にintの"slot", Pokemonの"pokemon"、またこの"pokemon"の中にname,urlが入っている状態です。(複雑ですね...)

まずは一番下層のPokemon型を作成。

import 'package:json_annotation/json_annotation.dart';

part 'pokemon.g.dart';

@JsonSerializable(fieldRename: FieldRename.snake)
class Pokemon {
  String? name;
  String? url;

  Pokemon({this.name, this.url});

  factory Pokemon.fromJson(Map<String, dynamic> json) =>
      _$PokemonFromJson(json);

  Map<String, dynamic> toJson() => _$PokemonToJson(this);
}

次にTypePokemon

import 'package:json_annotation/json_annotation.dart';
import 'package:poke_api/model/pokemon.dart';

part 'type_pokemon.g.dart';

@JsonSerializable(fieldRename: FieldRename.snake)
class TypePokemon {
  int? slot;
  Pokemon? pokemon;
#省略
}

最後にType型のモデル。始め"Type"としていたのですが、Type classはもう既に存在してるよと怒られてしまったので、PokeTypeに変更です。

import 'package:json_annotation/json_annotation.dart';
import 'package:poke_api/model/type_pokemon.dart';

part 'type.g.dart';

@JsonSerializable(fieldRename: FieldRename.snake)
class PokeType {
  int? id;
  String? name;
  List<TypePokemon>? pokemon;
#省略
}

Viewを作る

以下の文を書き換えます。

import 'package:logger/logger.dart';
import 'package:retrofit_example/example.dart';
import 'package:dio/dio.dart';

final logger = Logger();
void main(List<String> args) {
  final dio = Dio(); // Provide a dio instance
  dio.options.headers["Demo-Header"] = "demo header"; // config your dio headers globally
  final client = RestClient(dio);

  client.getTasks().then((it) => logger.i(it));
}

まずはmainの中身を関数化

Future<PokeType> fetchData() async {
    var type = "ground"; //じめんタイプを指定。ここを変えれば他のタイプに。
    final dio = Dio();
    final client = RestClient(dio);
    dio.options.headers["Demo-Header"] = "demo header";//なくてもOKでした。
    final data = await client.getNamesWithType(type);
    return data;
  }

お次は簡単にViewを書いていきます

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("PokeApi"),
      ),
      body: FutureBuilder<PokeType>(
        future: fetchData(),//
        builder: (BuildContext context, AsyncSnapshot<PokeType> snapshot) {
          return ListView.builder(
          //データ有ならListViewの長さを返ってきたデータの中のpokemon分の長さに。
          //違ったら0に。
            itemCount: snapshot.hasData ? snapshot.data!.pokemon!.length : 0,
            itemBuilder: (context, index) {
              final pokeData = snapshot.data!.pokemon![index].pokemon!;
              return snapshot.hasData
                  ? ListTile(
                      title: Text(pokeData.name!),
                    )
                  : const Center(
                      child: CircularProgressIndicator(),
                    );
            },
          );
        },
      ),
    );
  }

Sandshrew (Pokémon) - Bulbapedia, the community-driven Pokémon encyclopedia

よしよし。

最後に

作ってみると行数の少ないコードですが、意外と苦戦しました。知らないものを理解するのって難しいですね、、

アドベントカレンダー明日はKaoru Mataraiさんです!!!お楽しみに!