Kodokon kodokon.com

استدعاء واجهة برمجية HTTP: فك الترميز وحالات الشاشة

نظِّم استدعاءً شبكيًا احترافيًا باستخدام حزمة 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. الثاني: تُرجِع jsonDecode قيمة dynamic؛ فحوِّل فورًا (as List<dynamic> وas Map<String, dynamic>) لاستعادة الأنواع. أما الحمولات الكبيرة، فأحِل فك ترميزها إلى عازل (isolate) باستخدام 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 عرض الأحرف ذات العلامات دون حزمة مخصّصة.
    • يُغفل الخادم مجموعة المحارف: فيُفَكّ ترميز response.body عندئذٍ على أنه latin-1؛ استخدم utf8.decode(response.bodyBytes).
  3. ما الميزة الحاسمة للصنف sealed في نمذجة حالة الشاشة؟
    • يصبح الـ switch شاملًا: إذ يُبلِّغ المُصرِّف عن أيّ حالة غير مُعالَجة.
    • يجعل الشاشة تُرسَم بشكل أسرع.
    • يتيح لك تعديل الحالة دون إعادة بناء الودجت.