بناء وتشغيل تطبيق مبتدئ كامل
المشروع التطبيقي: تطبيق بطاقة الملف الشخصي
في هذا الدرس الأخير من برنامج تعليم إعداد Flutter والتطبيق الأول، ستبني تطبيق بطاقة ملف شخصي كامل من الصفر. يجمع هذا المشروع التطبيقي كل ما تعلمته: إنشاء مشروع، تكوين pubspec.yaml، بناء شجرة عناصر الواجهة، إضافة خطوط وألوان مخصصة، استخدام إعادة التحميل السريع، تصحيح الأخطاء، والبناء للإصدار. تابع خطوة بخطوة لإنشاء تطبيق مصقول واحترافي المظهر.
الخطوة 1: إنشاء المشروع
لنبدأ بإنشاء مشروع Flutter جديد باسم منظمة مخصص.
إنشاء المشروع
# إنشاء المشروع
flutter create --org com.example profile_card_app
# الانتقال إلى المشروع
cd profile_card_app
# الفتح في بيئة التطوير
code . # VS Code
# أو
studio . # Android Studio
نظرة عامة على هيكل المشروع
بعد الإنشاء، يحتوي مشروعك على هذا الهيكل:
هيكل مجلد المشروع
profile_card_app/
├── android/ # تكوين خاص بـ Android
├── ios/ # تكوين خاص بـ iOS
├── lib/ # كود Dart الخاص بك هنا
│ └── main.dart # نقطة دخول التطبيق
├── test/ # ملفات الاختبار
├── web/ # ملفات منصة الويب
├── pubspec.yaml # تكوين المشروع
├── pubspec.lock # ملف قفل التبعيات
├── analysis_options.yaml # قواعد الفحص
└── README.md
الخطوة 2: تكوين pubspec.yaml
قبل كتابة أي كود، لنقم بتكوين تبعيات وأصول مشروعنا.
pubspec.yaml المحدّث
name: profile_card_app
description: A personal profile card app built with Flutter.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
google_fonts: ^6.1.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
flutter:
uses-material-design: true
assets:
- assets/images/
إعداد مجلد الأصول
أنشئ مجلد الأصول وأضف صورة ملف شخصي نائبة:
إنشاء مجلدات الأصول
# إنشاء مجلد الأصول
mkdir -p assets/images
# أضف صورة ملف شخصي إلى assets/images/
# يمكنك استخدام أي ملف صورة باسم profile.jpg أو profile.png
# في الوقت الحالي، سنستخدم صورة نائبة من الشبكة
# تثبيت التبعيات
flutter pub get
assets/images/ وتستخدم Image.asset().الخطوة 3: تعريف نظام الألوان والسمة
لننشئ نظام ألوان نظيف واحترافي لتطبيقنا. سنحدد سمتنا في main.dart.
lib/main.dart - نقطة دخول التطبيق والسمة
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const ProfileCardApp());
}
class ProfileCardApp extends StatelessWidget {
const ProfileCardApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Profile Card',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1A73E8),
brightness: Brightness.light,
),
textTheme: GoogleFonts.poppinsTextTheme(),
useMaterial3: true,
),
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1A73E8),
brightness: Brightness.dark,
),
textTheme: GoogleFonts.poppinsTextTheme(
ThemeData.dark().textTheme,
),
useMaterial3: true,
),
home: const ProfileScreen(),
);
}
}
ColorScheme.fromSeed() وهي طريقة Material 3 لإنشاء لوحة ألوان متناغمة من لون بذرة واحد. هذا يضمن أن جميع ألوانك تبدو جيدة معاً تلقائياً.الخطوة 4: بناء شاشة الملف الشخصي
الآن لنبني شاشة الملف الشخصي الرئيسية مع Scaffold و AppBar وجسم قابل للتمرير.
عنصر واجهة ProfileScreen
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('My Profile'),
centerTitle: true,
backgroundColor: colorScheme.primaryContainer,
foregroundColor: colorScheme.onPrimaryContainer,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const SizedBox(height: 20),
const ProfileHeader(),
const SizedBox(height: 24),
const ProfileInfoCard(),
const SizedBox(height: 16),
const SkillsCard(),
const SizedBox(height: 16),
const ContactCard(),
const SizedBox(height: 20),
],
),
),
);
}
}
الخطوة 5: إنشاء رأس الملف الشخصي
يعرض رأس الملف الشخصي صورة المستخدم الرمزية والاسم والمسمى الوظيفي.
عنصر واجهة ProfileHeader
class ProfileHeader extends StatelessWidget {
const ProfileHeader({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
// الصورة الرمزية للملف الشخصي
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: colorScheme.primary,
width: 3,
),
boxShadow: [
BoxShadow(
color: colorScheme.primary.withOpacity(0.3),
blurRadius: 12,
spreadRadius: 2,
),
],
),
child: CircleAvatar(
radius: 60,
backgroundColor: colorScheme.primaryContainer,
child: Icon(
Icons.person,
size: 60,
color: colorScheme.onPrimaryContainer,
),
),
),
const SizedBox(height: 16),
// الاسم
Text(
'Edrees Salih',
style: textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
),
const SizedBox(height: 4),
// المسمى الوظيفي
Text(
'Flutter & Web Developer',
style: textTheme.titleMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 8),
// الموقع
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.location_on_outlined,
size: 16,
color: colorScheme.onSurfaceVariant,
),
const SizedBox(width: 4),
Text(
'Saudi Arabia',
style: textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
),
],
);
}
}
الخطوة 6: إنشاء بطاقة معلومات الملف الشخصي
تعرض هذه البطاقة نبذة مختصرة وإحصائيات رئيسية.
عنصر واجهة ProfileInfoCard
class ProfileInfoCard extends StatelessWidget {
const ProfileInfoCard({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// عنوان القسم
Row(
children: [
Icon(Icons.info_outline, color: colorScheme.primary),
const SizedBox(width: 8),
Text(
'نبذة عني',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(height: 24),
// نص النبذة
Text(
'مطور شغوف بخبرة في Flutter و Laravel '
'وتقنيات الويب الحديثة. أحب بناء تطبيقات '
'جميلة وعملية تحل مشاكل حقيقية.',
style: textTheme.bodyLarge?.copyWith(
height: 1.6,
color: colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 20),
// صف الإحصائيات
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStat(context, '+5', 'سنوات خبرة'),
_buildStat(context, '+50', 'مشروع'),
_buildStat(context, '+10', 'دورة'),
],
),
],
),
),
);
}
Widget _buildStat(BuildContext context, String value, String label) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Column(
children: [
Text(
value,
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: colorScheme.primary,
),
),
const SizedBox(height: 4),
Text(
label,
style: textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
],
);
}
}
الخطوة 7: إنشاء بطاقة المهارات
تعرض بطاقة المهارات كفاءات التقنية باستخدام عناصر Chip داخل عنصر Wrap.
عنصر واجهة SkillsCard
class SkillsCard extends StatelessWidget {
const SkillsCard({super.key});
static const List<Map<String, dynamic>> skills = [
{'name': 'Flutter', 'icon': Icons.phone_android},
{'name': 'Dart', 'icon': Icons.code},
{'name': 'Laravel', 'icon': Icons.web},
{'name': 'PHP', 'icon': Icons.data_object},
{'name': 'JavaScript', 'icon': Icons.javascript},
{'name': 'React', 'icon': Icons.display_settings},
{'name': 'MySQL', 'icon': Icons.storage},
{'name': 'Git', 'icon': Icons.merge_type},
{'name': 'Firebase', 'icon': Icons.cloud},
{'name': 'REST API', 'icon': Icons.api},
];
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// عنوان القسم
Row(
children: [
Icon(Icons.build_outlined, color: colorScheme.primary),
const SizedBox(width: 8),
Text(
'المهارات',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(height: 24),
// شرائح المهارات
Wrap(
spacing: 8,
runSpacing: 8,
children: skills.map((skill) {
return Chip(
avatar: Icon(
skill['icon'] as IconData,
size: 18,
color: colorScheme.onSecondaryContainer,
),
label: Text(skill['name'] as String),
backgroundColor: colorScheme.secondaryContainer,
labelStyle: TextStyle(
color: colorScheme.onSecondaryContainer,
),
side: BorderSide.none,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
);
}).toList(),
),
],
),
),
);
}
}
الخطوة 8: إنشاء بطاقة الاتصال
تعرض بطاقة الاتصال معلومات الاتصال مع أزرار تفاعلية.
عنصر واجهة ContactCard
class ContactCard extends StatelessWidget {
const ContactCard({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Card(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// عنوان القسم
Row(
children: [
Icon(Icons.contact_mail_outlined, color: colorScheme.primary),
const SizedBox(width: 8),
Text(
'الاتصال',
style: textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const Divider(height: 24),
// عناصر الاتصال
_buildContactItem(
context,
Icons.email_outlined,
'البريد الإلكتروني',
'edrees@example.com',
),
const SizedBox(height: 12),
_buildContactItem(
context,
Icons.language,
'الموقع الإلكتروني',
'esb1995.com',
),
const SizedBox(height: 12),
_buildContactItem(
context,
Icons.code,
'GitHub',
'github.com/edrees',
),
const SizedBox(height: 20),
// أزرار الإجراء
Row(
children: [
Expanded(
child: FilledButton.icon(
onPressed: () {
debugPrint('Send message tapped');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('ميزة الرسائل قادمة قريباً!'),
),
);
},
icon: const Icon(Icons.send),
label: const Text('رسالة'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: () {
debugPrint('Share profile tapped');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('ميزة المشاركة قادمة قريباً!'),
),
);
},
icon: const Icon(Icons.share),
label: const Text('مشاركة'),
),
),
],
),
],
),
),
);
}
Widget _buildContactItem(
BuildContext context,
IconData icon,
String label,
String value,
) {
final colorScheme = Theme.of(context).colorScheme;
final textTheme = Theme.of(context).textTheme;
return Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
size: 20,
color: colorScheme.onPrimaryContainer,
),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
Text(
value,
style: textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
),
),
],
),
],
);
}
}
الخطوة 9: استخدام إعادة التحميل السريع للتكرار
الآن بعد بناء تطبيقك، لنتدرب على استخدام إعادة التحميل السريع لإجراء تغييرات فورية.
سير عمل إعادة التحميل السريع
# شغّل التطبيق
flutter run
# قم بتغيير في الكود، مثلاً غيّر الاسم:
# 'Edrees Salih' -> 'اسمك هنا'
# اضغط 'r' في الطرفية لإعادة التحميل السريع
# أو اضغط Ctrl+S (VS Code) / Cmd+S (macOS)
# يظهر التغيير فوراً دون فقدان حالة التطبيق!
# جرّب هذه التغييرات وأعد التحميل السريع لكل واحدة:
# 1. غيّر لون البذرة: Color(0xFF1A73E8) -> Color(0xFF6750A4)
# 2. غيّر نص النبذة
# 3. أضف مهارة جديدة لقائمة المهارات
# 4. غيّر معلومات الاتصال
R) بدلاً من ذلك.الخطوة 10: تصحيح خطأ متعمد
لنُدخل خطأ عمداً ونتدرب على تصحيحه. هذا التمرين يعلمك كيفية قراءة رسائل الخطأ وإصلاح المشاكل الشائعة.
إدخال خطأ تخطيط
// استبدل صف الإحصائيات في ProfileInfoCard بهذا الكود المعيب:
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStat(context, '+5', 'سنوات خبرة في التطوير'),
_buildStat(context, '+50', 'مشاريع مكتملة تم تسليمها'),
_buildStat(context, '+10', 'دورات عبر الإنترنت منشورة'),
],
)
// هذا يسبب تجاوز RenderFlex لأن النص الطويل
// لا يتسع في Row!
// الخطأ: A RenderFlex overflowed by 42 pixels on the right.
// The relevant error-causing widget was: Row
// الإصلاح: اجعل تسميات الإحصائيات أقصر، أو لف كل إحصائية
// في عنصر Expanded:
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(child: _buildStat(context, '+5', 'سنوات')),
Expanded(child: _buildStat(context, '+50', 'مشروع')),
Expanded(child: _buildStat(context, '+10', 'دورة')),
],
)
التصحيح بنقاط التوقف
// أضف نقطة توقف في ProfileInfoCard.build()
// 1. انقر على الهامش بجوار: final colorScheme = ...
// 2. شغّل في وضع التصحيح (F5)
// 3. انتقل إلى شاشة الملف الشخصي
// 4. يتوقف التنفيذ عند نقطة التوقف
// في شريط التصحيح الجانبي، افحص:
// - context.widget.runtimeType
// - colorScheme.primary (شاهد اللون الفعلي)
// - textTheme.titleLarge (شاهد خصائص الخط)
// أضف تعبير مراقبة:
// MediaQuery.of(context).size.width
// هذا يُظهر لك عرض الشاشة، مفيد للتصميم المتجاوب
الخطوة 11: البناء للإصدار
الآن بعد أن يعمل تطبيقك بشكل صحيح، لنبنه للإصدار.
بناء نسخة الإصدار
# أولاً، حلل الكود بحثاً عن أي مشاكل
flutter analyze
# شغّل جميع الاختبارات
flutter test
# بناء APK للإصدار (Android)
flutter build apk --split-per-abi
# بناء App Bundle لـ Play Store (Android)
flutter build appbundle
# بناء للويب
flutter build web
# بناء لـ iOS (macOS فقط)
flutter build ios
إيجاد مخرجات البناء
# ملفات APK (مقسمة حسب المعمارية):
# build/app/outputs/flutter-apk/app-arm64-v8a-release.apk (~15 MB)
# build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk (~12 MB)
# build/app/outputs/flutter-apk/app-x86_64-release.apk (~15 MB)
# App Bundle:
# build/app/outputs/bundle/release/app-release.aab
# مخرجات الويب:
# build/web/index.html (والملفات المساعدة)
الخطوة 12: ملف main.dart الكامل
إليك ملف main.dart الكامل مع جميع العناصر التي بنيناها. يمكنك نسخ هذا الملف بالكامل للحصول على التطبيق العامل:
lib/main.dart الكامل
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const ProfileCardApp());
}
class ProfileCardApp extends StatelessWidget {
const ProfileCardApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Profile Card',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF1A73E8),
brightness: Brightness.light,
),
textTheme: GoogleFonts.poppinsTextTheme(),
useMaterial3: true,
),
home: const ProfileScreen(),
);
}
}
class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('My Profile'),
centerTitle: true,
backgroundColor: colorScheme.primaryContainer,
foregroundColor: colorScheme.onPrimaryContainer,
),
body: const SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
SizedBox(height: 20),
ProfileHeader(),
SizedBox(height: 24),
ProfileInfoCard(),
SizedBox(height: 16),
SkillsCard(),
SizedBox(height: 16),
ContactCard(),
SizedBox(height: 20),
],
),
),
);
}
}
// ... (جميع فئات العناصر من الخطوات 5-8 أعلاه)
أفكار للتخصيص
الآن بعد أن لديك تطبيق بطاقة ملف شخصي يعمل، إليك أفكار لجعله خاصاً بك:
أفكار للتحسين
// 1. أضف صورة ملفك الشخصي
// استبدل الأيقونة بصورة فعلية:
CircleAvatar(
radius: 60,
backgroundImage: AssetImage('assets/images/profile.jpg'),
)
// 2. أضف قسم خبرة مع جدول زمني
// 3. أضف عرض مشاريع مع لقطات شاشة
// 4. أضف زر تبديل الوضع الداكن
// 5. أضف حركة للصورة الرمزية
// 6. أضف روابط وسائل التواصل مع url_launcher
// 7. اجعله متجاوباً للأجهزة اللوحية والويب
// 8. أضف زر إجراء عائم للإجراءات السريعة
ما بنيته
تهانينا! في هذا الدرس التطبيقي، بنيت تطبيق Flutter كاملاً من الصفر يتضمن:
- إنشاء المشروع مع
flutter create --org - تكوين pubspec.yaml مع التبعيات والأصول
- سمة Material 3 مع
ColorScheme.fromSeed()و Google Fonts - تكوين العناصر باستخدام Scaffold و AppBar و Column و Card و Row و Wrap والمزيد
- عناصر مخصصة مقسمة إلى مكونات قابلة لإعادة الاستخدام ومركزة
- أيقونات وتنسيق نصوص باستخدام نظام السمات
- تفاعل المستخدم مع الأزرار و SnackBars
- سير عمل إعادة التحميل السريع للتكرار السريع
- ممارسة التصحيح مع أخطاء متعمدة
- بناءات الإصدار لـ Android و iOS والويب
ملخص البرنامج التعليمي
على مدار هذا البرنامج التعليمي، أتقنت أساسيات إعداد Flutter والتطوير:
- الدروس 1-4: تثبيت Flutter وتكوين بيئة التطوير وفهم هيكل المشروع
- الدروس 5-8: العناصر الأساسية والتخطيطات والتنسيق وشجرة العناصر
- الدرس 9: تقنيات تصحيح الأخطاء لإيجاد وإصلاح الأخطاء بكفاءة
- الدرس 10: إدارة التبعيات والأصول مع pubspec.yaml
- الدرس 11: أوامر Flutter CLI لكل مرحلة من مراحل التطوير
- الدرس 12: بناء تطبيق كامل يربط كل شيء معاً
أنت الآن مستعد للانتقال إلى مواضيع Flutter الأكثر تقدماً مثل إدارة الحالة والتنقل وبناء تطبيقات إنتاج كاملة.