Zenn
🖼️

【Flutter】2つのHTTPクライアントパッケージの比較

2025/02/24に公開
4

HTTP パッケージ

アプリから HTTP リクエストを送る際の選択肢には、以下の2つのパッケージが候補に挙がります。

あまり真っ向から比較した記事が見当たらなかったので、本記事ではいくつかの項目で比較していきたいと思います。

結論から言うと、どちらも優れたパッケージで互いに大きな差異はありません。

おそらく多くの人が想像する通り、http はパッケージとしての信頼性の面で軍配が上がり、dio は便利さで少々軍配が上がります。
dio にできて http にできないことは、ほぼないと言って良いので、http のみで機能を実現したい場合でも十分におすすめできると考えています。

開発元の信頼性

dio

元々、個人が開発していたものが開発困難になったため、flutter.cn に移行されました。

https://x.com/AlexV525/status/1624978831860989952

公式ではなかったり、Flutter Favorite ではないのが少し気にはなりますが、一度開発困難になったものが移行された背景があることを考えれば、十分と言えるでしょう。

http

公式の dart.dev チームがメンテナンスを行なっています。

さらに公式のドキュメントによると、公式チームの中でも最上位の優先度にあると明言されているので、パッケージとしての安心感で言えば最高峰です。

機能の重要性を考えると、開発がストップすることはまずないでしょう。


https://dart.dev/resources/dart-team-packages

結論

圧倒的に http の信頼性が高いですが、dio も十分と言えます。

CancelToken

HTTP通信中に、たとえばウィジェットが破棄されたりした時に、リクエストを中断できる機能です。

dio

クラスでしっかりと機能が用意されています。

https://pub.dev/documentation/dio/latest/dio/CancelToken-class.html

class _HogeState extends State<Hoge> {
  final _cancelToken = CancelToken();
  
  late final _future = Future(() async {
    return Dio().get(
      'hoge.com',
      cancelToken: _cancelToken,
    );
  });
  
  
  void dispose() {
    // _future 実行中にウィジェットが dispose されれば、HTTP リクエストが中断される。
    _cancelToken.cancel();
  }
}

http

CancelToken はありませんが、リクエスト時に生成する Client クラスに close が用意されているので、そちらを使えば実現可能です。

class _HogeState extends State<Hoge> {
  Client? _httpClient;
  
  late final _future = Future(() async {
    _httpClient = Client();
    return _httpClient!.get(
      'hoge.com',
    );
  });
  
  
  void dispose() {
    // _future 実行中にウィジェットが dispose されれば、HTTP リクエストが中断される。
    _httpClient?.close();
  }
}

ですが、若干取り回しづらいので、もし dio のように、CancelToken を渡す形に変えたい場合は自前でクラスをつくる必要があります。

しかし、CancelToken の構造自体は、Completer さえ知っていれば理解できるシンプルな構造なので、そこまで難易度は高くないように思います。

もしくは、関数で渡せるようにするかです。

Future<Response> get({
  required Uri uri,
  void Function(Client client)? onClientCreated,
}) async {
  final client = Client();

  onClientCreated?.call(client);

  return client.get(uri);
}

Clinet? _httpClient;

final response = await get(
  uri,
  (client) { _httpClient = clinent; },
);

結論

どちらでも実現可能だが、dio の方が取り回しがしやすい構造になっている。
http でもそこまで実現は難しくない。

Interceptor

リクエストの実行前や、レスポンスの受け取り直後に処理を挟み、値の加工やヘッダの追加などを行える機構。

dio

Dio クラス自体に用意されています。
自前で実装できるのはもちろん、ログを表示させるものなど、いくつかすでに用意されているものもあります。

https://pub.dev/documentation/dio/latest/dio/Interceptor-class.html

final dio = Dio()
  ..intercepters.addAll([
    LogInterceptor(),
    MyAuthInterceptor(token: token),
  ]);

class MyAuthInterceptor extends Interceptor {
  MyAuthInterceptor({required this.token});

  final String? token;

  
  void onRequest(
    RequestOptions options,
    RequestInterceptorHandler handler,
  ) {
    if (token != null) {
      options.headers['Authorization'] = 'Bearer ${token}';
    }
    super.onRequest(options, handler);
  }
}

加えて、dio には、クッキーにブラウザと同等の動きをさせる拡張パッケージが存在します。
dio_cookie_manager

しかし、クッキーの保存先に path_provider が推奨されていたりするので、使うかどうかは慎重になった方がいいかもしれません。

http

こちらで Intercepter を利用する場合は、後述する、自動生成パッケージの chopper を使用する必要があります。

https://pub.dev/documentation/chopper/latest/chopper/Interceptor-class.html

final client = ChopperClient(
  // ...略
  intercepters: [
    HttpLoggingInterceptor(),
    MyAuthInterceptor(token: token),
  ],
);

class MyAuthInterceptor implements Interceptor {
  MyAuthInterceptor({required this.token});

  final String? token;

  
  FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) {
    final request = applyHeader(chain.request, 'Authorization', 'Bearer $token');
    return chain.proceed(request);
  }
}

chopper にも、dio 同様ログを表示するものなどは用意されています。

結論

自動生成パッケージを採用しない場合は、dio でないと実現できない。
採用する場合、Intercepter は実現可能。

dio_cookie_manager を使いたい場合は、dio を採用する。

自動生成パッケージ

REST API のメソッド、パス、クエリパラメータ、ボディなどをコード生成によって最小限の記述で実現するためのパッケージ。

dio

retrofit という有名なパッケージが存在します。

https://pub.dev/packages/retrofit

多く記事が落ちてると思うので、使い方などは割愛します。

http

こちらにも chopper という Flutter Favorite のパッケージが存在します。
JsonConverter や Form リクエストへの対応など、retrofit にできることは可能で、特有の問題にぶち当たったことは自分あはりません。

https://pub.dev/packages/chopper

retrofit ほどメジャーじゃない感覚があるので、記事はそこまで多くないように思います。
retrofit + Intercepter などの dio 特有の機能というイメージです。

郵便番号APIを叩いている実装例を置いておきます。

https://github.com/Zudah228/rich_memo_app/blob/main/lib/repository/post_code_search/post_code_search_repository.dart

結論

どちらでも実現は可能。
retrofit(dio) の方がメジャーな印象があるので、チームのキャッチアップの面やAIのコード生成には強いかもしれない。

preserveHeaderCase パラメータ

前提として、HTTP のヘッダのキーには、case-insensitivity(大文字小文字を区別しない)という設計思想があります。
逆に言えば、API の設計者は、ヘッダーの大文字小文字は、両方ともカバーするように実装しないといけないということです(ほとんどの場合は、SDK がそのようにやってくれている)。

そして、Dart では、ヘッダのキーが全て小文字に変換されます。
つまり、Authorzation でも authorization でも autorization に変換されます。

もし、叩く予定の Web API の実装者が case-insensitivity を守れておらず、大文字でしか受け入れていない場合は、この機能は邪魔になってしまいます。

dio

preserveHeaderCase パラメータで、勝手に小文字に変換される機能をオフにできます。

http

パッケージでは意図的に preserveHeaderCase を取り入れられていません。

Issue にて議論は少しだけ行われていますが、オープンのままです。

Add support for preserveHeaderCase · Issue #609 · dart-lang/http

結論

dio では実現可能だが、http では完全に実現できない。
ただし、HTTP の思想に則ればそもそもこの壁にぶつかる可能性は低い(自分は1度ぶち当たったことありますが…)。

リトライ

使う人は少ないと思いますが、処理が失敗した時に何度か再実行する機構です。

dio

dio_smart_retry というサードパーティのパッケージが存在します。

https://pub.dev/packages/dio_smart_retry

http

google 製のパッケージが存在しますが、2年ほど更新が止まっていそうです。

https://pub.dev/packages/retry

結論

どちらもパッケージは存在する。

エラーの扱い

dio

200 台のステータス以外はエラーを throw して処理を中断する仕様が、デフォルトで入っています。

bool _defaultValidateStatus(int? status) {
  return status != null && status >= 200 && status < 300;
}

これを防ぎたい場合は、Dio インスタンス生成時に、validateStatus を上書きすることもできます。

final options = BaseOptions(
  // どんなステータスコードが来ても、「成功」とみなす条件式
  validateStatus: (_) => true,
);
final dio = Dio(options);

http

validateStatus みたいな機能はなく、ステータスコードによって勝手に throw されることはありません。

結論

扱いは微妙に違うが、大きな差はない。

まとめ

preserveHeaderCase という特殊な例を除けば、どちらでも十分に開発は可能だと思います。

ただ、http で実装する場合は少しだけ工夫することがあるので、スピードやパッケージの安定性を重視しない場合は、dio が良さそうです。

ですが、http x chopper もかなり信頼がおける組み合わせで、機能も十分に揃っています。
長く続くプロダクトで、安定性を重視する場合、キャッチアップの多少のコストを重視しない場合は、かなりいいチョイスだと思います。

4

Discussion

ログインするとコメントできます