Kodokon kodokon.com

HTTP APIを呼び出す:デコードと画面の状態

httpパッケージでプロフェッショナルなネットワーク呼び出しを組み立てましょう。タイムアウト、ステータスコード、型付きデコード、そして網羅的な状態です。

11 分 · 3 問

このレッスンを Kodokon で開く

httpパッケージは要点を押さえています。リクエスト、ヘッダー、ボディです。dioはインターセプター、キャンセル、リトライを加えますが、より重い依存関係と引き換えです。アプリがいくつかのエンドポイントを超えて大きくなったら良い選択でしょう。どのクライアントを使うにせよ、まじめなネットワーク呼び出しの構造は変わりません。タイムアウトHTTPステータスコードの確認、型付きのデコード、そして三つの画面の状態(ロード中、エラー、データ)を明示的に扱うことです。

BASH
flutter pub add http
DART
import 'dart:convert';

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

class Article {
  const Article({required this.title});

  factory Article.fromJson(Map<String, dynamic> json) =>
      Article(title: json['title'] as String);

  final String title;
}

Future<List<Article>> fetchArticles() async {
  final uri = Uri.https('api.example.com', '/articles');
  final response = await http
      .get(uri)
      .timeout(const Duration(seconds: 8));
  if (response.statusCode != 200) {
    throw Exception('HTTP ${response.statusCode}');
  }
  final body = utf8.decode(response.bodyBytes);
  final data = jsonDecode(body) as List<dynamic>;
  return [
    for (final item in data)
      Article.fromJson(item as Map<String, dynamic>),
  ];
}
タイムアウト、確認されたステータス、UTF-8デコード、それから型付け。

二つの挙動がよく人を驚かせます。一つ目、httpは404や500のステータスに対して例外を投げません。ネットワークの観点からはリクエストは「成功」したので、statusCodeを確認するのはあなたの役目です。二つ目、jsonDecodedynamicを返します。型付けを取り戻すために、すぐにキャストしましょう(as List<dynamic>as Map<String, dynamic>)。大きなペイロードでは、UIを固まらせないよう、デコードをcomputeでアイソレートに逃がしましょう。

残るのは画面の状態を表現することです。isLoadingのような真偽フラグをerror != nullと組み合わせると、必ず矛盾した状態に行き着きます(ロード中でかつ同時にエラー)。sealedクラスは状態を互いに排他的にし、switchを網羅的にします。明日あなたが状態を一つ追加すれば、コンパイラが更新すべき画面をすべて指摘してくれます。

DART
import 'package:flutter/material.dart';

sealed class ArticlesState {
  const ArticlesState();
}

class ArticlesLoading extends ArticlesState {
  const ArticlesLoading();
}

class ArticlesError extends ArticlesState {
  const ArticlesError(this.message);

  final String message;
}

class ArticlesLoaded extends ArticlesState {
  const ArticlesLoaded(this.titles);

  final List<String> titles;
}

Widget buildArticlesBody(ArticlesState state) {
  return switch (state) {
    ArticlesLoading() => const Center(
        child: CircularProgressIndicator(),
      ),
    ArticlesError(:final message) => Center(
        child: Text(message),
      ),
    ArticlesLoaded(:final titles) => ListView.builder(
        itemCount: titles.length,
        itemBuilder: (context, index) => ListTile(
          title: Text(titles[index]),
        ),
      ),
  };
}
網羅的なswitch:どの状態も忘れられない。

理解度チェック

このレッスンの要点をしっかり覚えているか確認しましょう。

  1. あなたのhttp.get呼び出しが500のレスポンスを受け取ります。httpパッケージは何をしますか?
    • try/catchでキャッチできる例外を投げる。
    • Responseを普通に返す。statusCodeを確認するのはあなたの役目だ。
    • リクエストを自動的に三回リトライする。
  2. 受け取ったデータのなかでアクセント付きの文字が正しく表示されません。最も可能性の高い原因は?
    • jsonDecodeがUTF-8を扱えない。
    • Flutterは専用のパッケージなしにはアクセント付きの文字を表示できない。
    • サーバーがcharsetを省略している。するとresponse.bodyはlatin-1としてデコードされる。utf8.decode(response.bodyBytes)を使おう。
  3. 画面の状態をモデル化するうえで、sealedクラスの決定的な利点は何ですか?
    • switchが網羅的になる。コンパイラが未処理の状態を指摘してくれる。
    • 画面の描画が速くなる。
    • ウィジェットをリビルドせずに状態を変更できる。