Flutterで郵便番号検索アプリを作ってみよう

Flutterアプリケーションで郵便番号情報を取得するためには、APIを提供するサービスと通信する必要があります。ここでは、郵便番号APIを利用して情報を取得する一連の流れと、そのために必要なパッケージの使用方法を紹介します。

今回は、Flutterのパッケージ使用方法でご紹介したDioパッケージを使ってアプリを作成します。

目次

プロジェクトの作成

STEP
zip_cloud名でプロジェクトの作成

郵便番号APIはZip Cloudを利用しようと思いますので、zip_cloud名でプロジェクトを作成します。

main.dartの作成

STEP

デフォルトアプリの全削除

デフォルトのアプリを全削除して、main.dartを作成し直します。

STEP

新しいmain.dartの記述

main.dartを下記の様に記述します。

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TopPage(),
    );
  }
}
  • ここまでは、「Flutterで初めてのプロジェクト」に記載していますので、問題は無いと思います。
  • 唯一違うのはhomeでScaffoldWidgetでは無く、TopPageクラスを指定していることです。
  • 尚、このままでは、エラーになりますので、TopPageクラスを作成します。
STEP

TopPageクラスの作成

main.dartにTopPageクラスを追加します。stfと入力すれば、コード補完が出ますので、ほとんどの部分は自動生成されるはずです。

class TopPage extends StatefulWidget {
  const TopPage({Key? key}) : super(key: key);

  @override
  State<TopPage> createState() => _TopPageState();
}

class _TopPageState extends State<TopPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('郵便番号検索'),
      ),
    );
  }
}

簡単にコードの説明を下記に記載しておきます。

TopPageクラス
  • 1行目:StatefulWidgetを継承しています。これは、ウィジェットがアプリケーションのライフサイクル中に変更され得る状態を持っていることを意味します。
  • 2行目:コンストラクタでは、オプショナルなkeyを受け取り、親クラスのコンストラクタに渡しています。keyはウィジェットの一意性を保証するために使用されます。
  • 5行目:ウィジェットの状態を管理する_TopPageStateクラスのインスタンスを生成します。
_TopPageStateクラス
  • 8〜20行目:Stateクラスを継承し、TopPageウィジェットの状態を保持します。
  • 10行目:buildメソッドは、ウィジェットのビジュアル表現を構築するためにフレームワークによって呼び出されます。
  • 11行目:Scaffoldウィジェットは、マテリアルデザインのレイアウト構造を提供します。これにより、アプリバーとそのタイトル、本文などの主要なUI要素を配置できます。
  • 12〜14行目:AppBarウィジェットは、アプリケーションの上部に表示されるバーを提供し、titleプロパティにはTextウィジェットを使用して「郵便番号検索」というタイトルを表示しています。

Dioのインストール

この記事では、Dioを使用して郵便番号情報を取得する方法を紹介します。

STEP

Dioとは

Dioは高機能なHTTPクライアントライブラリであり、エラーハンドリング、インターセプタ、フォームデータの送信など、標準のhttpパッケージよりも豊富な機能を提供します。

STEP

Dioの検索

  • pub.dev で dio を検索します。
  • Installingタブを調べるとコマンドでのインストール方法が出ていますので、AndroidStudioのターミナルから、下記のコマンドを実行します。
Dart
flutter pub add dio

これでDioパッケージがインストールできました。

ZipCodeクラスの作成

STEP

ZipCodeクラスを作成

郵便番号から住所を検索するためのコードを記述します。

class ZipCode {
  static Future<String> searchAddressFromZipCode(String? code) async {
    Dio dio = Dio();
    String mainUrl = 'https://zipcloud.ibsnet.co.jp/api/search';
    Map<String, dynamic> queryParameters = {'zipcode': code};
    try {
      var res = await dio.get(
        mainUrl,
        queryParameters: queryParameters,
      );
      if (jsonDecode(res.data)['results'] == null) {
        return jsonDecode(res.data)['message'];
      } else {
        var results = jsonDecode(res.data)['results'][0];
        var address = results['address1'] + results['address2'] +
            results['address3'];
        return address;
      }
    } catch (e) {
      print("エラーです");
      return "郵便番号が正しくありません";
    }
  }
}
STEP

メソッドの説明

zipCodeクラスの中にsearchAddressFromZipCodeというメソッドを作成します。このメソッドの説明を下記に記述しておきます。

  • 3行目:Dioインスタンスを作成しています。これはHTTPクライアントで、ネットワークリクエストを行うために使用されます。尚、Dioを使用するためにこのファイルの先頭に import文を書く必要があります。
  • 4行目:mainUrl変数にAPIのURLを設定しています。
  • 5行目:queryParametersマップにリクエストパラメータとして郵便番号(’zipcode’:code)を設定しています。
  • 7行目:dio.getメソッドを使用して、APIに対してGETリクエストを非同期で行います。リクエストはqueryParametersを用いてパラメータ化されます。
  • 11行目:レスポンスが返ってきたら、jsonDecodeを使用してレスポンスボディをデコードします。jsonDecodeはJSON形式の文字列をDartのオブジェクトに変換する関数です。
  • 16行目:デコードされたデータからresultsキーを確認し、値がnullであるかどうかをチェックします。nullの場合、APIからのmessageを返します。
  • 15行目:resultsがnullでない場合、最初の結果(results[0])から住所の部分(address1、address2、adddress3)を取り出し、これらを連結して完全な住所として返します。
  • 6~11行目:try-catchブロックを使用しており、何らかのエラー(例えばネットワークエラーやデコードエラー)が発生した場合は、エラーメッセージを表示し、「郵便番号が正しくありません」という文字列を返します。

_TopPageStateクラスの修正

STEP

修正コード

先ほど作成した_TopPageStateクラスは、UI画面を表示するためだけに作成したので、実際にフォームからのデータを受け取ってAPIに渡し、APIからレスポンスを受け取ることは出来ません。

そこで、下記の様に修正します。

class _TopPageState extends State<TopPage> {
  final TextEditingController _controller = TextEditingController();
  String _addressResult = '';

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Future<void> _search() async {
    final zipCode = _controller.text;
    final address = await ZipCode.searchAddressFromZipCode(zipCode);
    setState(() {
      _addressResult = address;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('郵便番号検索'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('郵便番号を入力してください'),
            const SizedBox(height: 16),
            SizedBox(
              width: 200,
              child: TextField(
                controller: _controller,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: '郵便番号',
                ),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _search,
              child: const Text('検索'),
            ),
            const SizedBox(height: 16),
            Text('検索結果: $_addressResult'),
          ],
        ),
      ),
    );
  }
}
STEP

コードの説明

下記にコードの説明を記述しておきます。

  • 2行目:ユーザーが入力した郵便番号を追跡するためのテキスト編集コントローラーです。このコントローラーはTextFieldウィジェットに結びついており、ユーザーの入力を取得できます。
  • 3行目:検索結果を保存するための変数で、初期状態は空文字列です。
  • 5〜9行目:ウィジェットのライフサイクルが終了し、ステートが破棄される時にコントローラーをクリーンアップするためにオーバーライドされています。
  • 11〜17行目:ユーザーが入力した郵便番号を_Controllerから取得し、ZipCodeクラスのsearchAddressFromZipCodeメソッドを非同期で呼び出して住所を検索します。結果は_addressResultに格納され、setStateを呼び出すことでUIが更新されます。
  • 20行目:Widget buildは、 UIの構造を定義するメソッドです。Scaffoldウィジェットを使用して、アプリバーとボディコンテンツを持つ基本的なページレイアウトを作成しています。ボディ内では、ユーザーが郵便番号を入力するためのTextFieldと、検索を実行するためのElevatedButton、そして検索結果を表示するTextウィジェットが垂直に配置されています。
STEP

検索結果

詳しい説明は飛ばして、まず動かすことをメインにこの記事を書いてみました。
まずは動かしてFlutterが動く実感を味わってください。

全コードは下記に記載しておきます。

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: TopPage(),
    );
  }
}

class TopPage extends StatefulWidget {
  const TopPage({Key? key}) : super(key: key);

  @override
  State<TopPage> createState() => _TopPageState();
}

class _TopPageState extends State<TopPage> {
  final TextEditingController _controller = TextEditingController();
  String _addressResult = '';

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Future<void> _search() async {
    final zipCode = _controller.text;
    final address = await ZipCode.searchAddressFromZipCode(zipCode);
    setState(() {
      _addressResult = address;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('郵便番号検索'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('郵便番号を入力してください'),
            const SizedBox(height: 16),
            SizedBox(
              width: 200,
              child: TextField(
                controller: _controller,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: '郵便番号',
                ),
              ),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: _search,
              child: const Text('検索'),
            ),
            const SizedBox(height: 16),
            Text('検索結果: $_addressResult'),
          ],
        ),
      ),
    );
  }
}

class ZipCode {
  static Future<String> searchAddressFromZipCode(String? code) async {
    Dio dio = Dio();
    String mainUrl = 'https://zipcloud.ibsnet.co.jp/api/search';
    Map<String, dynamic> queryParameters = {'zipcode': code};
    try {
      var res = await dio.get(
        mainUrl,
        queryParameters: queryParameters,
      );
      if (jsonDecode(res.data)['results'] == null) {
        return jsonDecode(res.data)['message'];
      } else {
        var results = jsonDecode(res.data)['results'][0];
        var address = results['address1'] + results['address2'] +
            results['address3'];
        return address;
      }
    } catch (e) {
      print("エラーです");
      return "郵便番号が正しくありません";
    }
  }
}

追伸

今回のアプリの詳細な考え方とAPI通信する上での基本的な情報を「Flutterで郵便番号検索アプリを作ってみよう(解説編)」でご紹介していますので、よろしければそちらのページもご覧下さい。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次