Logo

المعمل 07: ListView.builder، تصحيح الأخطاء، وخدمات الويب وواجهات API

14 دقيقة قراءة
شرائح الدرس
1 / 13

المعمل 07: ListView.builder، وتصحيح الأخطاء، وخدمات الويب وواجهات البرمجة

القوائم الكسولة، وتصحيح الأخطاء بنقاط التوقف، واستهلاك واجهات REST من Flutter

نظرة عامة

يغطي هذا المعمل أربعة مواضيع مترابطة في Flutter تنقلك من عرض قوائم فعّال إلى تطبيقات متصلة بالشبكة. ستتعلم كيفية بناء قوائم ديناميكية عالية الأداء باستخدام ListView.builder ودالة الاستدعاء itemBuilder الخاصة بها، وكيفية تشخيص وإصلاح أخطاء التشغيل (Runtime Errors) في Flutter باستخدام نقاط التوقف (Breakpoints) والمصحح (Debugger)، وكيفية استهلاك واجهات REST API عبر HTTP - وفهم ماهية خدمة الويب (Web Service) وكيفية الاتصال بها من تطبيق Dart/Flutter باستخدام حزمة http.


الأهداف

بنهاية هذا المعمل ستكون قادرًا على:

  • شرح الفرق بين ListView (قائمة عناصر فرعية ثابتة) و ListView.builder (كسولة، مدفوعة بالفهرس Index)
  • استخدام دالة الاستدعاء itemBuilder بشكل صحيح لبناء عناصر الواجهة حسب الفهرس
  • تشغيل ListView.builder من List حقيقية من كائنات النموذج في Dart
  • قراءة سجل أخطاء وقت التشغيل في Flutter وتحديد عنصر الواجهة وإطار المكدّس (Stack Frame) المسبب للانهيار
  • ضبط نقاط توقف والتنقل خطوة بخطوة عبر الكود في وضع التصحيح (Debug Mode) داخل Android Studio / VS Code
  • تقييم قيم المتغيرات عند نقطة توقف لتحديد خطأ قيمة فارغة (null)
  • تعريف ما هي خدمة الويب (Web Service) وواجهة API، والتفريق بين المصطلحين
  • شرح دورة طلب/استجابة HTTP ودور عميل HTTP (HTTP Client)
  • استخدام حزمة http في Dart لجلب بيانات JSON من نقطة نهاية (Endpoint) REST
  • تحليل استجابة JSON إلى كائنات نموذج Dart مُحدَّدة النوع باستخدام fromJson
  • إدارة ثلاث حالات واجهة (تحميل، خطأ، نجاح) داخل StatefulWidget

المتطلبات الأساسية

  • إعداد مشروع Flutter ومعرفة بعناصر الواجهة (المعامل 01-06)
  • الإلمام بأساسيات StatefulWidget، وsetState()، وListView
  • صياغة أساسية لأصناف وقوائم وصياغة async/await في Dart

خلفية نظرية

1. ListView.builder - قوائم كسولة مدفوعة بالفهرس

لماذا ListView.builder؟

يبني الباني العادي ListView(children: [...]) كل عناصر الواجهة مقدمًا، حتى تلك الموجودة خارج الشاشة. بالنسبة لمجموعات بيانات كبيرة أو ديناميكية، هذا يهدر الذاكرة ويكون بطيئًا.

يبني ListView.builder فقط عناصر الواجهة المرئية حاليًا على الشاشة. عندما يمرر المستخدم، يستدعي Flutter itemBuilder عند الحاجة لكل عنصر جديد يدخل مجال الرؤية.

توقيع الباني (Constructor Signature)

ListView.builder(
  itemCount: courses.length,      // total number of items
  itemBuilder: (BuildContext context, int index) {
    return CourseCard(course: courses[index]);
  },
)
المعاملالنوعالوصف
itemCountintعدد العناصر التي تحتويها القائمة
itemBuilderWidget Function(BuildContext, int)تُستدعى مرة واحدة لكل عنصر مرئي؛ تستقبل index يبدأ من الصفر

كيف يستدعي Flutter itemBuilder

عندما يبني Flutter القائمة لأول مرة، فإنه:

  1. يقرأ itemCount (مثلًا 5).
  2. يستدعي itemBuilder(context, 0) - يعرض العنصر 0.
  3. يستدعي itemBuilder(context, 1) - يعرض العنصر 1.
  4. يستمر حتى itemCount - 1.
  5. عندما يمرر المستخدم، يستدعي itemBuilder للعناصر المرئية حديثًا.
flowchart TD
    A[إنشاء ListView.builder] --> B[قراءة itemCount = 5]
    B --> C[استدعاء itemBuilder بالفهرس 0]
    C --> D[استدعاء itemBuilder بالفهرس 1]
    D --> E[استدعاء itemBuilder بالفهرس 2]
    E --> F[...]
    F --> G[الوصول لـ itemCount - توقف]
    G --> H{المستخدم يُمرِّر}
    H -->|عنصر جديد يدخل نطاق العرض| I[استدعاء itemBuilder للفهرس الجديد]

اربط itemCount دائمًا بطول القائمة

// BAD: hard-coded count goes out of sync when the list changes
ListView.builder(
  itemCount: 5,          // crashes if courses has only 3 items
  itemBuilder: (context, index) => CourseCard(course: courses[index]),
)
 
// GOOD: always matches the actual data
ListView.builder(
  itemCount: courses.length,
  itemBuilder: (context, index) => CourseCard(course: courses[index]),
)

إذا كتبت itemCount: 5 لكن القائمة الفعلية تحتوي على عنصرين فقط، سيرمي Flutter الخطأ RangeError (index): Invalid value: Not in inclusive range 0..1: 2 - انهيار تجاوز نطاق الفهرس (Index Out of Range) الكلاسيكي.

مثال على قائمة ديناميكية

final List<String> courseNames = ['Intro to Dart', 'Flutter Basics', 'State Management'];
 
ListView.builder(
  itemCount: courseNames.length,
  itemBuilder: (BuildContext context, int index) {
    return ListTile(
      leading: CircleAvatar(child: Text('${index + 1}')),
      title: Text(courseNames[index]),
    );
  },
)

أضف عنصرًا آخر إلى courseNames وستعرضه القائمة تلقائيًا - لا حاجة لتغيير أي كود في ListView.builder.

استخلاص صنف عنصر واجهة لكل صف

النمط المُوصى به هو استخلاص الصف إلى StatelessWidget خاص به:

// course_card.dart
class CourseCard extends StatelessWidget {
  final String name;
  const CourseCard({super.key, required this.name});
 
  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(title: Text(name)),
    );
  }
}
 
// courses_screen.dart
ListView.builder(
  itemCount: courseNames.length,
  itemBuilder: (context, index) => CourseCard(name: courseNames[index]),
)

هذا أفضل من كتابة return Card(...) مباشرة لأن:

  • يمكن اختبار البطاقة بشكل مستقل
  • تملك دورة حياة إعادة بناء خاصة بها (تُعاد بناؤها فقط عندما تتغير حالتها الخاصة)
  • قابلة لإعادة الاستخدام عبر شاشات متعددة

ListView مع قائمة من كائنات النموذج

في التطبيقات الحقيقية، العناصر هي كائنات نموذج، وليست نصوصًا خامًا:

class Course {
  final String name;
  final String imageUrl;
  Course({required this.name, required this.imageUrl});
}
 
final List<Course> courses = [
  Course(name: 'Flutter Basics', imageUrl: 'https://example.com/flutter.png'),
  Course(name: 'Dart Deep Dive', imageUrl: 'https://example.com/dart.png'),
];
 
ListView.builder(
  itemCount: courses.length,
  itemBuilder: (context, index) {
    final course = courses[index];
    return Row(
      children: [
        Image.network(course.imageUrl, width: 60, height: 60),
        const SizedBox(width: 12),
        Text(course.name),
      ],
    );
  },
)

امتلاك List<Course> واحدة بدلًا من قائمتين متوازيتين (List<String> names + List<String> urls) يضمن بقاء البيانات متزامنة تلقائيًا.


2. تصحيح الأخطاء في Flutter (Debugging)

قراءة سجل الأخطاء (وضع التشغيل - Run Mode)

عندما ينهار التطبيق وتظهر الشاشة الحمراء، فإن الخطوة الأولى هي قراءة سجل تبويب Run - وليس التخمين. يعرض السجل:

  1. رسالة الاستثناء (Exception Message) - مثل The method was called on null.
  2. اسم عنصر الواجهة الذي تسبب فيه.
  3. تتبع المكدّس (Stack Trace) - قائمة بأسماء الملفات وأرقام الأسطر من الاستدعاء الأعمق إلى الخارج.

انقر على اسم ملف في تتبع المكدّس للانتقال مباشرة إلى ذلك السطر في المحرر.

flowchart LR
    A[شاشة حمراء أو خطأ] --> B[افتح تبويب Run]
    B --> C[جد رسالة الاستثناء]
    C --> D[جد الملف:السطر في تتبع المكدّس]
    D --> E[انقر الرابط لفتح الملف]
    E --> F[حدد القيمة الفارغة أو الخاطئة]

أنماط الأخطاء الشائعة:

نص الخطأالسبب المحتمل
The method was called on nullمتغيّر قيمته null قبل استدعاء طريقة/getter عليه
RangeError: Invalid value ... indexitemCount أكبر من طول القائمة
type 'Null' is not a subtype of type 'String'حقل يُفترض ألا يكون فارغًا وصل بقيمة null من JSON

ضبط نقطة توقف والدخول في وضع التصحيح

  1. انقر في الهامش (Gutter) (الحافة اليسرى للمحرر) بجانب السطر الذي تريد فحصه. تظهر نقطة حمراء - هذه هي نقطة التوقف (Breakpoint).
  2. شغّل التطبيق باستخدام زر Debug (أيقونة الحشرة)، وليس زر Run العادي.
  3. يعمل Flutter بشكل طبيعي حتى يصل إلى سطر نقطة التوقف الخاص بك، ثم يتوقف مؤقتًا.
  4. في لوحة Variables، افحص القيم الحالية لكل المتغيرات المحلية.
flowchart TD
    A[حدد السطر المشتبه به من السجل] --> B[انقر الهامش ← نقطة توقف]
    B --> C[اضغط زر Debug لبدء التطبيق]
    C --> D[يعمل التطبيق بشكل طبيعي حتى نقطة التوقف]
    D --> E[التنفيذ يتوقف مؤقتًا]
    E --> F[افحص لوحة Variables]
    F --> G{هل القيمة فارغة أو خاطئة؟}
    G -->|نعم| H[تتبّع للخلف حتى مكان ضبط القيمة]
    G -->|لا| I[انتقل لنقطة التوقف التالية]
    H --> J[جد السبب الجذري في مصدر البيانات / JSON]

التنقل خطوة بخطوة عبر الكود

الزرالإجراء
Resume (▶)استمر حتى نقطة التوقف التالية
Step Over (→)نفّذ السطر الحالي وانتقل للسطر التالي
Step Into (↓)ادخل داخل الطريقة المُستدعاة في هذا السطر
Step Out (↑)أنهِ الطريقة الحالية وعُد إلى المستدعي

إيجاد قيمة فارغة (null) عبر التتبع للخلف

سيناريو واقعي شائع: يجلب التطبيق بيانات المستخدم من API ويحاول عرض user.points، لكنه ينهار بسبب قيمة فارغة.

مسار تصحيح الأخطاء:

  1. يُظهر السجل أن points فارغة (null). ضع نقطة توقف عند السطر الذي يقرأ points.
  2. شغّل في وضع التصحيح. عند التوقف، مرر المؤشر فوق المتغيّر أو وسّعه في لوحة Variables.
  3. تأكد أن points تساوي null.
  4. استخدم Ctrl+Click أو Find Usages لتحديد مكان تعيين points.
  5. ضع نقطة توقف هناك. استأنف التنفيذ. عند التوقف، افحص خريطة JSON.
  6. قيّم استجابة API الخام - إذا لم تكن points موجودة في JSON، فإن API يُرجع بيانات ناقصة.
  7. الاستنتاج: الخطأ في استجابة API، وليس في كود Dart. تواصل مع مطور الخلفية (Backend).

هذا نمط يعكس تصحيح الأخطاء الاحترافي الواقعي: الخطأ في طبقة البيانات (API)، وقد اكتُشِف عبر تتبع منهجي بنقاط التوقف.


3. خدمات الويب وواجهات API - المفاهيم

ما هي واجهة API؟

API اختصار لـ Application Programming Interface (واجهة برمجة التطبيقات). هي واجهة تسمح لبرنامج بالتواصل مع برنامج آخر برمجيًا (عبر الكود، وليس واجهة رسومية).

GUI  → يتفاعل الإنسان مع البرنامج باستخدام الأزرار والقوائم
API  → يتفاعل البرنامج مع برنامج آخر باستخدام الكود

ما هي خدمة الويب (Web Service)؟

خدمة الويب (Web Service) هي برنامج موجود على الويب ومتاح كخدمة لتستخدمها أنظمة أخرى. كل خدمات الويب هي واجهات API، لكن ليست كل واجهات API خدمات ويب - يمكن أن توجد واجهة API بين برنامجين محليين دون أي اتصال بالشبكة.

عمليًا، يُستخدم مصطلحا "API" و"web service" بالتبادل في الصناعة. الفرق التقني هو:

خدمة الويب (Web Service)واجهة API
يجب أن تكون على الويبنعملا
تتبع معيارًا (REST، SOAP)نعمغير مطلوب
صيغة البياناتعادة JSON أو XMLأي صيغة

لماذا تبني الشركات واجهات API؟

  1. تحقيق الدخل (Monetisation) - شركات مثل Twilio (الرسائل النصية)، وSendGrid (البريد الإلكتروني)، وAmazon تبيع الوصول إلى واجهات API الخاصة بها.
  2. مشاركة البيانات - تكشف Facebook وTwitter وGoogle Maps واجهات API حتى تستطيع تطبيقات طرف ثالث قراءة البيانات أو نشرها.
  3. التكامل الداخلي - يحتاج تطبيق موبايل إلى قاعدة بيانات على خادم؛ وتكون واجهة API هي الجسر بينهما.

حالات استخدام واقعية

flowchart LR
    App[تطبيق موبايل] -->|طلب HTTP| API[واجهة API على الخادم]
    API -->|استجابة HTTP بصيغة JSON| App
    API -->|قراءة / كتابة| DB[(قاعدة بيانات)]

حالة الاستخدام 1 - خدمة طرف ثالث: تقدّم شركة خدمة مدفوعة (مثل إرسال الرسائل النصية). يستدعي المطورون واجهة API الخاصة بها لإطلاق رسائل نصية من تطبيقاتهم الخاصة.

حالة الاستخدام 2 - الخلفية (Backend) الخاصة بالتطبيق: يحتاج تطبيق اختبارات (Quiz) إلى تخزين النتائج على خادم حتى يرى المستخدمون لوحة المتصدرين (Leaderboard). يستدعي تطبيق الموبايل واجهة API الخاصة بالشركة نفسها والتي تقرأ/تكتب من قاعدة بيانات الخادم.

بروتوكول HTTP

تتواصل خدمات الويب باستخدام بروتوكول HTTP (أو HTTPS). HTTP هو مجموعة من القواعد التي تحكم التواصل بين عميل (Client) وخادم (Server).

  • العميل (Client) - البرنامج الذي يُجري الطلب (تطبيق موبايل، متصفح، Postman)
  • الخادم (Server) - الجهاز الذي يستضيف البيانات أو الخدمة
العميل                        الخادم
  |                              |
  |--- طلب HTTP ----------->|
  |                              | (يعالج الطلب)
  |<-- استجابة HTTP -----------|
  |                              |

HTTPS هو بروتوكول HTTP مع تشفير: تُشفَّر البيانات المنقولة بين العميل والخادم، بحيث لا يرى من يعترض حركة المرور سوى نص مشفّر غير مفهوم.

عميل HTTP (HTTP Client)

عميل HTTP هو برنامج أو مكتبة تتيح لك إرسال طلبات HTTP واستقبال الاستجابات. أمثلة:

الفئةأمثلة
برامج مستقلةChrome، Firefox، Postman
مكتبة داخل لغة برمجةحزمة http (Dart/Flutter)، requests (Python)، HttpClient (Java)

في Flutter، توفّر حزمة http عميل HTTP الذي تحتاجه للتواصل مع خدمة ويب.


4. استهلاك واجهة REST API في Flutter

أضف حزمة http

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.0

شغّل flutter pub get بعد الحفظ.

إجراء طلب GET

import 'package:http/http.dart' as http;
import 'dart:convert';
 
Future<void> fetchData() async {
  final response = await http.get(
    Uri.parse('https://dummyjson.com/posts'),
  );
 
  if (response.statusCode == 200) {
    final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
    final List<dynamic> data = decoded['posts'] as List<dynamic>;
    // parse data...
  } else {
    throw Exception('Request failed: ${response.statusCode}');
  }
}

http.get() غير متزامنة (Asynchronous) - تُرجع Future<Response>. استخدم await معها دائمًا.

المعمارية ثلاثية الطبقات مع مصدر بيانات عبر الشبكة

graph TD
    subgraph App
        UI[عنصر واجهة الشاشة\nStatefulWidget]
        BL[طبقة المنطق\nتصفّي وتحوّل]
        DL[طبقة البيانات\nاستدعاءات HTTP]
    end
    API[(REST API\ndummyjson.com)] --> DL
    DL --> BL
    BL --> UI
الطبقةالملفالمسؤولية
البياناتposts_data.dartfetchPosts() - يستدعي API، يُرجع List<PostModel>
المنطقposts_logic.dartيصفّي القائمة أو يحدّها أو يحوّلها
الواجهةposts_screen.dartتمتلك الحالة، تستدعي طبقة البيانات، تمرر النتائج لعناصر الواجهة
عنصر الواجهةpost_card.dartيعرض صفًا واحدًا من المنشورات

تحليل JSON إلى نموذج

عرّف صنف نموذج بواسطة بانٍ مصنعي (Factory) باسم fromJson:

class PostModel {
  final int id;
  final String title;
  final String body;
 
  const PostModel({required this.id, required this.title, required this.body});
 
  factory PostModel.fromJson(Map<String, dynamic> json) {
    return PostModel(
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
  }
}

ثم حوّل قائمة JSON (ملاحظة: يضع dummyjson.com المنشورات داخل مفتاح posts):

final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
final List<dynamic> data = decoded['posts'] as List<dynamic>;
final posts = data
    .map((item) => PostModel.fromJson(item as Map<String, dynamic>))
    .toList();

إدارة ثلاث حالات واجهة باستخدام setState

يجب أن تتعامل الشاشة التي تحمّل بيانات من الشبكة مع ثلاث حالات:

stateDiagram-v2
    [*] --> Loading: فتح الشاشة
    Loading --> Success: وصول البيانات
    Loading --> Error: فشل الطلب
    Error --> Loading: المستخدم ينقر إعادة المحاولة
    Success --> Loading: المستخدم ينقر تحديث
class _PostsScreenState extends State<PostsScreen> {
  List<PostModel> _posts = [];
  bool _isLoading = false;
  String? _error;
 
  Future<void> _loadPosts() async {
    setState(() { _isLoading = true; _error = null; });
 
    try {
      final posts = await PostsData.fetchPosts();
      setState(() { _posts = posts; _isLoading = false; });
    } catch (e) {
      setState(() { _error = e.toString(); _isLoading = false; });
    }
  }
 
  @override
  Widget build(BuildContext context) {
    if (_isLoading) return const Center(child: CircularProgressIndicator());
    if (_error != null) return Center(child: Text('Error: $_error'));
    return ListView.builder(
      itemCount: _posts.length,
      itemBuilder: (context, index) => PostCard(post: _posts[index]),
    );
  }
}

يُطلق كل استدعاء لـ setState() عملية إعادة بناء واحدة بالضبط بالقيم المُحدَّثة.

لماذا لا نستدعي API مباشرة داخل build()؟

يمكن استدعاء build() مرات عديدة. وضع http.get() داخل build() سيُطلِق طلب شبكة جديد مع كل إعادة بناء - وهذا يهدر النطاق الترددي (Bandwidth) ويسبب حلقات لا نهائية. بدلًا من ذلك، استدعِها من initState() أو من إجراء يقوم به المستخدم:

@override
void initState() {
  super.initState();
  _loadPosts();   // runs once when the widget first mounts
}

مهام المعمل

المهمة 1: ابنِ قائمة مدفوعة بالفهرس

  1. أنشئ List<String> تحتوي على 6 أسماء دورات على الأقل في كود Dart.
  2. اعرضها في ListView.builder باستخدام ListTile لكل عنصر.
  3. اضبط itemCount على courses.length (وليس رقمًا ثابتًا).
  4. أضف عنصرًا سابعًا إلى القائمة. تأكد أنه يظهر تلقائيًا دون تغيير ListView.builder.

الناتج المتوقع: قائمة قابلة للتمرير تعكس دائمًا الطول الفعلي للقائمة الأساسية.

المهمة 2: استخلص صنف عنصر واجهة لصف

  1. استخلص كل صف ListTile إلى StatelessWidget باسم CourseCard يستقبل معامل String name.
  2. في ListView.builder، استبدل ListTile المكتوب مباشرة بـ CourseCard(name: courses[index]).
  3. شغّل flutter analyze وأصلح أي تحذيرات.

الناتج المتوقع: ملف course_card.dart يحتوي على CourseCard، وملف courses_screen.dart نظيف.

المهمة 3: حاكِ وصحّح انهيارًا بسبب قيمة فارغة (null)

  1. أنشئ شاشة بسيطة تعرض نتيجة مستخدم من متغيّر int? userScore.
  2. اترك userScore فارغة (null) عمدًا واستدعِ .toString() عليها بطريقة قد تسبب انهيارًا (أو اعرضها عبر دمج النصوص لملاحظة سلامة القيم الفارغة - Null Safety).
  3. اقرأ سجل تبويب Run وحدد السطر المسبب للمشكلة.
  4. ضع نقطة توقف قبل ذلك السطر وشغّل في وضع التصحيح.
  5. افحص userScore في لوحة Variables. تأكد أنها null.
  6. أصلح المشكلة بتوفير قيمة افتراضية: userScore ?? 0.

الناتج المتوقع: تعرض الشاشة 0 بدلًا من الانهيار.

المهمة 4: اجلب منشورات من واجهة REST API

  1. أضف http: ^1.2.0 إلى pubspec.yaml وشغّل flutter pub get.
  2. أنشئ PostModel بالحقول id وtitle وbody وبانٍ مصنعي fromJson.
  3. أنشئ صنف PostsData بطريقة ثابتة fetchPosts() تستدعي https://dummyjson.com/posts.
  4. أنشئ PostsScreen - وهو StatefulWidget بمتغيرات حالة _isLoading و_error و_posts.
  5. استدعِ fetchPosts() داخل initState().
  6. اعرض CircularProgressIndicator أثناء التحميل، ورسالة خطأ عند الفشل، وListView.builder عند النجاح.

الناتج المتوقع: تعرض الشاشة مؤشر تحميل دائري، ثم قائمة بعناوين المنشورات المجلوبة من الإنترنت.

المهمة 5: استخلص PostCard وأضف استرجاعًا من الأخطاء

  1. استخلص صف القائمة إلى StatelessWidget باسم PostCard يستقبل PostModel.
  2. أضف زر إعادة محاولة إلى حالة الخطأ يستدعي _loadPosts() مرة أخرى.
  3. أضف أيقونة تحديث إلى AppBar تعيد تحميل القائمة عند النقر عليها.
  4. شغّل flutter analyze - أصلح كل المشكلات.

الناتج المتوقع: كود معياري (Modular) بملفات منفصلة للنموذج، والبيانات، والمنطق، والشاشة، وعنصر الواجهة. صفر مشكلات من المُحلِّل (Analyzer).


الملخص

المفهومالفكرة الأساسية
ListView.builderيبني فقط عناصر الواجهة المرئية بكسل (Lazy)؛ اضبط itemCount دائمًا على list.length
itemBuilderدالة يستدعيها Flutter لكل عنصر مرئي؛ تستقبل context وindex
صنف عنصر الواجهة للصفوفمُفضَّل على البناء المباشر - دورة حياة خاصة، قابل لإعادة الاستخدام، قابل للاختبار بشكل مستقل
مصحح Flutter (Debugger)ضع نقاط توقف، شغّل في وضع التصحيح، افحص لوحة Variables لإيجاد القيم الفارغة
تتبع المكدّس (Stack Trace)يشير مباشرة إلى الملف والسطر المسبب للانهيار - اقرأه دائمًا أولًا
APIواجهة برمجة التطبيقات - عقد برمجي بين نظامين
خدمة الويب (Web Service)واجهة API تعيش على الويب وتتواصل عبر HTTP/HTTPS
طلب/استجابة HTTPيرسل العميل طلبًا إلى خادم؛ ويرد الخادم باستجابة ورمز حالة (Status Code)
حزمة httpمكتبة Dart توفّر عميل HTTP - استخدم http.get() لجلب البيانات
بانٍ مصنعي fromJsonيحوّل خريطة (Map) JSON خامًا إلى كائن نموذج Dart مُحدَّد النوع
ثلاث حالات واجهةتحميل (CircularProgressIndicator)، خطأ (رسالة + إعادة محاولة)، نجاح (قائمة)
initState()المكان الصحيح لإطلاق أول استدعاء شبكة - يعمل مرة واحدة عند تركيب عنصر الواجهة