🦓

[Flutter]httpを使ってAPIからデータを取得する

2023/12/17に公開

はじめに

ランダムなユーザーを生成してくれAPIサービスから、Flutterのhttpライブラリを使ってAPIからデータを取得する流れをまとめてみました。

https://randomuser.me/
https://pub.dev/packages/http

環境

Flutter 3.10.1
Flutterをインストールされた上で進めていきます。

tl;dr

  1. Flutter アプリを作成する
  2. httpを導入する
  3. APIリクエストを作成する
  4. データクラスを作成する
  5. APIリクエストを呼び出す
  6. ユーザー一覧を作成する
  7. ユーザー詳細を作成する

Flutter アプリを作成する

flutter createコマンドを実行し新規Flutterプロジェクトを作成します。

flutter create flutter_http
Creating project flutter_http...
Resolving dependencies in flutter_http... (1.4s)
Got dependencies in flutter_http.
Wrote 129 files.

All done!
You can find general documentation for Flutter at: https://docs.flutter.dev/
Detailed API documentation is available at: https://api.flutter.dev/
If you prefer video documentation, consider: https://www.youtube.com/c/flutterdev

In order to run your application, type:

  $ cd flutter_http
  $ flutter run

Your application code is in flutter_http/lib/main.dart.

プロジェクトディレクトリに移動し、flutter runを実行します。
カウントアプリの画面表示されたら大丈夫です。


main.dartにあるコードを削除し、ユーザー一覧だけを表示するようにします。

lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_http/home_page.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'HTTP練習アプリ',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(), // ユーザー一覧
    );
  }
}

httpを導入する

flutter pub add http

使用するファイルにインポートします。

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

APIリクエストを作成する

まずAPIエンドポイントを定義します。
APIにリクエストを送るメソッドを作成します。

lib/const/user_service.dart
class UserService {
  Future<List<User>> getUsers() async {
    try {
      final response = await http.get(
        Uri.parse("https://randomuser.me/api?results=50&seed=galaxies"),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        final List<User> userList = [];

        for (var entry in data['results']) {
          userList.add(User.fromJson(entry));
        }

        return userList;
      } else {
        throw Exception('Failed to load users. Status code: ${response.statusCode}');
      }
    } catch (e) {
      throw Exception('Failed to connect to the server. Error: $e');
    }
  }
}
results
{
results: [
{
gender: "female",
name: {
title: "Miss",
first: "Chinmayee",
last: "Bansal"
},
location: {
street: {
number: 3624,
name: "Bannerghatta Rd"
},
city: "Shimla",
state: "Chhattisgarh",
country: "India",
postcode: 27471,
coordinates: {
latitude: "6.3302",
longitude: "47.7567"
},
timezone: {
offset: "-2:00",
description: "Mid-Atlantic"
}
},
email: "chinmayee.bansal@example.com",
login: {
uuid: "214650d3-3cf6-4991-9233-d1095def7f6b",
username: "bluesnake172",
password: "callie",
salt: "W9yLJeKi",
md5: "7ece6152b544a6717f326fff3eaeaa5f",
sha1: "1595b2249ba972fb42a29511d1b3827413711bb6",
sha256: "a4b2c38e7f35ba3adccfbb310c5bb5b0974947cdd8a2e9e2a5730c821b5262a5"
},
dob: {
date: "1947-06-28T12:59:58.388Z",
age: 76
},
registered: {
date: "2013-08-20T01:58:02.377Z",
age: 10
},
phone: "9894947466",
cell: "8545545763",
id: {
name: "UIDAI",
value: "371170947273"
},
picture: {
large: "https://randomuser.me/api/portraits/women/31.jpg",
medium: "https://randomuser.me/api/portraits/med/women/31.jpg",
thumbnail: "https://randomuser.me/api/portraits/thumb/women/31.jpg"
},
nat: "IN"
}
],
info: {
seed: "ab9ded7e61d93114",
results: 1,
page: 1,
version: "1.4"
}
}

https://randomuser.me/api/

データクラスを作成する

取得したデータの中から必要な部分をデータクラスに格納します。
データクラスに格納することによってデータの安全性と再利用性を向上します。
Userクラスを定義します。

lib/user_service.dart
import 'package:http/http.dart' as http;
import 'dart:convert';

// Nameはresultsにネストされているため別でクラスを作成する
class Name {
  final String first;
  final String last;

  const Name ({
    required this.first,
    required this.last,
  });
  // jsonからNameオブジェクトを取得する
  factory Name.fromJson(Map<String, dynamic> json) {
    return Name(first: json['first'], last: json['last']);
  }
}

// Userクラス
class User {
  final String email;
  final String picture;
  final Name name;

  const User ({
    required this.email,
    required this.picture,
    required this.name,
  });
  // jsonからUserオブジェクトを取得する
  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      email: json['email'],
      picture: json['picture']['medium'],
      name: Name.fromJson(json['name']));
  }
}

APIリクエストを作成しましたので呼び出します。
fstfulでStatefulWidgetを生成します。

ユーザー一覧を作成する

lib/home_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_http/user_page.dart';
import 'package:flutter_http/user_service.dart';

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

  
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Future<List<User>> futureUsers;

  
  void initState() {
    super.initState();
    futureUsers = UserService().getUsers();
  }
  
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(title: const Text('ユーザー')),
      body: Center(
        child: FutureBuilder<List<User>>(
          future: futureUsers,
          builder: ((context, AsyncSnapshot snapshot) {
            if (snapshot.hasData) {
              return ListView.separated(
                itemBuilder: (context, index) {
                  User user = snapshot.data?[index];
                  return ListTile(
                    title: Text(user.email),
                    subtitle: Text('${user.name.first} ${user.name.last}'),
                    trailing: const Icon(Icons.chevron_right_outlined),
                    onTap: (() => openPage(context, user)), // 詳細ページを表示する
                  );
                },
                separatorBuilder: (context, index) {
                  return const Divider(color: Colors.black26);
                },
                itemCount: snapshot.data!.length,
              );
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }
            return const CircularProgressIndicator(); // アニメーション
          }),

        ),
      )
    );
  }
 // ビルドコンテキストとユーザーを渡す
  openPage(context, User user) {
    Navigator.push(
      context, MaterialPageRoute(builder: (context) => UserPage(
        user: user
      )));
  }
}

initStateメソッドはFlutterのStateライフサイクルの一部です。
ステートフルウィジェットが初めてウィジェットツリーに挿入されるときに呼び出されます。
UserServiceクラスからgetUsersメソッドを呼び出してfutureUsers変数を初期化するために使われています。

FutureBuilderはユーザーデータの非同期読み込みを処理するために使用されています。
データの読み込みを待っている間はCircularProgressIndicatorでアニメーションを表示します。
openPageは、ユーザーがタップされたときにUserPageにナビゲートするメソッドです。
選択したユーザーをパラメータとして渡します。

ユーザー詳細を作成する

lib/user_page.dart
import 'package:flutter/material.dart';
import 'package:flutter_http/user_service.dart';

class UserPage extends StatelessWidget {
  final User user;
  const UserPage({ Key? key, required this.user });

  
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(title: Text('${user.name.first} ${user.name.last}')),
      body: Center(
        child: Column(children: [
          const SizedBox(
            height: 30,
          ),
          Image.network(user.picture),
          const SizedBox(
            height: 30,
          ),
          Text(user.email)
        ],)
      )
    );
  }
}

UserPageクラスは、特定のユーザーの情報を表示する StatelessWidget です。
Userオブジェクトを受け取り、そのユーザーの名前、写真、と電子メールを表示します。

Image from Gyazo

MacOSでソケット通信が失敗したエラー

APIにリクエストを送る際にこちらのエラーが出る可能性があります。

Unhandled Exception: Exception: Failed to connect to the server. Error: ClientException with SocketException: Connection failed (OS Error: Operation not permitted, errno = 1)

macOSがネットワークにアクセスするためには、特定のエンタイトルメントを要求する必要があリマス。
解決方法はDebugProfile.entitlementsRelease.entitlementsに以下キーと値のペアを追加します。

<!-- クライアント側の場合 -->
<key>com.apple.security.network.client</key>
<true/>
<!-- サーバー側の場合 -->
<key>com.apple.security.network.server</key>
<true/>

https://docs.flutter.dev/platform-integration/macos/building#setting-up-entitlements

終わりに

簡単ですがhttpを使ってAPIからデータを取得してみました。

Discussion