إعداد Flutter والتطبيق الأول

تطبيقك الأول في Flutter

55 دقيقة الدرس 5 من 12

إنشاء مشروع Flutter الأول

الآن بعد تثبيت Flutter وتهيئته حان الوقت لبناء تطبيقك الأول. يوفر Flutter قالب تطبيق عداد افتراضي يوضح المفاهيم الأساسية مثل الودجات وإدارة الحالة وشجرة الودجات. في هذا الدرس سننشئ ونستكشف ونعدل ونشغل هذا التطبيق خطوة بخطوة.

إنشاء مشروع Flutter جديد

flutter create my_first_app
cd my_first_app

ينشئ هذا الأمر مشروع Flutter كاملاً بهيكل مجلدات منظم. الملف الرئيسي الذي سنركز عليه هو lib/main.dart الذي يحتوي على نقطة دخول التطبيق.

ملاحظة: أسماء مشاريع Flutter يجب أن تكون بأحرف صغيرة مع شرطات سفلية (snake_case). مثلاً my_first_app صالح لكن MyFirstApp أو my-first-app غير صالحين.

فهم تطبيق العداد الافتراضي

عند فتح lib/main.dart سترى تطبيق العداد الافتراضي. دعنا نحلله قطعة بقطعة لفهم كل جزء.

دالة main()

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

دالة main() هي نقطة دخول كل تطبيق Dart. دالة runApp() تأخذ ودجت وتجعلها جذر شجرة الودجات. كل تطبيق Flutter يبدأ من هنا.

MaterialApp -- الأساس

ودجت MaterialApp هي الودجت العليا التي تغلف تطبيقك بالكامل. توفر تصميم Material Design والتنقل والسمات والعديد من الميزات الأخرى جاهزة للاستخدام.

MyApp كـ StatelessWidget

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

الخصائص الرئيسية لـ MaterialApp:

  • title -- عنوان التطبيق المعروض في محول المهام
  • theme -- يحدد المظهر المرئي (الألوان والخطوط والأشكال)
  • home -- الودجت المعروضة عند بدء التطبيق
  • useMaterial3: true -- يفعل أحدث تصميم Material Design 3
نصيحة: ColorScheme.fromSeed() ينشئ لوحة ألوان كاملة من لون بذرة واحد. هذه ميزة في Material Design 3 تضمن ألواناً متسقة وسهلة الوصول في تطبيقك.

Scaffold -- هيكل الصفحة

ودجت Scaffold توفر هيكل التخطيط المرئي الأساسي لصفحة Material Design. تمنحك شريط تطبيق ومنطقة محتوى وزر عائم وأدراج جانبية والمزيد.

MyHomePage كـ StatefulWidget

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

هذا StatefulWidget مما يعني أنه يمكنه الاحتفاظ بالحالة وتغييرها بمرور الوقت. دالة createState() تعيد كائن State المرتبط حيث توجد البيانات القابلة للتغيير ومنطق البناء.

State و setState() ودالة Build

فئة State هي حيث يحدث السحر. تحتفظ بالبيانات القابلة للتغيير (_counter) وتحدد كيفية بناء وإعادة بناء واجهة المستخدم عند تغير الحالة.

فئة State

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '\$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
مهم: استدعِ دائماً setState() عند تغيير متغيرات الحالة. بدونها لن يعرف Flutter أن واجهة المستخدم تحتاج إلى إعادة بناء ولن تظهر تغييراتك على الشاشة. لا تعدل متغيرات الحالة خارج setState() في StatefulWidget.

فهم الودجات الرئيسية

دعنا نفحص كل ودجت مستخدمة في تطبيق العداد:

AppBar

ودجت AppBar تنشئ شريط أدوات أعلى الشاشة. تحتوي عادة على عنوان وأيقونات تنقل وأزرار إجراءات.

خصائص AppBar

AppBar(
  backgroundColor: Theme.of(context).colorScheme.inversePrimary,
  title: Text(widget.title),
  // خصائص اختيارية:
  // leading: IconButton(icon: Icon(Icons.menu), onPressed: () {}),
  // actions: [IconButton(icon: Icon(Icons.search), onPressed: () {})],
  // centerTitle: true,
  // elevation: 4.0,
)

Center و Column والتخطيط

ودجت Center تركز ابنها في وسطها. Column ترتب أبناءها عمودياً. معاً يضعان المحتوى في منتصف الشاشة مكدساً من الأعلى للأسفل.

ودجت Text

ودجت Text تعرض سلسلة نصية بنمط واحد. يمكنك تخصيصها بمعامل style.

أمثلة ودجت Text

// نص بسيط
const Text('مرحباً Flutter!')

// نص منسق
Text(
  'العداد: \$_counter',
  style: Theme.of(context).textTheme.headlineMedium,
)

// نص بتنسيق مخصص
Text(
  'نص مخصص',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.blue,
  ),
)

FloatingActionButton

زر الإجراء العائم (FloatingActionButton أو FAB) هو زر دائري يطفو فوق المحتوى. يُستخدم عادة للإجراء الرئيسي على الشاشة.

خصائص FloatingActionButton

FloatingActionButton(
  onPressed: _incrementCounter,  // الدالة المستدعاة عند النقر
  tooltip: 'Increment',          // يظهر عند الضغط المطول
  child: const Icon(Icons.add),   // الأيقونة المعروضة
  // خصائص اختيارية:
  // backgroundColor: Colors.green,
  // foregroundColor: Colors.white,
  // mini: true,  // نسخة أصغر
)

تعديل تطبيق العداد

الآن دعنا نخصص التطبيق. سنغير الألوان ونضيف زر إنقاص ونعدل النص.

تغيير مخطط الألوان

ألوان سمة مخصصة

MaterialApp(
  title: 'My Counter App',
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
    useMaterial3: true,
  ),
  home: const MyHomePage(title: 'My Counter App'),
)

إضافة زر إنقاص

دعنا نضيف زراً ثانياً لإنقاص العداد. سنضيف أيضاً زر إعادة تعيين.

State معدلة بإجراءات متعددة

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  void _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  void _resetCounter() {
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _resetCounter,
            tooltip: 'Reset',
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('قيمة العداد:'),
            Text(
              '\$_counter',
              style: Theme.of(context).textTheme.displayMedium?.copyWith(
                color: _counter >= 0 ? Colors.teal : Colors.red,
              ),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: _decrementCounter,
                  icon: const Icon(Icons.remove),
                  label: const Text('إنقاص'),
                ),
                const SizedBox(width: 16),
                ElevatedButton.icon(
                  onPressed: _incrementCounter,
                  icon: const Icon(Icons.add),
                  label: const Text('زيادة'),
                ),
              ],
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
نصيحة: ودجت SizedBox تُستخدم بشكل شائع لإضافة مسافات بين الودجات. SizedBox(height: 20) تضيف 20 بكسل منطقي من المسافة العمودية و SizedBox(width: 16) تضيف مسافة أفقية.

تشغيل التطبيق

هناك عدة طرق لتشغيل تطبيق Flutter:

استخدام الطرفية

التشغيل من سطر الأوامر

# التشغيل على الجهاز الافتراضي
flutter run

# التشغيل على جهاز محدد
flutter run -d chrome          # متصفح الويب
flutter run -d emulator-5554   # محاكي Android
flutter run -d iPhone          # محاكي iOS

# عرض الأجهزة المتاحة أولاً
flutter devices

استخدام VS Code

في VS Code يمكنك الضغط على F5 أو الذهاب إلى Run > Start Debugging. تأكد من اختيار جهاز في شريط الحالة السفلي. إضافة Flutter توفر محدد أجهزة مريح.

استخدام Android Studio

في Android Studio اختر جهازاً من القائمة المنسدلة في شريط الأدوات ثم انقر على زر Run الأخضر أو اضغط Shift + F10.

ملاحظة: عند تشغيل تطبيق Flutter لأول مرة قد يستغرق البناء عدة دقائق. ستكون عمليات التشغيل اللاحقة أسرع بكثير بسبب الترجمة التزايدية وقدرة إعادة التحميل السريع في Dart VM.

فهم شجرة الودجات

كل تطبيق Flutter مبني من شجرة ودجات. لتطبيق العداد تبدو شجرة الودجات هكذا:

هيكل شجرة الودجات

MaterialApp
  > MyHomePage (StatefulWidget)
    > Scaffold
      > AppBar
        > Text (العنوان)
      > Center (المحتوى)
        > Column
          > Text ('قيمة العداد:')
          > Text (_counter)
          > SizedBox
          > Row
            > ElevatedButton (إنقاص)
            > SizedBox
            > ElevatedButton (زيادة)
      > FloatingActionButton
        > Icon (إضافة)

فهم شجرة الودجات أساسي. كل عنصر مرئي على الشاشة هو ودجت والودجات تتركب معاً في تسلسل هرمي شجري. عند استدعاء setState() يعيد Flutter بناء الجزء المتأثر من الشجرة بكفاءة.

إضافة تنسيق مخصص

دعنا نحسن تطبيقنا ببعض التنسيق الإضافي ليكون أكثر جاذبية بصرياً.

محتوى محسن بتنسيق مخصص

body: Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [
        Theme.of(context).colorScheme.primaryContainer,
        Theme.of(context).colorScheme.surface,
      ],
    ),
  ),
  child: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          Icons.touch_app,
          size: 48,
          color: Theme.of(context).colorScheme.primary,
        ),
        const SizedBox(height: 16),
        const Text(
          'انقر الأزرار لتغيير العداد',
          style: TextStyle(fontSize: 16),
        ),
        const SizedBox(height: 24),
        Container(
          padding: const EdgeInsets.all(24),
          decoration: BoxDecoration(
            color: Theme.of(context).colorScheme.surface,
            borderRadius: BorderRadius.circular(16),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.1),
                blurRadius: 10,
                offset: const Offset(0, 4),
              ),
            ],
          ),
          child: Text(
            '\$_counter',
            style: Theme.of(context).textTheme.displayLarge,
          ),
        ),
      ],
    ),
  ),
),

أخطاء المبتدئين الشائعة

إليك الأخطاء الشائعة التي يرتكبها مطورو Flutter الجدد وكيفية تجنبها:

  • نسيان مُنشئات const: استخدم const للودجات التي لا تتغير أبداً لتحسين الأداء
  • تعديل الحالة بدون setState(): لن يتم تحديث واجهة المستخدم ما لم تغلف التغييرات في setState()
  • نسيان الفواصل: Dart تستخدم الفواصل اللاحقة بكثرة -- أضفها بعد كل معامل للحصول على تنسيق أفضل
  • نسيان الاستيراد: ودجات Material تتطلب import 'package:flutter/material.dart';
خطأ شائع: لا تجرِ عمليات حسابية ثقيلة أو عمليات غير متزامنة داخل setState(). يجب أن يقوم الاستدعاء المُمرر إلى setState() فقط بتحديث متغيرات الحالة. نفذ المنطق أولاً ثم استدعِ setState() لتحديث واجهة المستخدم.

ملخص

في هذا الدرس تعلمت كيفية:

  • إنشاء مشروع Flutter جديد بأمر flutter create
  • فهم هيكل تطبيق العداد الافتراضي
  • استخدام MaterialApp و Scaffold و AppBar و FloatingActionButton
  • العمل مع StatefulWidget و setState()
  • تعديل التطبيق بتغيير الألوان وإضافة أزرار وتخصيص النص
  • تشغيل التطبيق على أجهزة مختلفة باستخدام الطرفية أو بيئة التطوير
  • فهم تسلسل شجرة الودجات الهرمي

تمرين عملي

عدل تطبيق العداد ليتضمن: (1) عنوان يعرض “عدادي المخصص” في شريط التطبيق، (2) قيمة العداد تغير لونها بناءً على ما إذا كانت موجبة (أخضر) أو سالبة (أحمر) أو صفر (رمادي)، (3) ثلاثة أزرار: زيادة بـ 1 وإنقاص بـ 1 وزيادة بـ 5، (4) زر إعادة تعيين في إجراءات شريط التطبيق. اختبر التطبيق على جهازك المفضل أو المحاكي.