TEXTBOOK SECTION / AI LEARNING

検索ボタンでPokéAPIを呼び出す

Flutterアプリケーション開発概論の「Flutter API連携入門|ポケモン図鑑アプリの作り方」より、検索ボタンでPokéAPIを呼び出すを解説。生成AI、AI活用、DX、業務改善を実践しながら学べるオンライン教材です。

11Flutter API連携入門|ポケモン図鑑アプリの作り方Flutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発

OVERVIEW

この節で学べること

概要を表示する
項目内容
教材名Flutterアプリケーション開発概論
Flutter API連携入門|ポケモン図鑑アプリの作り方
検索ボタンでPokéAPIを呼び出す
カテゴリFlutter / iOS / Android / MacOS / Windows / 基礎から学ぶ / 開発 / アプリ開発
学習内容生成AI、AI活用、DX、業務改善を実践しながら理解するための教材です。

TABLE OF CONTENTS

目次

CONTENT

ここから

前の節では、TextField に入力したポケモン番号を取得できるようにしました。

今回は、検索ボタンを押したタイミングでPokéAPIを呼び出す処理を作ります。

http パッケージは、Dart / FlutterでHTTPリクエストを扱うためのパッケージです。TextField はユーザー入力を受け取るためのFlutter標準Widgetです。


この節で作ること

作業内容
番号を入力するTextField にポケモン番号を入れる
ボタンを押すElevatedButton で検索処理を実行する
APIを呼び出すhttp.get() でPokéAPIへアクセスする
名前を表示する取得した name を画面に出す

忙しい方はここだけ見て

検索ボタンでAPIを呼び出す中心コードはこれです。

Future<void> _searchPokemon() async {
  final int? pokemonId = int.tryParse(_pokemonIdController.text);

  if (pokemonId == null) {
    setState(() {
      _pokemonName = '数字を入力してください';
    });
    return;
  }

  final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
  final String name = data['name'] as String;

  setState(() {
    _pokemonName = name;
  });
}

ボタンには、この関数をつなぎます。

ElevatedButton(
  onPressed: _searchPokemon,
  child: const Text('検索する'),
)

処理の流れ

検索ボタンを押すと、次の順番で動きます。

1. TextFieldの入力値を読む
2. 数字に変換する
3. PokéAPIへアクセスする
4. JSONをMapに変換する
5. nameを取り出す
6. setStateで画面を更新する

つまり、ボタンはただの飾りではありません。

「入力 → 通信 → 表示」をつなぐスイッチです。


API取得関数

まず、ポケモン番号を受け取ってPokéAPIを呼び出す関数を用意します。

/**
 * 指定されたポケモン番号でPokéAPIからデータを取得する関数。
 *
 * 入力: pokemonId ポケモン番号
 * 出力: ポケモン情報を持つMap<String, dynamic>
 */
Future<Map<String, dynamic>> fetchPokemonData(int pokemonId) async {
  final Uri url = Uri.https(
    'pokeapi.co',
    '/api/v2/pokemon/$pokemonId',
  );

  final http.Response response = await http.get(url);

  if (response.statusCode != 200) {
    throw Exception('ポケモンデータを取得できませんでした');
  }

  final Map<String, dynamic> data =
      jsonDecode(response.body) as Map<String, dynamic>;

  return data;
}

ポイントはここです。

'/api/v2/pokemon/$pokemonId'

pokemonId25 なら、ピカチュウのデータを取得します。


検索ボタンの処理

次に、ボタンを押したときの処理を作ります。

/**
 * 入力された番号を使ってPokéAPIを呼び出し、結果を画面に表示する関数。
 *
 * 入力: なし
 * 出力: 検索結果または入力エラーメッセージを画面に表示する
 */
Future<void> _searchPokemon() async {
  final int? pokemonId = int.tryParse(_pokemonIdController.text);

  if (pokemonId == null) {
    setState(() {
      _pokemonName = '数字を入力してください';
    });
    return;
  }

  final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
  final String name = data['name'] as String;

  setState(() {
    _pokemonName = name;
  });
}

int.tryParse() を使うと、空欄や文字入力でもアプリが止まりにくくなります。

final int? pokemonId = int.tryParse(_pokemonIdController.text);

完成コード

lib/main.dart を次のようにします。

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

/**
 * アプリの起点になる関数。
 *
 * 入力: なし
 * 出力: MyAppを起動する
 */
void main() {
  runApp(const MyApp());
}

/**
 * 指定されたポケモン番号でPokéAPIからデータを取得する関数。
 *
 * 入力: pokemonId ポケモン番号
 * 出力: ポケモン情報を持つMap<String, dynamic>
 */
Future<Map<String, dynamic>> fetchPokemonData(int pokemonId) async {
  final Uri url = Uri.https(
    'pokeapi.co',
    '/api/v2/pokemon/$pokemonId',
  );

  final http.Response response = await http.get(url);

  if (response.statusCode != 200) {
    throw Exception('ポケモンデータを取得できませんでした');
  }

  final Map<String, dynamic> data =
      jsonDecode(response.body) as Map<String, dynamic>;

  return data;
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  /**
   * アプリ全体のUIを構築する関数。
   *
   * 入力: BuildContext
   * 出力: MaterialApp
   */
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ポケモン図鑑',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
        useMaterial3: true,
      ),
      home: const PokemonHomePage(),
    );
  }
}

class PokemonHomePage extends StatefulWidget {
  const PokemonHomePage({super.key});

  @override
  State<PokemonHomePage> createState() => _PokemonHomePageState();
}

class _PokemonHomePageState extends State<PokemonHomePage> {
  final TextEditingController _pokemonIdController = TextEditingController();

  String _pokemonName = 'ポケモン番号を入力してください';

  /**
   * TextEditingControllerを破棄する関数。
   *
   * 入力: なし
   * 出力: Controllerのリソースを解放する
   */
  @override
  void dispose() {
    _pokemonIdController.dispose();
    super.dispose();
  }

  /**
   * 入力された番号を使ってPokéAPIを呼び出し、結果を画面に表示する関数。
   *
   * 入力: なし
   * 出力: 検索結果または入力エラーメッセージを画面に表示する
   */
  Future<void> _searchPokemon() async {
    final int? pokemonId = int.tryParse(_pokemonIdController.text);

    if (pokemonId == null) {
      setState(() {
        _pokemonName = '数字を入力してください';
      });
      return;
    }

    final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
    final String name = data['name'] as String;

    setState(() {
      _pokemonName = name;
    });
  }

  /**
   * ポケモン検索画面を構築する関数。
   *
   * 入力: BuildContext
   * 出力: Scaffold
   */
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ポケモン図鑑'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: <Widget>[
            TextField(
              controller: _pokemonIdController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(
                labelText: 'ポケモン番号',
                hintText: '例: 25',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: _searchPokemon,
                child: const Text('検索する'),
              ),
            ),
            const SizedBox(height: 32),
            Text(
              _pokemonName,
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

動作確認

アプリを起動します。

flutter run

入力欄に 25 と入れて、検索します。

pikachu

1 と入れると、次のように表示されます。

bulbasaur

番号によって表示されるポケモンが変われば成功です。


よくあるエラー

エラー原因対応
何も表示されないonPressed が未設定onPressed: _searchPokemon を確認
いつも同じ結果になるURLが固定されている$pokemonId を使う
空欄で落ちるint.parse() を使っているint.tryParse() にする
jsonDecode が使えないimport不足import 'dart:convert'; を追加

まとめ

この節では、検索ボタンを押してPokéAPIを呼び出す処理を作りました。

大事なコードは、この2つです。

final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
onPressed: _searchPokemon

これで、入力欄とAPI通信と画面表示がつながりました。

次は、取得した画像URLを使って、ポケモン画像を表示していきます。

FAQ

よくある質問

検索ボタンでPokéAPIを呼び出すは医療関係者向けだけの内容ですか。
医療分野の例が含まれる場合もありますが、医療関係者だけに限定した内容ではありません。生成AI、AI活用、DX、業務改善、プロトタイプ開発など、一般的なAI学習の事例として読める内容です。
AI初心者でも読めますか。
はい。AIをこれから学ぶ方、数学が苦手な方、仕事でAIを使いたい方にも読み進めやすいように、教材の章と節の流れに沿って整理しています。
サムネイル画像は必ず表示されますか。
はい。教材にcoverUrlが設定されている場合はその画像を表示し、未設定の場合は代替サムネイル画像を表示します。
Flutterアプリケーション開発概論のほかの章も読めますか。
はい。教材トップから章立てを確認でき、前後の節へもページ下部のナビゲーションから移動できます。