دليل شامل: بناء تطبيق حاسبة الإكرامية باستخدام فلاتر (Flutter)

دقائق القراءة: 12

يشهد إطار عمل تطوير تطبيقات الجوال Flutter نموًا متزايدًا في شعبيته، مما يدفع العديد من الشركات لتبنيه في مشاريعها. يقدّر المطورون قدرته على بناء واجهات مستخدم دقيقة للغاية (pixel perfect UIs) باستخدام هيكل بسيط من الأدوات (widgets). نرى أن Flutter يمثل مستقبل تطوير تطبيقات الجوال بفضل سهولته في تصميم الواجهات وقدرته على تشغيل منطق الميزات باستخدام لغة البرمجة Dart.

يركز هذا الدليل بشكل أساسي على تعليمك أساسيات إطار عمل Flutter من خلال بناء تطبيق حاسبة إكرامية (Tip Calculator) بسيط. سنتناول أنماط البرمجة القياسية، بما في ذلك فئات الأدوات ذات الحالة (Stateful widgets) وعديمة الحالة (Stateless widgets)، بالإضافة إلى بعض الأدوات الأكثر استخدامًا في تطوير تطبيقات Flutter. الفكرة هنا هي البدء بإعداد مشروع Flutter أساسي، ثم الانتقال إلى تنفيذ واجهة المستخدم الشاملة والوظائف الأساسية. فلنبدأ!

إعداد مشروع فلاتر (Flutter) الخاص بك

لإنشاء مشروع Flutter جديد، يجب أن يكون لديك حزمة تطوير البرامج (SDK) الخاصة بـ Flutter مثبتة على نظامك. لعملية تثبيت سريعة ومبسطة، يمكنك اتباع الوثائق الرسمية لـ Flutter. تذكر أنه يتطلب أيضًا Android Studio و Android SDK إذا كنت تقوم بتطوير تطبيق لمنصة Android.

بعد إتمام عملية الإعداد بنجاح باتباع التوثيقات، يمكنك تشغيل الأمر التالي في الطرفية (terminal):

flutter create tipCalculator

سيقوم هذا الأمر تلقائيًا بتنزيل وإعداد مشروع Flutter الأساسي لك. الآن يمكنك فتح المشروع في بيئة التطوير المتكاملة (IDE) Visual Studio Code. إذا كان لديك محاكي جهاز (device simulator) أو جهاز هاتف ذكي حقيقي متصل، يمكنك ببساطة تشغيل الأمر التالي لتشغيل التطبيق:

flutter run

بدلاً من ذلك، يمكنك الضغط على مفتاح 'F5' على لوحة المفاتيح، مما سيؤدي إلى ظهور قائمة خيارات في VSCode. من هذه القائمة، يمكنك اختيار الجهاز الذي ترغب في تشغيل التطبيق عليه. لاحظ أنه يجب أن تكون داخل ملف بامتداد .dart لتشغيل هذا الأمر بنجاح. قم بالبناء والتشغيل باستخدام الأمر أعلاه أو F5 للحصول على القالب الأولي التالي في المحاكي/الجهاز الفعلي:

شاشة تطبيق فلاتر (Flutter) الافتراضي بعد التشغيل الأول

الآن يجب أن يكون تطبيق Flutter الخاص بك يعمل. دعنا نتعمق قليلًا في ما يحدث في ملف المشروع الرئيسي، main.dart. في ملف main.dart، لدينا كائنان من الفئات (class objects). أحدهما يمتد إلى أدوات ذات حالة (Stateful widgets) والآخر إلى أدوات عديمة الحالة (Stateless widgets). فماذا يعني ذلك؟

  • أداة ذات حالة (Stateful widget): هي الفئة التي تحتوي على حالات التطبيق (states). يمكن لهذه الحالات أن تتغير وتؤدي إلى إعادة عرض الأدوات (widgets) داخل فئة الأداة ذات الحالة هذه. تساهم في التغييرات الديناميكية للحالة.
  • أداة عديمة الحالة (Stateless widget): هذه الفئة لا تحتوي على أي حالة. إنها تمثل عرض الأداة (widget view) الذي لا يتغير. لا تساهم في أي تغييرات ديناميكية للحالة.

يحتوي ملف main.dart أيضًا على الدالة main() التي تستدعي الفئة MyApp داخل الدالة runApp لتشغيل تطبيق Flutter على الجهاز.

بناء واجهة المستخدم لتطبيق حاسبة الإكرامية

للبدء في تنفيذ واجهة المستخدم (UI) الخاصة بنا، نحتاج إلى مسح كل ما هو موجود افتراضيًا داخل الفئة _MyHomePageState. بعد مسحها، سنقوم بإرجاع أداة Scaffold بسيطة من داخل دالة build. توفر أداة Scaffold الخصائص اللازمة لإضافة شريط التطبيق (appBar) بالإضافة إلى جسم التطبيق (body). في الوقت الحالي، سنضيف شريط تطبيق بسيطًا. يمكنك رؤية التنفيذ العام في مقتطف الشفرة أدناه:

 class _MyHomePageState extends State < MyHomePage > {
 @override
 Widget build(BuildContext context) {
 return Scaffold(
 appBar: AppBar(
 title:Text(
 'Tip Calculator' ,
 style: TextStyle(color: Colors.black87),),
 ),
 body: Container()
 );
 }
 }

قم بالبناء والتشغيل بعد إضافة appBar باستخدام أداة AppBar مع خاصية title. ستحصل على النتيجة التالية في شاشة المحاكي الخاص بك:

شاشة تطبيق فلاتر (Flutter) مع شريط عنوان بسيط

لاحظ أن Flutter يتميز بإعادة التحميل السريع (hot reloading) عند حفظ ملف Dart. لذا، كلما حفظت أي تغييرات في ملف Dart الخاص بمشروعك، ستنعكس التغييرات تلقائيًا في المحاكي.

الخطوة 1: تصميم شريط التطبيق (AppBar)

هنا، سنقوم بتعديل أداة AppBar باستخدام الخصائص المتنوعة التي توفرها. يمكنك رؤية الشفرة المعدلة في المقتطف أدناه:

appBar: AppBar(
 title: Text(
 'Tip Calculator' ,
 style: TextStyle(color: Colors.black87),),
 centerTitle: true ,
 elevation: 0.0 ,
 backgroundColor: Colors.white70,
 ),

قم بالبناء والتشغيل، وستحصل على النتيجة التالية في شاشة المحاكي:

شريط عنوان تطبيق فلاتر (Flutter) بتصميم محسّن

لقد استخدمنا هنا بعض الخصائص الأساسية لأداة AppBar مثل elevation، التي تمكننا من التحكم في تأثير الظل في شريط التطبيق بشكل مشابه لخاصية z-index، وخاصية centerTitle لتوسيط العنوان، كما قمنا بتغيير لون الخلفية إلى الأبيض.

الخطوة 2: تصميم جسم التطبيق (Scaffold Body)

حتى هذه النقطة، كان لدينا فقط أداة Container فارغة في خاصية body. الآن، سنضيف بعض الخصائص والأدوات الفرعية (child widgets) إلى أداة Container كما هو موضح في مقتطف الشفرة أدناه:

body: Container(
 color: Colors.white70,
 padding: const EdgeInsets.all( 16.0 ),
 child: Center(
 child: Form(
 child: Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: <Widget>[
 ],
 ),
 ),
 ),

قم بالبناء والتشغيل، وستحصل على النتيجة التالية في شاشة المحاكي:

جسم تطبيق فلاتر (Flutter) مع خلفية بيضاء وتوسيط للعناصر

كما ترى في لقطة الشاشة أعلاه، قمنا بتغيير لون خلفية الجسم إلى الأبيض. أضفنا أيضًا بعض المسافات الداخلية (padding) بالإضافة إلى أداة Center كأداة فرعية، والتي ستقوم بتوسيط واجهة المستخدم بأكملها في الجسم. تحتوي أداة Center على أداة Form (إحدى أدواتها الفرعية) والتي سنقوم من خلالها بإنشاء نموذج بسيط يحتوي على حقول نصية. والأهم من ذلك، لدينا أداة Column كأداة فرعية لـ Form. توفر لنا أداة Column خاصية مصفوفة الأدوات الفرعية (children widget array property) التي يمكننا من خلالها دمج أي عدد من الأدوات التي ستظهر عموديًا على الشاشة.

الخطوة 3: تعريف الثوابت والمتغيرات

قبل تنفيذ عناصر النموذج (بما في ذلك حقول النص)، نحتاج إلى تعريف بعض الثوابت للتعامل مع المدخلات من حقول الإدخال. يمكنك رؤية الثوابت والمتغيرات المطلوبة في مقتطف الشفرة أدناه:

 // This is the default bill amount
 static const defaultBillAmount = 0.0 ;
 // This is the default tip percentage
 static const defaultTipPercentage = 15 ;
 // This is the TextEditingController which is used to keep track of the change in bill amount
 final _billAmountController = TextEditingController(text: defaultBillAmount.toString());
 // This is the TextEditingController which is used to keep track of the change in tip percentage
 final _tipPercentageController = TextEditingController(text: defaultTipPercentage.toString());
 // This stores the latest value of bill amount calculated
 double _billAmount = defaultBillAmount;
 // This stores the latest value of tip percentage calculated
 int _tipPercentage = defaultTipPercentage;

في مقتطف الشفرة أعلاه، نلاحظ استخدامنا لـ TextEditingController. تتيح لنا هذه الدالة التحكم في مدخلات النص في أداة TextFormField لاحقًا، والتي يتم تهيئتها بقيم افتراضية.

الخطوة 4: إضافة حقول إدخال النموذج

الآن، سنقوم بإضافة حقلي إدخال للنموذج باستخدام أداة TextFormField. عند استخدام هذه الأداة، يجب علينا إلزاميًا تعيين خاصية controller بمتغيرات التحكم التي عرفناها سابقًا. يمكنك رؤية التنفيذ البرمجي الشامل للأداة في مقتطف الشفرة أدناه:

body: Container(
 color: Colors.white70,
 padding : const EdgeInsets.all( 16.0 ),
 child : Center(
 child: Form(
 child: Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children : < Widget > [
 TextFormField(
 key: Key("billAmount"),
 controller: _billAmountController,
 keyboardType: TextInputType.numberWithOptions(decimal: true),
 decoration: InputDecoration(
 hintText: 'Enter the Bill Amount',
 labelText: 'Bill Amount',
 labelStyle: TextStyle(
 fontSize: 25,
 letterSpacing: 1,
 fontWeight: FontWeight.bold
 ),
 fillColor: Colors.white,
 border: new OutlineInputBorder(
 borderRadius: new BorderRadius.circular(20.0),
 ),
 ),
 ),
 SizedBox(height: 25,),
 TextFormField(
 key: Key("tipPercentage"),
 controller: _tipPercentageController,
 keyboardType: TextInputType.number,
 decoration: InputDecoration(
 hintText: 'Enter the Tip Percentage',
 labelText: 'Tip Percentage',
 labelStyle: TextStyle(
 fontSize: 25,
 letterSpacing: 1,
 fontWeight: FontWeight.bold
 ),
 fillColor: Colors.white,
 border: new OutlineInputBorder(
 borderRadius: new BorderRadius.circular(20.0),
 ),
 ),
 ),
 ],
 ),
 ),
 ),
 ),

هنا، قمنا بتعيين خاصية keyboardType التي تمكننا من عرض نوع لوحة المفاتيح المطلوبة عندما ينقر المستخدم على حقل الإدخال. لدينا أيضًا خصائص تزيين (decoration properties) يمكننا من خلالها تصميم حقول الإدخال الخاصة بنا باستخدام أداة InputDecoration. في أداة InputDecoration، لدينا عدة خصائص تساعدنا في إظهار النص النائب (placeholder text) بالإضافة إلى التسمية فوق حقل الإدخال. لقد طبقنا أيضًا خاصية border لإظهار حد خارجي منحني. قم بالبناء والتشغيل، وستحصل على النتيجة التالية في شاشة المحاكي الخاص بك:

شاشة تطبيق حاسبة الإكرامية مع حقلي إدخال مصممين

الخطوة 5: إضافة مستمعي الأحداث والدوال

نظرًا لأننا سنقوم بحساب مبلغ الإكرامية فور قيام المستخدم بإدخال مبلغ الفاتورة أو النسبة المئوية، نحتاج إلى الاستماع إلى التغييرات في حقول إدخال النص. لذلك، نحتاج إلى إضافة مستمعي الأحداث (event listeners) إلى المتحكمات (controllers) باستخدام الدالة addListener. الآن، بمجرد حدوث أي تغييرات في حقل الإدخال، نريد أيضًا تشغيل دالة معينة لتحديث مبلغ الفاتورة ونسبة الإكرامية. لذلك، سنستخدم الدوال المطلوبة مع الدالة setState التي تساعدنا في إعادة عرض واجهة المستخدم بأكملها بمجرد حدوث بعض التغييرات. لاحظ أن الدالة setState تؤدي إلى إعادة تشغيل الدالة build. يمكنك رؤية التنفيذ البرمجي الشامل في مقتطف الشفرة أدناه:

@override
void initState() {
 super .initState();
 _billAmountController.addListener(_onBillAmountChanged);
 _tipPercentageController.addListener(_onTipAmountChanged);
}
_onBillAmountChanged() {
 setState(() {
 _billAmount = double.tryParse(_billAmountController.text) ?? 0.0 ;
 });
}
_onTipAmountChanged() {
 setState(() {
 _tipPercentage = int.tryParse(_tipPercentageController.text) ?? 0 ;
 });
}

هنا، لدينا أيضًا الدالة initState. تعمل هذه الدالة بمجرد دخولنا إلى شاشة التطبيق هذه. لذلك، نضيف مستمعي الأحداث فور دخولنا إلى الشاشة.

الخطوة 6: إضافة قسم المبالغ

الآن نعود إلى أدوات واجهة المستخدم الخاصة بنا. سنقوم بإضافة قسم المبالغ أسفل حقول الإدخال مباشرة داخل أداة Column. هنا، سنستخدم أيضًا أداة SizedBox التي تمكننا من توفير بعض المسافات المطلوبة بين الأدوات. يمكنك رؤية التنفيذ البرمجي لقسم المبالغ في مقتطف الشفرة أدناه:

),
SizedBox(height: 20 ,),
Container(
 margin: EdgeInsets.all( 15 ),
 padding : EdgeInsets.all( 15 ),
 decoration : BoxDecoration(
 color: Colors.white,
 borderRadius : BorderRadius.all( Radius.circular( 15 ),
 ),
 border : Border.all(color: Colors.white),
 boxShadow : [
 BoxShadow(
 color: Colors.black12,
 offset : Offset( 2 , 2 ),
 spreadRadius : 2 ,
 blurRadius : 1 ,
 ),
 ],
 ),
 child : Column(
 children: [
 Text( "Tip Amount" ),
 Text( "Total Amount" )
 ],
 ),
 ),

هنا، لدينا Container مع بعض التزيينات الأسلوبية المطلوبة. تحتوي خاصية child على أداة Column أخرى تحتوي على أداتين من نوع Text مرتبتين عموديًا. قم بالبناء والتشغيل، وستحصل على النتيجة التالية في شاشة المحاكي الخاص بك:

شاشة تطبيق حاسبة الإكرامية مع قسم المبالغ

الخطوة 7: إنشاء أداة عديمة الحالة منفصلة لعرض المبالغ

نظرًا لأننا نرغب في عرض مبلغ الإكرامية (Tip Amount) والمبلغ الإجمالي (Total Amount) ببعض التنسيق، ولن تحتوي هذه الأداة على أي حالات ولكنها ستعتمد على القيمة التي يتم تمريرها من الأداة ذات الحالة (Stateful widget)، يمكننا إنشاء أداة عديمة الحالة (Stateless widget) منفصلة. يمكنك رؤية التنفيذ البرمجي الشامل لفئة أداة AmountText عديمة الحالة في مقتطف الشفرة أدناه:

 class AmountText extends StatelessWidget {
 final String text;
 const AmountText( this .text, { Key key, })
 : super (key: key);
 @override
 Widget build ( BuildContext context ) {
 return Container(
 padding: EdgeInsets.all( 8 ),
 child : Text(text.toUpperCase(), style : TextStyle(fontWeight: FontWeight.bold, color : Colors.blueAccent, fontSize : 20 )),
 );
 }
 }

هنا، استخدمنا فئة الباني (constructor class) للحصول على قيمة النص الفعلي المراد عرضه. تُرجع دالة build لهذه الفئة أداة Container مع padding بسيط وأداة Text كأداة فرعية.

بما أن أداة AmountText جاهزة، يمكننا الآن استدعاء الأداة في الأداة ذات الحالة. سنقوم بإضافة الأداة داخل أداة Column التي عرفناها سابقًا مع أدوات Text البسيطة. نحتاج فقط إلى استبدال أداة Text بأداة AmountText وتمرير قيم النص المطلوبة. يمكنك رؤية التنفيذ البرمجي في مقتطف الشفرة أدناه:

Container(
 margin: EdgeInsets.all( 15 ),
 padding : EdgeInsets.all( 15 ),
 decoration : BoxDecoration(
 color: Colors.white,
 borderRadius : BorderRadius.all( Radius.circular( 15 ),
 ),
 border : Border.all(color: Colors.white),
 boxShadow : [
 BoxShadow(
 color: Colors.black12,
 offset : Offset( 2 , 2 ),
 spreadRadius : 2 ,
 blurRadius : 1 ,
 ),
 ],
 ),
 child : Column(
 children: [
 AmountText( 'Tip Amount: ${_getTipAmount()}' , key : Key( 'tipAmount' ), ),
 AmountText( 'Total Amount: ${_getTotalAmount()}' , key : Key( 'totalAmount' ), ),
 ],
 ),
 ),

هنا، قمنا بتمرير الدالة داخل أداة AmountText. تُرجع الدالة قيم مبلغ الإكرامية والمبلغ الإجمالي، كما يمكنك رؤيته في مقتطف الشفرة أدناه:

 _getTipAmount() => _billAmount * _tipPercentage / 100 ;
 _getTotalAmount() => _billAmount + _getTipAmount();

أخيرًا، نحتاج إلى إنهاء المتحكمات (controllers) عند الخروج من العرض. لذلك، نحتاج إلى استخدام دالة dispose المضمنة. تعمل هذه الدالة بينما نخرج من الشاشة الحالية. داخل هذه الدالة، نحتاج إلى استدعاء المتحكمات باستخدام دوال dispose لإنهاء متحكمات إدخال النص. سيؤدي هذا إلى توقف المتحكم عن الاستماع إلى التغييرات في حقول الإدخال. يمكنك رؤية دالة dispose في مقتطف الشفرة أدناه:

 @override
 void dispose() {
 // To make sure we are not leaking anything, dispose any used TextEditingController
 // when this widget is cleared from memory.
 _billAmountController.dispose();
 _tipPercentageController.dispose();
 super .dispose();
 }

قم بالبناء والتشغيل للحصول على النتيجة النهائية لتطبيق حاسبة الإكرامية، والتي يمكنك رؤيتها في العرض التوضيحي أدناه:

عرض توضيحي لتطبيق حاسبة الإكرامية النهائي في فلاتر (Flutter)

ستلاحظ أن قيمة كلا المبلغين تتغير بمجرد تغيير المدخلات في حقول الإدخال. لقد وصلنا الآن إلى نهاية هذا الدليل. لقد قمت بنجاح بتنفيذ حاسبة إكرامية بسيطة باستخدام إطار عمل Flutter ولغة Dart.

خطواتك التالية في فلاتر (Flutter)

كان الهدف الرئيسي من هذا الدليل هو تعليمك أنماط البرمجة الأساسية في إطار عمل تطوير تطبيقات Flutter من خلال بناء حاسبة إكرامية بسيطة. هناك العديد من الأدوات والإضافات الأكثر إثارة للاهتمام التي يمكنك استكشافها. يمكنك تغيير واجهة المستخدم (UI) لتطبيقك مع الحفاظ على المكونات الوظيفية كما هي. بشكل عام، يجعل Flutter تصميم واجهة المستخدم المعقدة أبسط باستخدام نمط الأدوات (widget pattern). يمكنك ببساطة إنشاء واجهة مستخدم رائعة بمجرد تكديس الأدوات معًا باستخدام خصائصها الفرعية (child properties).

يمكن أن تكون الخطوة التالية هي استخدام آليات التنقل (navigation mechanisms) في Flutter للتنقل بين الشاشات المختلفة. يبسط Flutter أيضًا إضافة قوائم جانبية مخصصة (custom drawer menus) وعلامات تبويب سفلية (bottom tabs). هذه ليست سوى نقطة البداية لتطوير Flutter؛ هناك الكثير مما يمكن استكشافه. ما علينا سوى الاستمرار في الاستكشاف والبرمجة.

يتوفر العرض التوضيحي للمشروع بأكمله في Codepen. يمكنك الحصول على الإلهام لتطبيق Flutter الخاص بك من التطبيقات الأخرى الموجودة بالفعل.

برمجة سعيدة!

الخلاصة التقنية

يُظهر هذا الدليل العملي قوة Flutter ومرونته في بناء تطبيقات جوال تفاعلية بسرعة وكفاءة. من خلال التركيز على مفهوم الأدوات (Widgets) وتقسيمها إلى أدوات ذات حالة (Stateful) وأدوات عديمة الحالة (Stateless)، يوفر Flutter هيكلاً واضحًا ومنظمًا لإدارة واجهة المستخدم ومنطق التطبيق. إن استخدام TextEditingController للتعامل مع مدخلات المستخدم، و setState لتحديث الواجهة ديناميكيًا، و dispose لإدارة موارد الذاكرة، كلها ممارسات أساسية تضمن أداءً مستقرًا وتجربة مستخدم سلسة. هذا المشروع البسيط ليس مجرد حاسبة إكرامية، بل هو بوابة لفهم المبادئ الأساسية التي يقوم عليها تطوير Flutter، مما يمهد الطريق لبناء تطبيقات أكثر تعقيدًا وغنى بالميزات. إن قدرة Flutter على توفير تجربة تطوير سريعة وإعادة تحميل فوري (Hot Reload) تجعله خيارًا ممتازًا للمطورين الذين يسعون إلى الكفاءة والجودة.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *