Flutterでポケモンバトルを実装する(Step3: リファクタリング)

## はじめに
前回までの記事の続きです。ページ単位での管理、リファクタリングを実施しました。

https://qiita.com/koji0705/items/eee95ca5666facbdcf8c

## 成果物
![ポケモン図鑑.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/9358e8b3-f0a3-511e-8f23-582729c52eb2.gif)


## ソースコード
```shell:ディレクトリ構成
~/develop/pokemon_battle_app$ tree lib 
lib
├── data
│   └── sample_data.dart
├── main.dart
├── models
│   └── pokemon.dart
├── page
│   ├── battle.dart
│   ├── home.dart
│   └── info.dart
└── router.dart

4 directories, 7 files
```

```dart:main.go
import 'package:flutter/material.dart';
import 'router.dart';

void main() {
  runApp(PokemonApp());
}

class PokemonApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Pokemon App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routerConfig: router,
    );
  }
}
```

:::note info
ここではrouterを呼び出しているだけです。
:::


```dart:lib/router.dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

import 'page/battle.dart';
import 'page/home.dart';
import 'page/info.dart';

final GoRouter router = GoRouter(
  routes: <RouteBase>[
    ShellRoute(
      builder: (context, state, child) {
        return ScaffoldWithNavBar(child: child);
      },
      routes: <RouteBase>[
        GoRoute(
          path: '/',
          builder: (BuildContext context, GoRouterState state) {
            return HomePage();
          },
        ),
        GoRoute(
          path: '/search',
          builder: (BuildContext context, GoRouterState state) {
            return BattlePage();
          },
        ),
        GoRoute(
          path: '/info',
          builder: (BuildContext context, GoRouterState state) {
            return InfoPage();
          },
        ),
      ],
    ),
  ],
);

class ScaffoldWithNavBar extends StatefulWidget {
  final Widget child;

  ScaffoldWithNavBar({required this.child});

  @override
  _ScaffoldWithNavBarState createState() => _ScaffoldWithNavBarState();
}

class _ScaffoldWithNavBarState extends State<ScaffoldWithNavBar> {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
      switch (index) {
        case 0:
          context.go('/');
          break;
        case 1:
          context.go('/search?query=example');
          break;
        case 2:
          context.go('/info');
          break;
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter App'),
      ),
      body: widget.child,
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Info',
          ),
        ],
        currentIndex: _selectedIndex,
        selectedItemColor: Colors.amber[800],
        onTap: _onItemTapped,
      ),
    );
  }
}
```

```dart:lib/page/battle.dart
import 'package:flutter/material.dart';
import '../models/pokemon.dart';
import '../data/sample_data.dart';

class BattlePage extends StatelessWidget {
  final Pokemon pokemon1 = pikachu;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pokemon Battle'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            _buildPokemonInfo(pokemon1),
          ],
        ),
      ),
    );
  }

  Widget _buildPokemonInfo(Pokemon pokemon) {
    return Column(
      children: [
        Text(
          pokemon.name,
          style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
        ),
        Image.network(pokemon.imageUrl, height: 100, width: 100),
        Column(
          children: pokemon.moves
              .map((move) => Text('${move.name} (${move.power})'))
              .toList(),
        ),
      ],
    );
  }
}
```

```dart:lib/models/pokemon.dart
class Pokemon {
  final String name;
  final String imageUrl;
  final List<Move> moves;

  Pokemon({required this.name, required this.imageUrl, required this.moves});
}

class Move {
  final String name;
  final int power;

  Move({required this.name, required this.power});
}
```

```dart:lib/data/sample_data.dart
import '../models/pokemon.dart';

final pikachu = Pokemon(
  name: 'ピカチュウ',
  imageUrl:
      'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png',
  moves: [
    Move(name: 'Thunderbolt', power: 90),
    Move(name: 'Quick Attack', power: 40),
    Move(name: 'Iron Tail', power: 100),
    Move(name: 'Electro Ball', power: 80),
  ],
);

final charmander = Pokemon(
  name: 'ヒトカゲ',
  imageUrl:
      'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png',
  moves: [
    Move(name: 'Flamethrower', power: 90),
    Move(name: 'Scratch', power: 40),
    Move(name: 'Dragon Breath', power: 60),
    Move(name: 'Fire Spin', power: 35),
  ],
);
```