FlutterでHTTP通信をしてみた件

# はじめに
Flutterで簡単なHTTP通信を実装してたいと思い。作ってみました。
GETとPOSTしかしていません。

# 動作環境
この記事の動作環境は以下のとおりです。

Flutter：3.7.1
Dart SDK：2.19.1
IntelliJ IDEA：2022.2.2

# 完成イメージ
今回の完成イメージは下図のようになります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99960/206baec6-d1f5-0b16-317a-7f8069d94f6a.gif" width="50%" />

# サンプルコード
サンプルコードは[GitHub](https://github.com/ktaro-nakaya/bmi_sample)を確認してください。

# サーバサイド
このサンプルのサーバサイドはjson-serverを利用しています。
json-serverの使い方は[公式サイト](https://www.npmjs.com/package/json-server)を参照してください。

# コード解説
すべてのコード解説は行いません。Flutterで作成するアプリでHTTP通信に関するところのみを説明します。

## JSONのデコード・エンコード
このサンプルではjson-serverと送受信するデータはすべてJSON形式です。
そのため、JSONのデコードとエンコードが出来るようにします。
今回はBMIと体型を表すプロパティを2つ用意しています。

```dart:bmi.dart
class BMI{
  final double bmi;
  final String shape;

  const BMI(this.bmi, this.shape,);

  Map<String, dynamic> toMap() {
    return {
      'bmi': this.bmi,
      'shape': this.shape,
    };
  }

  BMI.fromJson(Map<String, dynamic> map)
  : bmi= map['bmi'], shape= map['shape'];
}
```

今回実装したBMIクラスの特徴は、JSONに変換するfromJSONメソッドとJSONからBMIクラスのオブジェクトに変換するtoMapメソッドです。

### JSONに変換する処理
オブジェクトからJSONに変換するためには[jsonEncode](https://api.dart.dev/stable/2.13.4/dart-convert/jsonEncode.html)関数を呼び出します。引数はObject型を受け取りますが、下記のデータ型であれば直接エンコードできます。

　- 数値
　- Boolean
　- 文字列
　- NULL
　- リスト
　- 文字列をキーとするマップ

そのため、BMIクラスにMapに変換するメソッドを用意しておきます。

```dart
  Map<String, dynamic> toMap() {
    return {
      'bmi': this.bmi,
      'shape': this.shape,
    };
  }
```

### JSONからオブジェクトに変換する処理
JSON文字列をオブジェクトに変換するために、[jsonDecode](https://api.flutter.dev/flutter/dart-convert/jsonDecode.html)関数を呼び出します。
戻り値は、引数に渡すJSON文字列によって変わります。引数に渡すJSON文字列がリスト形式のJSONの場合はList、JSONオブジェクトの場合はMap形式で受け取れます。
JSON文字列がリスト形式であってもリストの要素はJSONオブジェクトであるため、Map形式で個々のデータを取得できます。Map形式で受け取った場合、値はdynamic形です。

そのため、Map形式からオブジェクトに変換する処理を用意しておく必要があります。
サンプルコードではtoMapメソッドとして定義しました。

```dart
  BMI.fromJson(Map<String, dynamic> map)
  : bmi= map['bmi'], shape= map['shape'];
```
## HTTP通信
サンプルコードではHTTPのGETメソッドとPOStメソッドを使っています。
まずはコード全体を確認しましょう。

```dart:bmi_http_clinet.dart
import 'dart:convert';

import 'package:bmi_sample/model/bmi.dart';
import 'package:http/http.dart' as http;

class BMIHttpClientNative {

  // json-serverを実行しているIPアドレスを指定
  final String _ipAddress = "XXX.XXX.XXX.XXX";

  Future<List<BMI>> getBmi() async {
    List<BMI> list = [];

    var uri = Uri.parse('http://$_ipAddress:3000/bmi');
    var response = await http.get(uri);
    Iterable iterable = jsonDecode(response.body);
    var conertList = iterable.map((e) => BMI.fromJson(e)).toList();
    list.addAll(conertList);
    return list;
  }

  Future<int> postBmi(BMI bmi) async {
    http.Response? response;

    response = await http.post(
      Uri.parse('http://$_ipAddress:3000/bmi'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(bmi.toMap()),
    );

    return response.statusCode;
  }
}
```


### 準備
DartでHTTP通信するためには、HTTPクライアントを利用します。
そのため、HTTPクライアントのライブラリーを追加しなければなりません。
サンプルでは``pubspec.yaml ``に追記してビルド時に追加しています。
追加方法の詳細は[公式サイト](https://pub.dev/packages/http/install)を参照してください。

```yaml:pubspec.yaml
dependencies:
  http: ^0.13.5
```

### import文
ライブラリーを追加するだけでは利用できないのでHTTPクライアントをインポートします。

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

### 非同期処理
HTTP通信処理は一般的に非同期処理で実装します。
そのため、GETとPOSTを処理している関数は非同期処理で実装しています。
非同期処理を実装するためのasyncについては[公式サイト](https://dart.dev/codelabs/async-await#working-with-futures-async-and-await)を確認してください。

```dart
Future<int> postBmi(BMI bmi) async {
・・・省略・・・
}
```

### URIの準備と通信
HTTPクライアントの準備ができたら、接続する先のURIを用意します。
URI準備するには[Uriクラスのparseメソッド](https://api.dart.dev/stable/2.19.6/dart-core/Uri/parse.html)を呼び出し生成します。

```dart
Uri.parse('http://$_ipAddress:3000/bmi')
```

HTTP通信の準備が整いました。GETメソッドとPOSTメソッドで通信を行います。

#### GETメソッド
今回のサンプルのGETメソッドでのHTTP通信ではデータを送らないため、[http.get関数](https://pub.dev/documentation/http/latest/http/Client/get.html)にUriオブジェクトを渡しています。
第２引数にはヘッダー情報をMap型で渡せます。

```dart
await http.get(uri);
```
通信処理部分はawaitでHTTP通信処理が完了を待ちます。

#### POSTメソッド
今回のサンプルでは、POSTでJSON文字列でBMIと体型を送信しています。
そのため、2つの前処理をしています。

* ヘッダーの準備
* オブジェクトからJSON文字列への変換

ヘッダーはGETメソッドでも説明した通り、Map型で準備します。

```dart
     <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      }
```
JSON文字列への変換はすでに説明しているとおり、jsonEncode関数にMap型のデータを渡しています。

```dart
jsonEncode(bmi.toMap())
```

JSON文字列とヘッダーを[http.POST関数](https://pub.dev/documentation/http/latest/http/Client/post.html)を呼び出してHTTP通信を行います。

引数は下記の4つです。

|型|説明|必須 or オプション|
|:---|:---|:---|
|Uri型|接続先のURL|必須|
|Map型|ヘッダー情報|オプション|
|Object型|HTTP通信のボディで送信するデータ。String、List、Mapを引数に渡せます。|オプション|
|[Encoding](https://api.dart.dev/stable/2.19.4/dart-convert/Encoding-class.html)型|エンコードを指定します。デフォルトはutf-8です。|オプション|

今回のサンプルではUriとボディのみを渡しています。
また、GETメソッド同様にawaitでHTTP通信の処理が完了するまで待ちます。

```dart
    await http.post(
      Uri.parse('http://$_ipAddress:3000/bmi'),
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(bmi.toMap()),
    );
```

## レスポンス
HTTP通信が完了すると[Response](https://pub.dev/documentation/http/latest/http/Response-class.html)型で返ってきます。

```dart
    var response = await http.get(uri);
    Iterable iterable = jsonDecode(response.body);

    response = await http.post(
　　・・・省略・・・
    );
    return response.statusCode;
```
今回はbodyプロパティとstatusCodeプロパティを利用しています。必要に応じて他のプロパティを利用してください。

# まとめ
標準APIでHTTP通信を行ってみましたが、比較的使いやすく感じます。
外部ライブラリーを使うとどれくらい楽に実装できるか気になるのでいつか調べてみます。