FutureBuilder

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:second_app/contact.dart';
import 'package:second_app/products.dart';
import 'colors.dart';
import 'package:http/http.dart';

class HomaPage extends StatefulWidget {
  HomaPage({super.key});
  @override
  State<HomaPage> createState() => _HomaPageState();
}

class _HomaPageState extends State<HomaPage> {
  final userNameController = TextEditingController();
  final formKey = GlobalKey<FormState>();
  String? userName;

  Future<List> getData() async {
    var response = await get(Uri.parse("https://fakestoreapi.com/products"));
    var resBody = jsonDecode(response.body);
    return resBody;
  }

  @override
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Fake Store")),
      drawer: Drawer(),
      body: FutureBuilder<List>(
      future: getData(),
       builder: (context , Snapshot){
        if(Snapshot.connectionState == ConnectionState.waiting) {
          return Center(child: CircularProgressIndicator.adaptive(),);

        }
        return Column(
        children: [
          Expanded(
            child: GridView.builder(
              padding: EdgeInsets.all(10),
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2, // عدد الأعمدة
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
                childAspectRatio: 0.7,
              ),
              itemCount: Snapshot.data!.length,
              itemBuilder: (context, i) {
                final item = Snapshot.data![i];
                return Card(
                  elevation: 2,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(7),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      // الصورة
                      Expanded(
                        child: ClipRRect(
                          borderRadius: BorderRadius.vertical(
                            top: Radius.circular(8),
                          ),
                          child: Image.network(
                            item['image'],
                            width: double.infinity,
                            fit: BoxFit.cover,
                          ),
                        ),
                      ),

                      // النصوص
                      Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              item['title'],
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontWeight: FontWeight.bold,
                                color: Colors.blue,
                              ),
                            ),
                            Text(
                              item['description'],
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(),
                            ),
                            SizedBox(height: 4),
                            Text(
                              "\$${item['price']}",
                              style: TextStyle(
                                fontSize: 16,
                                fontWeight: FontWeight.w600,
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
        ],
      );
      }),
    );
  }
}
Dart
ما الذي يفعله الكود؟
  • يجلب منتجات من API عام: https://fakestoreapi.com/products.
  • يعرضها في Grid (شبكة بطاقات) تحتوي صورة، عنوان، وصف، وسعر.
  • يعتمد على FutureBuilder<List> لربط نتيجة الطلب غير المتزامن (Future) بواجهة Flutter.

  • اخترت StatefulWidget لأن عندك حالة (جلب بيانات لاحقًا).
  • فيه حقول:
    • userNameController, formKey, userName (حالياً غير مستخدمة في العرض).
  • دالة الجلب: Future<List> getData() async { var response = await get(Uri.parse("https://fakestoreapi.com/products")); var resBody = jsonDecode(response.body); return resBody; } ترجع Future<List> (القائمة القادمة من الـ API).

يُفضّل إضافة التحقق من statusCode والتعامل مع الأخطاء، وكتابة النوع بشكل أدق:
Future<List<Map<String, dynamic>>>.

3) الواجهة العامة
return Scaffold(
  appBar: AppBar(title: Text("Fake Store")),
  drawer: Drawer(),
  body: FutureBuilder<List>(
    future: getData(),
    builder: (context, Snapshot) {
      if (Snapshot.connectionState == ConnectionState.waiting) {
        return Center(child: CircularProgressIndicator.adaptive());
      }
      return Column(
        children: [
          Expanded(
            child: GridView.builder(
              ...
              itemCount: Snapshot.data!.length,
              itemBuilder: (context, i) {
                final item = Snapshot.data![i];
                ...
              },
            ),
          ),
        ],
      );
    },
  ),
);
Dart
شرح FutureBuilder بالكامل (النجمة هنا ⭐)

FutureBuilder<T> هو ودجت يبني نفسه بناءً على حالة Future (طلب غير متزامن) ويعطيك AsyncSnapshot<T> داخل builder. أهم النقاط:

1) الخصائص الأساسية
  • future: الـ Future الذي تنتظر نتيجته (مثلاً getData()).
  • builder: دالة تُستدعى في كل تغيير بالحالة (قبل وأثناء وبعد اكتمال الـ Future). تستقبل:
    • BuildContext
    • AsyncSnapshot<T> snapshot (في كودك مسميه Snapshot)
2) ما هو AsyncSnapshot؟

يحمل حالة البيانات في لحظة البناء:

  • connectionState: حالة الاتصال/التنفيذ:
    • ConnectionState.none: لا يوجد Future مرتبط (أو future=null).
    • ConnectionState.waiting: الـ Future قيد التنفيذ (نُظهر Loading).
    • ConnectionState.active: (أكثر مع StreamBuilder؛ نادرًا مع Future).
    • ConnectionState.done: الـ Future اكتمل (نجاح/فشل).
  • hasData و**data**: هل هناك بيانات؟ وما هي؟
  • hasError و**error**: هل حدث خطأ؟ وما رسالته؟
3) دورة الحياة والسلوك
  • عند تمرير future: getData(): يتم استدعاؤه، ويبدأ البناء:
    1. في البداية: waiting → اعرض مؤشّر تحميل.
    2. عند اكتمال الـ Future:
      • لو نجح: done, hasData=true → اعرض البيانات.
      • لو فشل: done, hasError=true → اعرض رسالة خطأ/زر إعادة محاولة.
  • تنبّه مهم: استدعاء getData() داخل build يعني أنها ستُستدعى كل مرة يُعاد بناء الودجت (قد يُعيد Flutter البناء لأسباب عديدة)، مما يسبب طلبات متكررة. الحل الأفضل: خزّن الـ Future مرة واحدة في initState، ومرّره للـ FutureBuilder.
4) ميزات قوية لـ FutureBuilder
  • يفصل منطق الحالات (Loading / Data / Error) في مكان واحد أنيق.
  • إعادة البناء تلقائيًا عند اكتمال الـ Future.
  • initialData: يمكنك توفير قيمة مبدئية لعرض Placeholder قبل وصول النتيجة.
  • التعامل الآمن مع الأخطاء: عبر snapshot.hasError.
  • إدارة بسيطة دون setState: لا تحتاج setState لتحديث الواجهة عند اكتمال الـ Future؛ FutureBuilder يتكفّل بهذا.
5) أفضل الممارسات مع FutureBuilder
  • خزّن الـ Future في متغير: late Future<List<Map<String, dynamic>>> _future; @override void initState() { super.initState(); _future = getData(); } ثم: FutureBuilder(future: _future, builder: ...); لتفادي إعادة الطلب عند كل build.
  • تحقق من الأخطاء: if (snapshot.hasError) { ... }
  • تحقق من البيانات قبل الوصول إليها: if (!snapshot.hasData || snapshot.data!.isEmpty) { ... }
  • لا تضع منطق ثقيل داخل builder؛ اجعله لعرض الحالة فقط.
  • إذا احتجت تحديث البيانات (Refresh)، أعد تعيين _future = getData(); setState(() {});.

متى أستخدم StreamBuilder بدل FutureBuilder؟
عندما لديك تدفق مستمر من البيانات (WebSocket, Firestore streams, …) وليس عملية واحدة وتنتهي.

شرح بقية الواجهة
Grid & Card
  • GridView.builder: إنشاء شبكة عناصر بكفاءة (يبني العناصر عند الحاجة).
  • SliverGridDelegateWithFixedCrossAxisCount: تحدد عدد الأعمدة، المسافات، ونسبة البُعد (Aspect ratio).
  • itemBuilder: يبني بطاقة لكل منتج:
    • Card + ClipRRect: لتطبيق زوايا مستديرة على الصورة.
    • Image.network: تحميل صورة من رابط (يستحسن إضافة errorBuilder وloadingBuilder).
    • Text مع maxLines وoverflow لتفادي تمدد النص.
    • السعر: "\$${item['price']}" (هروب الـ $ وInterpolation للـ double).

ملاحظات وتحسينات سريعة على كودك
  • عندك سطرين @override متتالين فوق build — احذف التكرار.
  • تسمية الكلاس: HomaPage يبدو خطأ مطبعي (ربما HomePage؟).
  • FutureBuilder<List>: يفضَّل النوع التفصيلي:
    FutureBuilder<List<Map<String, dynamic>>>.
  • استدعاء getData() داخل build يؤدي لإعادة الطلب — خزّنه في initState.
  • تحقّق من الأخطاء وكود الحالة قبل jsonDecode.
  • أضف حالات:
    • Error UI: عند snapshot.hasError.
    • Empty State: عند عدم وجود بيانات.
  • في الصور:
    • أضف errorBuilder وloadingBuilder لتحسين التجربة.
  • استخدم const حيث يمكن (Performance بسيطة).
  • سمِّ http: import 'package:http/http.dart' as http; واستخدم http.get.

نسخة مُحسّنة مقتضبة (تعالج النقاط أعلاه)
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Future<List<Map<String, dynamic>>> _future;

  Future<List<Map<String, dynamic>>> getData() async {
    final uri = Uri.parse("https://fakestoreapi.com/products");
    final response = await http.get(uri);

    if (response.statusCode >= 200 && response.statusCode < 300) {
      final decoded = jsonDecode(response.body);
      return List<Map<String, dynamic>>.from(decoded);
    } else {
      throw Exception("HTTP ${response.statusCode}");
    }
  }

  @override
  void initState() {
    super.initState();
    _future = getData(); // لا نعيد الطلب في كل build
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Fake Store")),
      drawer: const Drawer(),
      body: FutureBuilder<List<Map<String, dynamic>>>(
        future: _future,
        builder: (context, snapshot) {
          // حالة التحميل
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator.adaptive());
          }

          // حالة الخطأ
          if (snapshot.hasError) {
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('فشل التحميل: ${snapshot.error}'),
                  const SizedBox(height: 8),
                  ElevatedButton(
                    onPressed: () {
                      setState(() => _future = getData());
                    },
                    child: const Text('إعادة المحاولة'),
                  ),
                ],
              ),
            );
          }

          // حالة لا توجد بيانات
          final data = snapshot.data;
          if (data == null || data.isEmpty) {
            return const Center(child: Text('لا توجد بيانات لعرضها.'));
          }

          // عرض الشبكة
          return GridView.builder(
            padding: const EdgeInsets.all(10),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,      // عدد الأعمدة
              crossAxisSpacing: 10,
              mainAxisSpacing: 10,
              childAspectRatio: 0.7,  // نسبة الطول للعرض للبطاقة
            ),
            itemCount: data.length,
            itemBuilder: (context, i) {
              final item = data[i];
              return Card(
                elevation: 2,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(7),
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // الصورة
                    Expanded(
                      child: ClipRRect(
                        borderRadius: const BorderRadius.vertical(
                          top: Radius.circular(8),
                        ),
                        child: Image.network(
                          item['image'] ?? '',
                          width: double.infinity,
                          fit: BoxFit.cover,
                          // تحسينات اختيارية:
                          errorBuilder: (_, __, ___) => const Center(child: Icon(Icons.broken_image)),
                          loadingBuilder: (context, child, progress) {
                            if (progress == null) return child;
                            return const Center(child: CircularProgressIndicator.adaptive());
                          },
                        ),
                      ),
                    ),
                    // النصوص
                    Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            item['title'] ?? '',
                            maxLines: 2,
                            overflow: TextOverflow.ellipsis,
                            style: const TextStyle(
                              fontWeight: FontWeight.bold,
                              color: Colors.blue,
                            ),
                          ),
                          Text(
                            item['description'] ?? '',
                            maxLines: 2,
                            overflow: TextOverflow.ellipsis,
                          ),
                          const SizedBox(height: 4),
                          Text(
                            "\$${item['price']}",
                            style: const TextStyle(
                              fontSize: 16,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              );
            },
          );
        },
      ),
    );
  }
}
Dart

خلاصة سريعة عن FutureBuilder
  • يربط واجهتك بحالة Future بدون ما تدير setState بنفسك.
  • يتعامل مع 3 حالات أساسية: التحميل، الخطأ، البيانات.
  • استخدم snapshot.connectionState, snapshot.hasError, snapshot.hasData.
  • لا تنادِ getData() داخل build مباشرة؛ خزّنه في متغير Future في initState.
  • لو احتجت تحديث/تجديد البيانات، أعد تعيين الـ Future داخل setState.