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'
pokemonId が 25 なら、ピカチュウのデータを取得します。
検索ボタンの処理
次に、ボタンを押したときの処理を作ります。
/**
* 入力された番号を使って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を使って、ポケモン画像を表示していきます。