CONTENT
ここから
前の節では、検索ボタンを押してPokéAPIを呼び出し、ポケモン名を表示しました。
今回は、APIから取得した画像URLを使って、画面にポケモン画像を表示します。
Flutterでは、インターネット上の画像を表示するときに Image.network() を使います。
この節で作ること
| 作業 | 内容 |
|---|---|
| 画像URLを取り出す | sprites の中から画像URLを取得する |
| 画像用の変数を作る | _pokemonImageUrl に保存する |
| Image.networkを使う | API画像を画面に表示する |
| 検索結果を更新する | 名前と画像を同時に表示する |
忙しい方はここだけ見て
PokéAPIの画像URLは、次のように取り出します。
final Map<String, dynamic> sprites =
data['sprites'] as Map<String, dynamic>;
final String imageUrl = sprites['front_default'] as String;
画面には、次のように表示します。
Image.network(
_pokemonImageUrl,
width: 160,
height: 160,
fit: BoxFit.contain,
)
画像URLの場所
PokéAPIのポケモン画像は、JSONの中で次の場所にあります。
sprites
└─ front_default
つまり、Mapから取り出すときは2段階になります。
final Map<String, dynamic> sprites =
data['sprites'] as Map<String, dynamic>;
final String imageUrl = sprites['front_default'] as String;
name は浅い場所にありました。
final String name = data['name'] as String;
画像URLは、少し奥にあります。
画像用の状態変数を追加する
_PokemonHomePageState の中に、画像URLを保存する変数を追加します。
String _pokemonName = 'ポケモン番号を入力してください';
String? _pokemonImageUrl;
画像URLは、まだ取得していない状態があります。
そのため、String? にしています。
検索処理を変更する
検索ボタンを押したとき、名前だけでなく画像URLも保存します。
/**
* 入力された番号を使ってPokéAPIを呼び出し、名前と画像を画面に表示する関数。
*
* 入力: なし
* 出力: _pokemonName と _pokemonImageUrl を更新して画面を再描画する
*/
Future<void> _searchPokemon() async {
final int? pokemonId = int.tryParse(_pokemonIdController.text);
if (pokemonId == null) {
setState(() {
_pokemonName = '数字を入力してください';
_pokemonImageUrl = null;
});
return;
}
final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
final String name = data['name'] as String;
final Map<String, dynamic> sprites =
data['sprites'] as Map<String, dynamic>;
final String imageUrl = sprites['front_default'] as String;
setState(() {
_pokemonName = name;
_pokemonImageUrl = imageUrl;
});
}
ポイントはここです。
_pokemonImageUrl = imageUrl;
これで、検索結果に応じて画像が変わります。
Image.networkを画面に追加する
画像URLがあるときだけ、Image.network() を表示します。
if (_pokemonImageUrl != null)
Image.network(
_pokemonImageUrl!,
width: 160,
height: 160,
fit: BoxFit.contain,
),
_pokemonImageUrl! の ! は、「ここではnullではない」とDartに伝える記号です。
今回は、直前に if (_pokemonImageUrl != null) で確認しているため使えます。
完成コード
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 = 'ポケモン番号を入力してください';
String? _pokemonImageUrl;
/**
* TextEditingControllerを破棄する関数。
*
* 入力: なし
* 出力: Controllerのリソースを解放する
*/
@override
void dispose() {
_pokemonIdController.dispose();
super.dispose();
}
/**
* 入力された番号を使ってPokéAPIを呼び出し、名前と画像を画面に表示する関数。
*
* 入力: なし
* 出力: _pokemonName と _pokemonImageUrl を更新して画面を再描画する
*/
Future<void> _searchPokemon() async {
final int? pokemonId = int.tryParse(_pokemonIdController.text);
if (pokemonId == null) {
setState(() {
_pokemonName = '数字を入力してください';
_pokemonImageUrl = null;
});
return;
}
final Map<String, dynamic> data = await fetchPokemonData(pokemonId);
final String name = data['name'] as String;
final Map<String, dynamic> sprites =
data['sprites'] as Map<String, dynamic>;
final String imageUrl = sprites['front_default'] as String;
setState(() {
_pokemonName = name;
_pokemonImageUrl = imageUrl;
});
}
/**
* ポケモン検索画面を構築する関数。
*
* 入力: 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),
if (_pokemonImageUrl != null)
Image.network(
_pokemonImageUrl!,
width: 160,
height: 160,
fit: BoxFit.contain,
),
const SizedBox(height: 16),
Text(
_pokemonName,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}
動作確認
アプリを起動します。
flutter run
25 と入力して検索します。
pikachu
ピカチュウの画像と名前が表示されれば成功です。
次に 1 を入力します。
bulbasaur
画像もフシギダネに変われば、API画像の表示ができています。
よくあるエラー
| エラー | 原因 | 対応 |
|---|---|---|
| 画像が出ない | URLを取得できていない | sprites['front_default'] を確認 |
| nullエラーになる | 画像URLがnull | String? と if で確認する |
| 名前だけ表示される | Image.network を置いていない | build内に追加する |
| 画像が大きすぎる | サイズ指定なし | width と height を指定する |
まとめ
この節では、Image.network() を使ってAPI画像を表示しました。
大事な流れは、次の3つです。
final Map<String, dynamic> sprites =
data['sprites'] as Map<String, dynamic>;
final String imageUrl = sprites['front_default'] as String;
Image.network(_pokemonImageUrl!)
これで、ポケモン図鑑アプリに画像が入りました。
次は、名前・画像・番号をカードUIとして見やすく整えていきます。