Kodokon kodokon.com

การเรียก HTTP API: การถอดรหัสและสถานะของหน้าจอ

จัดโครงสร้างการเรียกเครือข่ายแบบมืออาชีพด้วยแพ็กเกจ http: ไทม์เอาต์, รหัสสถานะ, การถอดรหัสแบบมีชนิด และสถานะที่ครบถ้วน

11 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

แพ็กเกจ http ครอบคลุมสิ่งจำเป็นพื้นฐาน ได้แก่ รีเควสต์ เฮดเดอร์ และบอดี ส่วน dio เพิ่มตัวดักจับ (interceptor) การยกเลิก และการลองใหม่ โดยแลกกับดีเพนเดนซีที่หนักกว่า ซึ่งเป็นตัวเลือกที่ดีเมื่อแอปเติบโตจนมีเอนด์พอยต์เกินไม่กี่ตัว ไม่ว่าจะใช้ไคลเอนต์ตัวใด โครงสร้างของการเรียกเครือข่ายอย่างจริงจังก็ไม่เปลี่ยน นั่นคือ ไทม์เอาต์ การตรวจสอบ รหัสสถานะ 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 สอง jsonDecode คืนค่า dynamic จงแคสต์ทันที (as List<dynamic>, as Map<String, dynamic>) เพื่อกู้ชนิดข้อมูลกลับคืน สำหรับเพย์โหลดขนาดใหญ่ ให้ย้ายงานถอดรหัสไปยังไอโซเลตด้วย compute เพื่อไม่ให้ UI ค้าง

ที่เหลือคือการแทนสถานะของหน้าจอ แฟล็กบูลีนอย่าง 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 ไม่สามารถแสดงอักขระที่มีเครื่องหมายกำกับเสียงได้หากไม่มีแพ็กเกจเฉพาะ
    • เซิร์ฟเวอร์ละเว้นชุดอักขระ response.body จึงถูกถอดรหัสเป็น latin-1 ให้ใช้ utf8.decode(response.bodyBytes)
  3. อะไรคือข้อได้เปรียบชี้ขาดของคลาส sealed ในการจำลองสถานะของหน้าจอ?
    • switch จะครบถ้วน คอมไพเลอร์จะแจ้งเตือนสถานะใด ๆ ที่ไม่ได้จัดการ
    • มันทำให้หน้าจอเรนเดอร์เร็วขึ้น
    • มันช่วยให้คุณแก้ไขสถานะได้โดยไม่ต้องรีบิลด์วิดเจ็ต