Flutter - المهندس فيصل الاسود
Flutter - المهندس فيصل الاسود
38
البرمجة المتقدمة في دارت
Advanced Programming Dart
Larry Page
StackOverFlow Arabi
39
البرمجة المتقدمة في دارت
-1الأصناف:
بعد أن شكلنا الصنف يمكن استخدامه عدد لا نهائي من المرات بتشكيل كائن منه كما يلي:
;)Point p1 = new Point(5,3
وهنا أنشأنا كائن p1من نوع Pointوله القيمة x=5, y=3
وفي حال كتبنا :
;)(p1.printXAndY
StackOverFlow Arabi
40
أي القيمة الأولى الواردة عند انشاء الكائن هي نفسها قيمة xللصنف pointوالقيمة الثانية هي
قيمة yللصنف.
كما رأينا يستدعي الفعل (الطريقة) في dartعن طريق اسم الكائن p1ثم اسم الطريقة المطلوبة.
مثال :ننشأ صنف لسيارة لها (سرعة – لون) وعند إعطائها مساحة تقوم بتقدير الزمن لك عن طريق
فعل خاص لها.
{ class Car
;num speed
;String color
;)car (this.speed, this.color
)double giveMeTime(int distance
{
;return speed/distance
}
}
أنشأنا كائنين لسيارتين الأولى بسرعة 51ولون أحمر والثانية بسرعة 31ولون أخضر غيرنا سرعة السيارة
الثانية لـ 11
الخرج سيكون
2
1
StackOverFlow Arabi
41
التعليمة returnمهمتها إرجاع قيمة من الطريقة إلى التابع .كما رأينا عندما طلبت c1الزمن
بطريقة )( giveMeTimeتم ارجاع القيمة للبرنامج الرئيسي عن طريق returnوهناك بعض
الطريق لا ترجع شيء.
-2معرفات الوصول:
على خلاف الكثير من لغات البرمجة dartلا تحوي معرفات وصول الكلاسيكية انما تحوي فقط
معرف الخصوصية privateعلى مستوى الحزمة ككل.
أي إذا أردنا إنشاء متحول ضمن صنف classوهذا المتحول لا يوجد أي كلاس اخر يمكنه معرفة
قيمته نكتب المتحول بالشكل:
;var _ x = 5
بحيث نضع قبل اسمه _ وهذا الكلام ينطبق أيضا على التوابع والطرق بحيث لا استطيع استدعاء أي
طريقة من خازنة المكتبة في حال بدأت بالمعرف _ مثلا
)( int _giveMeTime
-3المتحول الستاتيكي:
الكثير يعاني من فهم المتحول الستاتيكي انما هو يحمل مفهوما بسيطا جدا .لنعود لمثالنا السابق
بخصوص إنشاء صنف سيارة ونفرض أننا عرفنا متحول السرعة بشكل staticكما يلي:
{ class car
;static num speed
;String color
;)car (this.speed, this.color
)double giveMeTime (int distance
{
;return speed/distance
}
}
StackOverFlow Arabi
42
هنا يكون شكل الكائنات عند إنشاء سيارتين كما يلي:
فنلاحظ أنه عند تعريف متحول ستاتيكي يصبح قيمة نفسها لكل الكائنات بحيث إذا عدلتها لأي
كائن (سيارة) سوف تتعدل للجميع.
طبعا يوجد أيضا التوابع الستاتيكية ويجب أن نعلم أن التابع الستاتيكي يتعامل حصرا مع متحول
ستاتيكي.
-4الوراثة:
الوراثة مفهوم بسيط يساعدني على بناء الأصناف بالاعتماد على أصناف سابقة ويريحني من عناء
صناعتها من جديد.
لنفرض أريد إنشاء صنف سيارة لديها (سرعة ,لون ,وزن) وتابع ليعطيني الوقت كما في المثال السابق
هنا نلاحظ أنه لدينا الصنف السيارة القديم فيه كل من السرعة واللون والتابع المطلوب ولكننا بحاجة
لإضافة خاصة الوزن .فهنا نستخدم الوراثة ويكون الصنف الجديد كما في الشكل:
{ class car2 extends car
car2(num speed, String color) :
;)super(speed, color
;var weight
}
StackOverFlow Arabi
43
أي صنف السيارة Car2يرث extendsمن صنف السيارة Carويأخذ جميع خصائصها وأفعالها.
StackOverFlow Arabi
44
StackOverFlow Arabi
4
45
3
Mark Zuckerberg
StackOverFlow Arabi
46
التعرف على بيئة فلاتر
مقدمة:
فلاتر هي منصة تطوير تطبيقات جوال مفتوحة المصدر أنشأت من قبل جوجل وتستخدم هذه
المنصة من اجل تطوير تطبيقات أنظمة اندرويد و iosوكذلك نظام فوشيا الجديد.
النسخة الأولى من فلاتر والمعروفة بـ skyالسماء قد بنيت لتطوير تطبيقات الاندرويد عام 1125
وهكذا تطورت حتى باقي أنظمة التشغيل.
يجب أن ننوه أن أي شيء في فلاتر هو widgetعلى سبيل المثال :النص ,الصورة والزر هي .widget
بعد أن نكون جهزنا أحد الـ )Android studio, IntliJ IDEAِ( IDEكما يمكنك زيارة التوثيق
الخاص ب Flutterمن خلال الموقع التالي :
https://flutter.io
سوف نستخدم في كتابة البرامج بيئة العمل IntliJ IDEAلأنها تمكننا من بناء تطبيقات نظامي الـ
iosوالاندرويد كما ننوه أيضا أن دارت هي اللغة التي ستكون لنظام فوشيا البديل من جوجل.
كما يجب التنويه إلى ضرورة تنصيب Android studioفي جميع الأحوال للاستفادة من المحاكيات
المتضمنة مع حزمته.
المشروع األول:
.2بعد فتح IntliJ IDEAنختار Create new projectونختار من الشريط الأيسر الخيار
Flutterكما في الصورة.
StackOverFlow Arabi
47
.1نعطي المشروع اسم ومسار محدد ثم نختار أساس عمل النظام ويفضل أن يبقى كما هو
محدد في الصورة وثم .Finish
.4في الشريط الأيسر من مجموعة المجلدات والملفات التابعة لمشروع Flutterوالتي لن
نتطرق لها كثيرا ,ما يهمنا هو بعض المجلدات مثل libالذي يحوي المكتبات في المشروع
StackOverFlow Arabi
48
التطبيق الأول:
;'import 'package:flutter/material.dart
{ )(void main
(runApp
(new Center
child: new Text('Hello Stack Overflow Arabi',
textDirection: TextDirection.ltr),
)
;)
}
هنا أنشأنا برنامجنا الأول والذي يطبع العبارة التالية كما في الصورة.
StackOverFlow Arabi
49
Material Design يعتمد بشكل أساسي علىFlutter يجب أن نعلم أن تصميم واجهات تطبيقات
الخاصة بـclasses المدعوم أيضا من جوجل ولكي نبدأ تصميم الواجهة سوف نستخدم أصناف
.Material Design
import 'package:flutter/material.dart';
void main() {
runApp(
new Material(
color: Colors.greenAccent,
child: new Center(
child: new Text('Feisal Aswad',
textDirection: TextDirection.ltr,
style: new TextStyle(
fontSize: 25,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic
)),
),
)
);
}
StackOverFlow Arabi
50
كما فيitalic, Bold والناتج هنا هو عبارة عن خلفية بلون "أخضر" مع كتابة تظهر بالوسط بخط
:الصورة التالية
حيث نقومstateless كما يمكن عزل التصميم بصنف خاص وهذا ما يسمى بعزل العناصر الثابتة
بوضع كل العناصر الثابتة والتي لا تطلب منها الاستجابة للأحداث أو التفاعل مع المستخدم بعزلها
:بصنف خاص بها واستدعاءها بالتابع الأساسي كما في الشكل
main.dart
import 'package:flutter/material.dart';
import 'MyUi.dart';
void main(){
runApp( new MaterialApp(
home:MyText(),
)
); // MaterialAPP
}
MyUi.dart
import 'package:flutter/material.dart';
StackOverFlow Arabi
51
), //Text
) //center
); //Material
}
}
والناتج سوف يكون مماثل للتطبيق السابق مع الاستفادة من كوننا أصبح لدينا widgetجاهزة
ويمكن استدعائها أكثر من مرة ويكون التعديل عليها سهل للغاية.
كما يجب الانتباه أننا وضعناه داخل المسار libفي ملف جديد اسمه MyUi.dartوهذا ما يفسر
الاستدعاء MyUi.dartفي البرنامج الرئيسي.
StackOverFlow Arabi
52
Layouts أنظمة التصميم
}
}
StackOverFlow Arabi
53
والملف الرئيسي main.dartهو نفسه الملف للبرنامج السابق.
ملاحظة :يمكن أن نجعل العرض النص الأخير في التخطيط يأخذ كامل المساحة المتبقية واستدعاء
التابع Expandedللعنصر الأخير كما يلي:
StackOverFlow Arabi
54
MyUi.dart
import 'package:flutter/material.dart';
}
}
StackOverFlow Arabi
55
الـ Widgetالمقدمة مع Flutter
StackOverFlow Arabi
56
main.dart
import 'Package:flutter/material.dart';
void main(){
runApp(new MaterialApp(
home: new ourWidget(),
));
}
ourWidgetFile.dart
class ourWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _Buildstate();
}
}
class _Buildstate extends State<ourWidget>
{
String names= '';
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar : new AppBar(
backgroundColor: Colors.blue,
title: new Text ("MyAPP"),
),
body: new Container(
child: new RaisedButton(
onPressed: () => clickfunc('Hi'),
child: new Text ('click me ${names}')
)
),
);
}
void clickfunc(String txt){
setState((){
names = txt;
});
}
}
في الكود السابق أنشأنا زر داخل تابع البناء وعند كل استدعاء لتابع التعديل سوف يعيد بناءه وهكذا
عن طريق تابع مجهول ويمرر له نص معينclickfunc نلاحظ أن الزر يطلب التابع.يكون الزر حرف
.كما أن التابع يعرف في نهاية الصنف
StackOverFlow Arabi
57
كما يبين الشكل التالي الزر من نوع, هو زر يأخذ شكل نص عادي فقط:FlatButton الزر المسطح
.FlatButton
}
void clickfunc(String txt){
setState((){
names = txt;
});
}
StackOverFlow Arabi
58
:IconButton الزر الأيقونة
:هو زر يحوي داخله صورة على شكل أيقونة كما في الشكل
}
void clickfunc(String txt){
setState((){
names = txt;
});
}
StackOverFlow Arabi
59
.حيث تتيح لنا فلاتر العديد من الرموز والاشكال الجاهزة
:TextField نص الإدخال
هو حقل نصي يسمح للمستخدم بإدخال نص معين مثل كلمة مرور أو اسم مستخدم كما هو مبين
:في الشكل
}
}
StackOverFlow Arabi
60
: بعض الخصائص الهامة نذكر منهاTextField كما يحوي الـ
الخاصة الشرح
Auto correct : true تجعل الحقل النصي يصحح األخطاء اللغوية
تلقائيا
keyboardType: TextInputType )تسمح لنا بتحديد المدخالت (رقمي – ايميل
وتساعد في عملية اختيار الدخل
decoration تسمح لك بتشكيل صنف إلظهار الحقل النصي
بعدة أشكال
:مثال تطبيقي
mobile number في مثالنا التالي سوف نقوم بتصميم واجهة تحوي حقل ادخال له العنوان
وبجانبه صورة المستخدم كما أن نوع المدخلات هي من نوع رقم هاتف
ourWidgetFile.dart
class ourWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _Buildstate();
}
}
class _Buildstate extends State<ourWidget>
{
String names= '';
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar : new AppBar(
backgroundColor: Colors.blue,
title: new Text ("MyAPP"),
),
body: new Container(
child: new TextField(
autocorrect: true,
keyboardType: TextInputType.phone,
decoration: new InputDecoration(
icon: Icon(Icons.perm_identity),
labelText: 'mobile number',
hintText: 'Enter Here',
), // Input Decoration
) // text field
),
);
}
}
StackOverFlow Arabi
61
:Checkbox مربع الاختيار
جميعنا نعرف مربع الاختيار المتعدد الذي يسمح لنا اختيار بعض العناصر وترك أخرى كما هو مبين
.في الصورة
ourWidgetFile.dart
class ourWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _Buildstate();
}
),
);
}
}
StackOverFlow Arabi
62
:switch مبدل الحالة
.switch الصورة التالية توضح العنصر, مع اختلاف في الشكلcheckbox هو مشابه تماما لـ
StackOverFlow Arabi
63
:Drawer الشريط الجانبي
هو الشريط المستخدم غالبا لإظهار خيارات المستخدم وملفه الشخصي ويكون موجود في الصفحة
. يحوي داخله بعض العناصرDrawer الصورة التالية تبين عنصر.الرئيسية للبرنامج
}
class _Buildstate extends State<ourWidget>
{
String names= '';
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
drawer: new Drawer(
child: new Container(
color: Colors.blue,
padding: EdgeInsets.all(12.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(padding: EdgeInsets.only(bottom: 25)),
new Text ('MyAcount'),
Padding(padding: EdgeInsets.only(bottom: 25)),
new Text ('Edit'),
Padding(padding: EdgeInsets.only(bottom: 25)),
new Text ('Exit'),]
),
),
),
appBar : new AppBar(
backgroundColor: Colors.blue,
title: new Text ("MyAPP"),
),
body: new Container(
),
);
}
}
تأليف فيصل الأسود | مهندس برمجيات
Youtube يمكنك الحصول على شرح لكامل محتويات الكتاب على قناة الـ
StackOverFlow Arabi
64
:(Alert Dialog) Notification Dialog مربع الحوار
.هو مربع رسالة لتنبيه المستخدم لأجراء معين مع إمكانية إعطائه عدة خيارات
,يجب أن تعلم ان مربع الحوار لا يستخدم إلا عند اعلام شيء ما أو سماحية المستخدم لاتخاذ القرار
.ومنه هذا المربع يستدعى دوما عن طريق تابع أو طريقة ما
ourWidgetFile.dart
import 'Package:flutter/material.dart';
import 'dart:io';
class ourWidget extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _Buildstate();
}
StackOverFlow Arabi
65
. والتي تقوم ببناء مربع التنبيه عند طلبها وإظهارهDialogBuild(context) بقي لدينا انشاء الدالة
ourWidgetFile.dart
Future<Null> DialogBuild(BuildContext context) async {
return showDialog<Null>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: new Text("Exit"),
content: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
new Text("Do you want to Exit"),
],
),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: new Text("No")),
new FlatButton(
onPressed: () {
exit(0);
},
child: new Text("Yes")),
],
);
});
}
StackOverFlow Arabi
66
والكود التالي لإنشاء تابع (دالة) يبني مربع الحوار البسيط
ourWidgetFile.dart
);
})) {
}
}
StackOverFlow Arabi
67
كما أنها تشكل ضمن تابع مستقل أيضا, BottomSheet والكود التالي لإنشاء قائمة من نوع
.وتستدعى عند الطلب
ourWidgetFile.dart
Bottomsheetopen(BuildContext context) async{
StackOverFlow Arabi
68
: كما في الكود التاليlayout يمكن إنشاء مثل هذا النوع من الـ
ourWidgetFile.dart
class _Buildstate extends State<ourWidget> {
String names = '';
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(),
body: new Container(
child:new Column(
children: <Widget>[
new Card(
color: Colors.amber,
child: new Column(
children: <Widget>[
new Text ('card'),
new RaisedButton(onPressed: null,child: new Text("Click
me"),)
],
),
),
new Card(
color: Colors.amber,
child: new Column(
children: <Widget>[
new Text ('card'),
new RaisedButton(onPressed: null,child: new Text("Click
me"),)
],
),
),
],
)
)
);
}
StackOverFlow Arabi
69
child: new CircleAvatar(
child: new Text('Name'),
backgroundColor: Colors.pink,
foregroundColor: Colors.white,
radius: 50,
),
غالبا له الشكل, ويأخذ شكل معين حسب تصميمكListTile كل عنصر في هذه القائمة يسمى
:التالي
children:<Widget>[new ListTile(
title: new Text("I'm Title 1"),
subtitle: new Text("I'm subtitle 1"),
leading: new Text("I'm leading 1")
) ,
new ListTile(
title: new Text("I'm Title 2"),
subtitle: new Text("I'm subtitle 2"),
leading: new Text("I'm leading 2")
)],// ListTile
)
StackOverFlow Arabi
70
Scaffold القوالب الجاهزة
تساعد في بناء واجهات قوية دون الخوض في عناء تصميمFlutter جاهزة تقدمهاwidgets هي
.الواجهات
الصور التالية تبين مجموعة قوالب جاهزة
:AppBar
AppBar القالبscaffold المثال التالي لصنف مستخدم قوالب الـ
MyUi.dart
import 'package:flutter/material.dart';
التي تظهر شريط في أعلى.widget AppBar ويكون ناتج البرنامج عبارة عن تطبيق يستخدم الـ
.البرنامج مع مجموعة اوامر معينة على شكل أيقونات كما في الشكل التالي
StackOverFlow Arabi
71
كما يمكن إضافة أحداث لتلك الأيقونات عن طريق تعريف تابع داخل الصنف واستدعاء هذا التابع
بالشكل التالي:
وهنا نحتاج الى دالة myFunctionضمن الصنف لتنفيذ الامر المطلوب ويمكن انشاء دالة مجهولة
مباشرة كما يلي:
StackOverFlow Arabi
72
وهنا يمكن وضع أجزاء جديدة فيScaffold الـBody داخل جسمwidgets كما يمكنك وضع
ثمBody وبالتحديد خاصةScaffold داخل الـwidget الواجهة في الكود التالي جزء من كود الـ
وهو نص عادي قابل للاستجابة للأحداث وقد تم ربطه بحيث عندInkwell إنشاء نص من نوع معين
.الضغط عليه ينفذ تابع معين
MyUi.dart
import 'package:flutter/material.dart';
),
),
appBar :new AppBar(
StackOverFlow Arabi
73
:Navigation Bar شريط التصفح
كما في الشكل وإعطاءscaffold بسهولة عن طريقBottom Navigation Bar يمكن إنشاء
.كل زر حدث يستجيب له
MyUi.dart
import 'package:flutter/material.dart';
StackOverFlow Arabi
74
والناتج يكون:
عبارة I am float button هنا عند الضغط على الزر سوف يطبع في الـ console
StackOverFlow Arabi
75
:كما في الكود التالي
MyUi.dart
import 'package:flutter/material.dart';
:Online التصميم
على الانترنت تصميم واجهات عن طريق أدوات وازرار يمكنكFlutter Studio تتيح لنا منصة
:الوصول لتلك المنصة عن طريق الرابط
https://www.flutterstudio.app
StackOverFlow Arabi
76
التصفح والتوجيه Navigation
في هذا القسم من الكتاب سوف نركز على كيفية التنقل بين الصفحات أو الواجهات وهذا ما يسمى
بمفهوم الـ .Navigation
في مثالنا التالي لدينا واجهتين سوف ننتقل من الواجهة الأولى (الأساسية) إلى الواجهة الثانية
باستخدام مفاهيم التصفح.
StackOverFlow Arabi
77
ثم نكتب التابع, لكل واجهة ونبني ضمنها التصميم الصحيحclass بالبداية سوف نشكل صنف
: كما يلي مستعينين بصنف ثالث يحدد لنا المساراتmain
main.dart
import 'package:flutter/material.dart';
import 'package:navigationProject/PageOneFile.dart';
import 'package:navigationProject/PageTwoFile.dart';
void main(){
runApp(new MaterialApp(
home: new MyApp())
);
}
class MyApp extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _MyApp();
}
}
class _MyApp extends State<MyApp>{
@override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
title: 'Navigation',
routes: <String, WidgetBuilder>{
'/First': (BuildContext context) => new PageOne(),
'/Second': (BuildContext context) => new PageTwo(),},
home: new PageOne());
}//MaterialApp
}
StackOverFlow Arabi
78
ونكتب كود الصنفين بحيث الأول يطلب الثاني والثاني يطلب الأول
:الصنف الأول
PageOneFile.dart
import 'package:flutter/material.dart';
class PageOne extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _PageOne();
}
}
:والصنف الثاني
PageOneFile.dart
import 'package:flutter/material.dart';
class PageTwo extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _PageTwo();
}
}
StackOverFlow Arabi
79
title: new Text("page 2"),
),
(body: new Container
(child: new Column
[>children: <Widget
(new RaisedButton
{ )( onPressed:
;)'Navigator.of(context).pushNamed('/First
},
))'child: new Text('Back
],
),
),
;)
}
}
بنينا موجهاً للصفحات وكما في الخاصة homeوضمنها الصفحة نلاحظ بالخاصية routes
PageOneكصفحة أولى.
نلاحظ عند الانتقال للصفحة الثانية ظهور زر الرجوع أوتوماتيكياً وهذا ما يوفره الـ Scaffold
بالطريقة .PushNamed
يمكن عدم إظهار هذا الزر باستخدام طريقة تصفح أخرى هي كما في الكود التالي:
{ )( onPressed:
Navigator.of(context).
;)pushNamedAndRemoveUntil('/Second',(Route<dynamic> route) => false
},
وهنا عند الذهاب من الصفحة الأولى إلى الصفحة الثانية لن يظهر زر الرجوع أما في باقي حالات
التصفح فسوف يظهر تلقائيا.
هناك أيضا طريقة للرجوع للصفحة السابقة كما يلي:
{)( onPressed:
;)(Navigator.of(context).pop
},
StackOverFlow Arabi
80
وهي تنوب تماما عن زر الرجوع في حال كانت إمكانية الرجوع مقبولة أي أرسلت بالطريقة
PushNamedوللتحقق من هذا يجب كتابة الكود السابق بالشكل:
{ )( onPressed:
))(if(Navigator.of(context).canPop
{
;)(Navigator.of(context).pop
}
}
ملاحظة :تستخدم عملية منع الرجوع كما في حالات صفحة الـ Loginحيث لا يسمح للمستخدم
بالرجوع إليها مرة أخرى.
StackOverFlow Arabi
81
:ويكون شكل التابع للصفحة الأولى كما في الكود التالي
PageOneFile.dart
import 'package:firebase_tutorial/PageTwoFile.dart';
import 'package:flutter/material.dart';
Navigator.of(context).
pushNamedAndRemoveUntil('/Second',(Route<dynamic>
route) => false);
},*/
onPressed: () {
toPageTwo(context);
},
toPageTwo(BuildContext context) {
Navigator.push(context,new MaterialPageRoute(builder:
(BuildContext context)
=> new PageTwo("hello")
));
}
StackOverFlow Arabi
82
" من الصفحة الأولى إلى الصفحة الثانية وعلينا الآن أن نجهز الصفحةhello" هنا أرسلنا العبارة
.الثانية لاستقبال البيانات
ومنه سوفtoPageTwo وهنا عند الضغط على زر معين من الصفحة الأولى سوف تنفذ الطريقة
. باستقبالها عن طريق البانيsecond " وتقوم الصفحةhello" ترسل العبارة
هنا اعتبرنا ان الصفحة الأولى فقط ترسل للصفحة الثانية وإذا اردنا أن نرسل من الثانية للأولى نشكل
.ًنفس التابع في الثانية وكذلك ننشئ باني في الأولى لاستقبال البيانات كما فعلنا مسبقا
StackOverFlow Arabi
83
الطريقة الثانية :نلخص الطريقة الثانية بإنشاء صنف خاص يحوي متحولات ستاتيكية عامة هذا
المتحولات يمكن تخزين البيانات فيها من قبل الصفحة المرسلة وتقوم الصفحة المستقبلة باستدعاء
هذه البيانات من الصنف وذلك عن طريق مفتاحها الخاص .key
StackOverFlow Arabi
84
التعامل مع Json
مقدمة:
في البداية دعونا نشرح Jsonوما الفائدة منها ,تعد Jsonهي الوسيلة الأفضل للحصول من
البيانات من المخازن الالكترونية أو مواقع الويب .بحيث علينا طلبها عن طريق رابط يدعى API
خاص لهذه البيانات فيعيد لي البيانات مكتوبة بلغة .Json
الحقيقة إن Jsonليست لغة برمجة إنما هي لغة هيكلة بيانات مثل XMLولكن تساعد جدا في
فهم كيفية استقبال البيانات من أي موقع.
كما يجدر الذكر أن هناك العديد من المواقع التي تقدم بيانات جاهزة تساعد المبرمجين في بناء
التطبيقات منها بيانات عن أسعار العملات او بيانات الطقس.
يجب أن نعلم أن طلب هذه الخدمة عن طريق APIالخاص بها يسمح لنا فقط باستعراضها
وعرضها ضمن تطبيقنا ,حيث أي ملف Jsonيكتب على شكل كائنات وخصائص لكل خاصية أو
كائن مفتاح (اسم معين) وقيمة.
لدينا هنا ملف Jsonيحوي بيانات مستخدمين حيث لكل مستخدم رقم معرف ,idاسم ,name
عنوان ,Addressالآباء .parents
JSON File: data.json
[
{
id: 1,
name: "Ahmad",
Address: "Syria",
{ Parents:
father: "Mohamad",
"mother: "Nour
}
},
{
id: 2,
Name: "Farah",
Address: "Damascus",
{Parents:
Father = "Feisal",
Mother = "Batoul",
}
}
]
StackOverFlow Arabi
85
يمكن الوصول للمعلومات بالشكل التالي :
"data [0] ['Name'] = "Ahmad
data [1] ['Parents'] ['Father'] = " Feisal ",
الآن لنعد إلى Flutterونتعلم كيفية جلب مثل تلك البيانات وعرضها على تطبيقنا.
بالبداية يجب جلب مكتبة ال httpالتي تتعامل مع jsonالى مشروعنا كما يلي:
نفتح الملف pubspec.yamlمن ضمن ملفات المشروع.
StackOverFlow Arabi
86
: إلى تطبيقناJson الطريقة التالية مهمتها جلب البيانات بطريقة
Future<List> getData() async{
String url = " our API ";
http.Response r= await http.get(url);
return json.decode(r.body);
}
.نلاحظ انها متزامنة انها تنتظر قدوم البيانات من الشبكة
import 'dart:convert';
import 'package:http/http.dart' as http;
:مثال
. تعيد لي الكثير من البيانات وأريد وصفها ضمن قائمةAPI ليكن لدينا
.سنبدأ الآن بكود كامل لحل تلك المشكلة وتظهر لدي البيانات كما في الصورة
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
appBar: AppBar(
StackOverFlow Arabi
87
itemCount: data.length,
itemBuilder: (BuildContext context, int Position){
return new ListTile(
title:new Text("${data[Position]['name']}",style: TextStyle(
color: Colors.greenAccent,
fontSize: 22,
),)
,
leading: new Text("${data[Position]['id']}",style: TextStyle(
color: Colors.greenAccent,
fontSize: 22,
),),
);
}
)
)
)
)
);
}
Future <List> getData() async{
String url = 'https://jsonplaceholder.typicode.com/comments';
StackOverFlow Arabi
88
استدامة البيانات
توفر فلاتر أكثر من طريقة لحفظ البيانات الخاصة بالتطبيقات أهم هذه الطرق تشمل ما يلي:
الذاكرة الداخلية ( :)Internal Storageوفيها يتم تخزين البيانات في الذاكرة الداخلية للتطبيق.
الذاكرة الخارجية ( )External Storageوفيها يتم تخزين البيانات في الذاكرة الخارجية العامة
بحيث أي تطبيق يمكن الوصول لها
التفضيلات المشتركة ( :)Shared Preferencesتشمل حفظ بيانات أساسية بمفاتيح محددة (Key-
.)Value pairs
قواعد البيانات ( :)SQLite Databasesوفيها يتم تخزين البيانات في قواعد بيانات خاصة
بالتطبيق.
استخدام أي من هذه الطرق يعتمد على احتياجاتك ,وكذلك المساحة المطلوبة لتخزين البيانات
وسيتم تاليا تفصيل كل طريقة على حدى.
يجب أن ننوه بالبداية أنه كي نستطيع استخدام التخزين يجب أن نضيف حزمة ioإلى مشروعنا
بحيث نضيف في الملف Pubspec.yamlوتحت السطر Cupertino_iconsسطر جديد يحتوي
Path_Provider: ^0.4.1ثم نضغط package getكما فعلنا مع حزمة .json
StackOverFlow Arabi
89
: والتي تعيد مسار تطبيقنا كما يليAppPath ) الأولى المستخدم هيmethod) التابع أو الطريقة
Future<String> AppPath()async{
final path = await getApplicationDocumentsDirectory();
return path.path;
}
:الطريقة الثانية تعيد الملف الذي سنستخدمه لحفظ أو استرجاع الملفات كما يلي
Future <File> AppFile() async {
final file = await AppPath();
return new File('$file/dart.txt');
}
StackOverFlow Arabi
90
ويستدعيsave الآن سوف نقوم ببناء كود يقوم بحفظ نص معين داخل ملف عن الضغط على الزر
. كما في الصورةload عند الضغط على زرlabel الأمر ويظهره على
main.dart
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
StackOverFlow Arabi
91
void main() async {
//List data = await getData();
runApp(new MaterialApp(
title: 'Json Example',
home: new Scaffold(
appBar: AppBar(title: new Text('Files')),
body: new Center(
child: new Column(
children: <Widget>[
new TextField(controller: input),
new TextField(controller: output),
RaisedButton(
child: new Text("Save"),
onPressed: () {
WriteFile(input.text);
},
),
RaisedButton(
child: new Text("Load"),
onPressed: () async {
output.text = await ReadFile();
})
],
)))));
}
StackOverFlow Arabi
92
التفضيلات المشتركة (:)Shared Preferences
التفضيلات المشتركة ) (SharedPreferencesهي مكون يوفر حفظ واسترجاع بيانات أساسية
بسيطة .كل معلومة يتم حفظها يجب أن تكون على شكل مفتاح وقيمة ) (key-valueبحيث
يحدد المفتاح ) (keyاسم مميز ليتم استعادة القيمة ) (valueفيما بعد عن طريقه .يمكن
استخدام التفضيلات المشتركة لحفظ بيانات من الأنواع البدائية )(primitive typesوتشمل :
booleans, floats, ints, longs, and Strings .يتم الاحتفاظ بالبيانات المخزنة من تطبيق ما
في التفضيلات المشتركة حتى بعد إغلاق هذا التطبيق.
ننوه بالبداية أنه كي نستطيع استخدام التخزين بالتفضيلات المشتركة يجب أن نضيف حزمة
shared_preferencesإلى مشروعنا بحيث نضيف في الملف Pubspec.yamlوتحت السطر
Cupertino_iconsسطر جديد يحتوي
shared_preferences: ^0.5.1+1
ثم نضغط package getكما فعلنا مع حزمة .json
المثال التالي يظهر زر عند الضغط عليه سيجلب قيمة محفوظة بالمفتاح counterويزيد عليها
واحد ويطبعها على الكونسول ثم يعيد حفظها ,واجهة البرنامج هي:
StackOverFlow Arabi
93
main.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: _incrementCounter,
child: Text('Increment Counter'),
),
),
),
));
}
_incrementCounter() async {
StackOverFlow Arabi
94
قواعد البيانات :Sqlite
تعتبر ( SQLiteاس كيو لايت ) نظام إدارة قواعد بيانات علائقية مثل ( MySQLو
) PostgreSQLمضمنة في مكتبة مبرمجة بلغة Cصغيرة الحجم تقريبا 511كليوبايت.
وخلافا لأنظمة إدارة قواعد البيانات التي تتبع نظام ( عميل -خادم ) فإن محرك اس كيو لايت غير
مستقل عن البرنامج التي يتخاطب ويتواصل معه .وبدلا عن ذلك في مكتبة اس كيو لايت تربط
بداخل ذلك البرنامج و هكذا تصبح متكاملة مع البرنامج .ويقوم البرنامج باستدعاء وظائف اس كيو
لايت بواسطة باستدعاءات داخلية بسيطة مما يقلل الزمن التأخير في الوصول إلى قاعدة البيانات.
قاعدة البيانات اس كيو لايت تحفظ البيانات و التعريفات والجداول في ملف واحد (قابل للنقل بين
أنظمة التشغيل) على الجهاز المستضيف .وهذا التصميم البسيط يسمح بقفل ملف قاعدة البيانات
عند بداية عملية نقل البيانات.
اس كيو لايت طورها الدكتور ريتشارد هب ,و يقدم و يبيع دورات تعلمية عليها و يقدم عقود الدعم
الفني و الإضافات مثل الضغط و التشفير.
ومصدر قاعدة البيانات اس كيو لايت مرخص تحت الترخيص الملكية العامة , public domain
بحيث يمكنك من استعمالها بحرية من قبل أي شخص لأي غرض كان.
مميزاتSQLite
-دعم معظم مقاييس SQL-92والتي شملت المناقلات قاعدة البيانات والتي تحوي على ثلاثة
مميزات Atomicityوتعي قدرة قاعدة البينات على إنجاز كافة المهام أو عدم انجازها بالكامل
مثل القدرة على نقل الودائع بشكل كامل أو فشلها بالكامل بسبب أي سبب من الأسباب.
الميزة الثانية isolatedوهي تعني قدرة التطبيق على جعل المناقلة تظهر منفصلة عن بقية
العمليات ,وهذا يعني أنه لا توجد عملية خارج المناقلة تستطيع باي شكل من الأشكال رؤية البيانات
في وسط المناقلة.
الميزة الثالثة durableوهي تعني ضمان أن المناقلات التي تمت بنجاح تبقى حية باستمرار ولا
تلغى بسبب فشل النظام ,مثال ذلك إذا أخبر نظام قواعد البيانات لحجز الرحلات بأن مقعد ما حجز
بنجاح فإن المقعد سيبقى محجوزا حتى لو انهار النظام.
-صغر حجمها.
-سهولة التركيب.
-سهولة نقل البيانات من مزود إلى آخر.
StackOverFlow Arabi
95
-لا توجد مشاكل بالترميز لا سيما مع اللغة العربية.
-قاعدة البيانات عبارة عن ملف واحد فقط.
-تدعم حجم قاعدة البيانات إلى 1تيرابايت ( 1142جيجابايت) -ماقبل الإصدارة 1.2كان الحد
الأقصى 1 :جيجابايت.
(-شيفرة الاتصال والاستعلام بها سهلة( مشابهة لـ MySQLعلى نحو أبسط.
-يمكن استخدامها على المواقع التي لا تدعمMySQL.
هرمية العمل:
N-tierاو الـ Frame Entityللتعامل مع قواعد سوف نعتمد في هذا الكتاب على هرمية
البيانات SQliteوالتي تعتبر من أفضل وابسط هرميات هندسة البرمجيات والتي تعتمد على
تقسيم الكود إلى طبقات حسب وظيفة كل جزء من الكود.
الشكل التالي يبين الهرمية المتبعة.
StackOverFlow Arabi
96
كما ننشئ ملفات المشروع بشكل يوافق الهرمية كما يلي:
شرح الطبقات:
الطبقة :DALيتم وضع فيها كل كافة توابع الاتصال مع قاعدة البيانات بالإضافة لتابع إنشاء
القاعدة أول مرة.
الطبقة :BLيتم وضع فيها كل الأصناف والتوابع التي تقوم بالعمليات من نوع CRUDوالتي هي
( )create, read, update, deleteأي جميع العمليات التي يمكن تطبيقها على قاعدة البيانات
لصنف معين مثلا (مستخدم ,موظف ,مخزن).
الطبقة :PLيتم فيها بناء واجهة المستخدم التي ستتعامل مع الجدول والأصناف المقابلين لها.
الطبقة :Modelsهي طبقة مساعدة تسهل التعامل مع بيانات نوع الجداول فمثلا لدي جدول
الموظفين فهنا أنشأنا في طبقة الـ Modelsصنف موظف بخصائص معينة.
StackOverFlow Arabi
97
الآن لنطبق هذه الهرمي بشكل مفصل وعلى جدول واحد هو جدول المستخدمين ولتسهيل الأمر
)password كلمة مرور,username اسم مستخدم, Id فرضنا لديهم فقط (معرف مستخدم
: كما يليuser بالبداية ننشئ الصنف
User.dart
class User {
int _Id;
String _Username;
String _Password;
User(this._Id, this._Username, this._Password);
String getUsername() {
return _Username;
}
setUsername(String username) {
_Username = username;
}
String getPassword() {
return _Password;
}
setPassword(String password) {
_Password = password;
}
}
والتي ستساعدنا في التعامل مع الملفات – قواعد البيانات و التزامن التابع الأول الذي سنتحدث عنه
getdb وهو
static Future<Database> getdb() async {
if (_db != null) {
return _db;
} else {
_db = await OpenDb();
return _db;
}
}
StackOverFlow Arabi
98
والذي مهمته أن يقوم بإرجاع قاعدة البيانات في حال كانت موجودة أما في حال كانت غير موجودة
OpenDb سيقوم بتحميلها مستدعيا تابع اخر وهو
static Future<Database> OpenDb() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = join(dir.path, 'Userdb.db');
return Userdb;
}
من مسارها في حال وجودها مسبقاDatabase وهذا بدوره سيقوم بالتقاط مسار التطبيق ثم تحميل
والذي يقوم بإنشاء قاعدة البياناتFirstCreate وعدا ذلك يقوم باستدعاء مرة ثانية لتابع هو
.المطلوبة
static void _FirstCreate(Database db, int version) async {
var CreateUserTableStatment =
"create table User(Id INTEGER PRIMARY KEY AUTOINCREMENT NOT
NULL,Username Text,Password Text)";
await db.execute(CreateUserTableStatment);
}
class DataBaseHelper {
static Database _db;
return Userdb;
}
StackOverFlow Arabi
99
static void Restart() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = join(dir.path, 'Userdb.db');
File f = new File(path);
if (!f.existsSync()) {
deleteDatabase(path);
print("DataBase has deleted");
}
}
await db.execute(CreateUserTableStatment);
}
إلى هنا نكون قد بنينا تابع مهمته الاتصال والتحكم بقاعدة المعطيات والمطلوب الآن بناء تابع
والذي مهمته إجراء العمليات على قاعدة البيانات كما ذكرنا وهو موجود ضمن الطبقةUserAPI
: ويحوي جميع التوابع الخاصة بالتعامل مع المستخدم كما يليBL
UserAPI.dart
import 'package:flutter_app2/DAL/DataBaseHelper.dart';
import 'package:flutter_app2/Models/User.dart';
class UserAPI {
static Future<int> AddUser(User user) async {
var db = await DataBaseHelper.getdb();
Map<String, dynamic> UserMap = new Map();
UserMap["Username"] = user.getUsername();
UserMap["Password"] = user.getPassword();
int result = await db.insert("User", UserMap);
return 0;
}
return result.toList();
}
StackOverFlow Arabi
100
Map<String, dynamic> UserMap = new Map();
UserMap["Username"] = user.getUsername();
UserMap["Password"] = user.getPassword();
UserMap["Id"] = user.getId();
int result = await db
.update("user", UserMap, where: "Id=?", whereArgs:
[user.getId()]);
return result;
}
وعند طلبه للبيانات نضعasync يجب أن نذكر أن أي تابع ينتظر أمرا من خارج التطبيق نضع له
.Future Method ويكون مستقبلي أيawait
: فتكون كما يليPL أما بخصوص الطبقة
MyUi.dart
import 'package:flutter/material.dart';
import 'package:flutter_app2/BL/UserAPI.dart';
import 'package:flutter_app2/Models/User.dart';
import 'package:flutter_app2/AddEditUIFile.dart';
@override
void initState() {
super.initState();
UserAPI.GetAllUser().then((usrs) {
setState(() {
usrs.forEach((usr) {
Users.add(usr);
});
});
});
}
@override
تأليف فيصل الأسود | مهندس برمجيات
Youtube يمكنك الحصول على شرح لكامل محتويات الكتاب على قناة الـ
StackOverFlow Arabi
101
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
floatingActionButton: new FloatingActionButton(
onPressed: () => _NavigateToAdd(context),
child: new Icon(Icons.add),
backgroundColor: Colors.blue),
appBar: new AppBar(
iconTheme: new IconThemeData(
color: Colors.green,
),
title: new Text('AppBar Title'),
backgroundColor: Colors.greenAccent,
),
body: new Column(
children: <Widget>[
new TextField(
decoration: new InputDecoration(hintText:
"Search")),
new Expanded(
child: new ListView.builder(
itemCount: Users.length,
itemBuilder: (BuildContext context, int
position) {
return new Card(
color: Colors.white,
child: new ListTile(
title: new Text(
"${Users[position]["Username"]}"),
subtitle:
new Text("Id:
${Users[position]["Id"]}"),
trailing: new Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new IconButton(
onPressed: () {
_NavigateToEdit(context,
position);
},
icon: new Icon(Icons.edit,
color:
Colors.lightBlue)),
new IconButton(
onPressed: () =>
_deleteUser(context,
position),
icon: new Icon(Icons.delete,
color:
Colors.redAccent)),
],
)));
}))
],
)));
}
StackOverFlow Arabi
102
Users[position]["Username"],
Users[position]["Password"]))));
RefreshList();
}
void RefreshList() {
Users.clear();
UserAPI.GetAllUser().then((usrs) {
setState(() {
usrs.forEach((usr) {
Users.add(usr);
});
});
});
}
Main.dart
import 'package:flutter/material.dart';
import 'MyUi.dart';
import 'package:flutter_app2/MyUi.dart';
List Users;
void main() async {
runApp(new MaterialApp(home: new MyListView()));
}
StackOverFlow Arabi
103
:ويكون المخطط لمثالنا كما يلي
@override
void initState() {
super.initState();
if (command == "Edit") {
setState(() {
_txtUsername.text = user.getUsername();
_txtPassword.text = user.getPassword();
});
}
تأليف فيصل الأسود | مهندس برمجيات
Youtube يمكنك الحصول على شرح لكامل محتويات الكتاب على قناة الـ
StackOverFlow Arabi
104
}
@override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text(command),
backgroundColor: Colors.greenAccent,
),
body: new Center(
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5),
),
new Text(
"Username:",
style: TextStyle(
fontSize: 25,
),
textAlign: TextAlign.left,
),
Padding(
padding: EdgeInsets.all(5),
),
new TextField(
controller: _txtUsername,
decoration: new InputDecoration(hintText: "Username"),
style: TextStyle(fontSize: 20, color: Colors.black),
),
Padding(
padding: EdgeInsets.all(5),
),
Padding(
padding: EdgeInsets.all(5),
),
new Text(
"Password:",
style: TextStyle(
fontSize: 25,
),
textAlign: TextAlign.left,
),
Padding(
padding: EdgeInsets.all(5),
),
new TextField(
controller: _txtPassword,
decoration: new InputDecoration(hintText: "Password"),
style: TextStyle(fontSize: 20, color: Colors.black),
),
Padding(
padding: EdgeInsets.all(5),
),
new RaisedButton(
onPressed: () => AddOrEdit(),
child: new Text(command),
color: Colors.greenAccent,
)
],
),
),
));
StackOverFlow Arabi
105
}
void AddOrEdit() {
if (_txtUsername.text.length > 0 && _txtPassword.text.length > 0) {
if (command == "Add") {
UserAPI.AddUser(new User(0, _txtUsername.text,
_txtPassword.text));
} else {
UserAPI.EditUser(
new User(user.getId(), _txtUsername.text,
_txtPassword.text));
}
Navigator.pop(context);
}
}
}
StackOverFlow Arabi
106
التعامل مع الصور
مقدمة:
تتيح لنا فلاتر بسهولة الية للتعامل مع الصور سواء اكانت مخزنة في الاستديو او التقاطها من
الكاميرا مباشرة.
كما يوفر المحاكي في الاندرويد استديو كاميرا افتراضية وهمية لاختبار عمل الكاميرا داخل التطبيق.
قد يتيح لنا الصنف imagepickerفي flutterالتعامل وجلب أي نوع من الصور أو التقاطها من
الكاميرا ويجب أن نعلم أن التوابع ضمنه متزامنة لذلك يجب تكتب أيضا ضمن توابع متزامنة.
مثال تطبيقي:
التابع التالي بسيط جدا يتيح عند استدعاءه جلب صورة من الاستديو ووضعها ضمن كائن fileومن
ثم وضعه في widgetلعرض الصور.
{picker() async
;)File img =await ImagePicker.pickImage(source: ImageSource.gallery
{)if(img !=null
;image=img
{ )((setState
;)}
}
}
نلاحظ أن التابع تعامل مع ملفات النظام وهنا يجب أن تقوم بتضمين مكتبة الـ ioفي استدعاء
المكتبات كما يلي:
;'import 'package:flutter/material.dart
;'import 'dart:io
;'import 'package:image_picker/image_picker.dart
StackOverFlow Arabi
107
الان نكتب ملف الواجهة كاملا وعند الضغط على الزر ضمنها سيأخذنا إلى الاستديو لجلب الملف
: كما في الكود التاليimage لإظهار الصور وهيwidget وسيضعه في
CameraUi.dart
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(title: new Text("ImageDemo")),
body: new Center(
child: image == null ? Text("There Is No Image") :
Image.file(image),
),
floatingActionButton: new FloatingActionButton(
onPressed: picker,
child: new Icon(Icons.add),
),
);
}
}
void main() {
runApp(new MaterialApp(home: new CameraUi(),));
}
StackOverFlow Arabi
108
الآن ماذا لو أردنا أن نتعامل مع الكاميرا بشكل مباشر هنا يجب أن نستدعي نفس الصنف والتابع
لكن بدلا من استدعاءه من أجل الاستديو نختار الـ cameraويكون بالشكل التالي
{ picker() async
;)File img = await ImagePicker.pickImage(source: ImageSource.camera
{ )if (img != null
;image = img
;)}{ )((setState
}
}
نود أن ننوه أن المحاكي يتيح لنا بيئة تصوير افتراضية فعند تشغيل البرنامج والتقاط صورة
باستخدام الكاميرا ستظهر بالشكل التالي وكأن الكاميرا تعمل:
StackOverFlow Arabi
109
الاشعارات الداخلية
Flutter-local-notification إمكانية اشعار المستخدم بسلاسة كبيرة عن طريق حزمةflutter يتيح
:والتي تقوم عند حدث معين بإشعار المستخدم بالشكل التالي
:مثال تطبيقي
كذلك,في مثالنا التالي سنقوم بإنشاء زر عند الضغط عليه سيقوم بإشعارنا بانه تم الضغط على الزر
.سنقوم بوضع الاستجابة عند الضغط على هذا الاشعار
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
@override
void initState() {
// TODO: implement initState
super.initState();
flutterLocalNotificationsPlugin = new
FlutterLocalNotificationsPlugin();
var android = new
AndroidInitializationSettings('@mipmap/ic_launcher');
var ios = new IOSInitializationSettings();
var initSettings = new InitializationSettings(android, ios);
flutterLocalNotificationsPlugin.initialize(
initSettings, onSelectNotification: clickOnNotification );
}
: فيمكن بناءه بعدة طرق ولكننا سنبنيه بالشكل التاليshowNotification أما التابع
showNotification() async{
var android = new AndroidNotificationDetails(
"channelId", "channelName", "channelDescription"
,priority: Priority.High,importance: Importance.Max);
var iOS = new IOSNotificationDetails();
StackOverFlow Arabi
110
نلاحظ في الصورة النصوص المكتوبة في الأعلى ومواضعها في الإشعار أما في خصوص العبارة
فهي ستظهر عند الضغط على هذا الاشعار ويكون تنفيذ طلب محتوى الاشعار بالتابعPayload
التالي
showNotification() async {
var android = new AndroidNotificationDetails(
"channelId", "channelName", "channelDescription",
priority: Priority.High, importance: Importance.Max);
var iOS = new IOSNotificationDetails();
print("jhj");
var platform = new NotificationDetails(android, iOS);
await flutterLocalNotificationsPlugin.show(
0, 'Notification 1', 'Notification 2', platform,
payload: 'Send Messages');
}
.ًوهكذا نكون قد فهمنا كل جزء من أجزاء البرنامج المطلوب ويكون برنامجنا كاملا
import
"package:flutter_local_notifications/flutter_local_notifications.dart";
import 'package:flutter/material.dart';
import 'dart:async';
@override
void initState() {
// TODO: implement initState
super.initState();
flutterLocalNotificationsPlugin = new
FlutterLocalNotificationsPlugin();
var android = new
AndroidInitializationSettings('@mipmap/ic_launcher');
var ios = new IOSInitializationSettings();
var initSettings = new InitializationSettings(android, ios);
flutterLocalNotificationsPlugin.initialize(initSettings,
onSelectNotification: clickOnNotification);
StackOverFlow Arabi
111
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Local Notification'),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'Click Button',
),
new RaisedButton(
child: Text('Click me'), onPressed: showNotification)
],
),
),
);
}
showNotification() async {
var android = new AndroidNotificationDetails(
"channelId", "channelName", "channelDescription",
priority: Priority.High, importance: Importance.Max);
var iOS = new IOSNotificationDetails();
print("jhj");
var platform = new NotificationDetails(android, iOS);
await flutterLocalNotificationsPlugin.show(
0, 'Notification 1', 'Notification 2', platform,
payload: 'Send Messages');
}
}
StackOverFlow Arabi
112
الحركة Animation
مقدمة:
تقدم حزمة Animation Flutterمكتبات جاهزة معها لإنجاز حركات قوية ,حيث تعطي الحركة
للواجهة قوة وحيوية.
مفهوم الحركات:
معظم الحركات في Flutterتعتمد على الصنف Animationالموجود في المكتبة المضمنة
افتراضيا:
''package: Flutter/animation.dart
والتي تحوي الكثير من الحركات الجاهزة والتوابع القوية.
إن النمط الهيكلي لأي حركة يتم بتقسيم متطلباتها إلى ثلاث أجزاء كما في المخطط التالي:
التصريح
اإلعداد
التنفيذ
وأيضا يجب أن نذكر أن لكل حركة متحكم وهو كائن من نوع AnimationControllerوالذي يقوم
بالتحكم بالحركة من (بدء – توقف – إعادة)
سنستعرض الآن هرمية بناء الحركات في Flutterوسننتقل لأمثلة ننفذ فيها عدة حركات.
في البداية نصرح عن الكائنات الخاصة بالحركة كما يلي:
;Animation animation
;AnimationController animationController
StackOverFlow Arabi
113
وسيتم ضبط فيه كل اعداداتinitAnimation الان سنقوم بتجهيز حركتنا في تابع خاص سنسميه
:الحركة المطلوبة
void initAnimation(){
animationController=AnimationController(duration: Duration(seconds:
1),vsync: this);
flipAnimation=Tween(begin: 0.0,end:
1.0).animate(CurvedAnimation(parent: animationController, curve:
Curves.fastOutSlowIn));
animationController.forward();
}
:السطر الأول
animationController=AnimationController(duration: Duration(seconds:
1),vsync: this);
.وذلك يقوم بتعيين المدة المطلوبة التي ستستغرقها الحركة وفي حالتنا نحدد ثانية واحدة
:السطر الثاني وهو مهم جدا والذي يعرف مكان بدء وانتهاء الحركة وكذلك طريقة دخولها للشاشة
flipAnimation=Tween(begin: 0.0,end:
1.0).animate(CurvedAnimation(parent: animationController, curve:
Curves.fastOutSlowIn));
ويمكنك الاطلاع على الأنواعfastOutSlowIn ودخول نوع2.1 إلى النقطة إلى1.1 بدأنا في النقطة
.الأخرى بالاعتماد على الجدول
StackOverFlow Arabi
114
التعليمة الأخيرة هي:
;)(AnimationController.Forward
مثال تطبيقي:
ليكن لدينا الواجهة التالية ونريد عند فتح التطبيق دخول الصفحة بالشكل التالي:
}
StackOverFlow Arabi
115
class _Flip extends State<Flip> with SingleTickerProviderStateMixin{
Animation flipAnimation;
AnimationController animationController;
@override
void initState(){
super.initState();
initAnimation();
}
void initAnimation(){
animationController=AnimationController(duration: Duration(seconds:
1),vsync: this);
flipAnimation=Tween(begin: 0.0,end:
1.0).animate(CurvedAnimation(parent: animationController, curve:
Curves.fastOutSlowIn));
animationController.forward();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context,Widget chile){
return UiWithoutAnimation();
},
);
}
Widget UiWithoutAnimation()
{
final double pi=3.14;
final double width=MediaQuery.of(context).size.width;
return new Scaffold(
body: Transform(
transform:
Matrix4.identity()..rotateX(2*pi*flipAnimation.value),
child: new Center(
],
),
)
),
),
);
}
StackOverFlow Arabi
116
نلاحظ أننا بنينا الواجهة في تابع منفصل هو UiWithoutAnimationكما يجب أن نذكر أن
الحركة تمت عن طريق وضع التحويل التالي:
)transform: Matrix4.identity()..rotateX(2*pi*flipAnimation.value
بتغير المحاور للدوران مثل rotate y, rotate zسنحصل على حركات جديدة.
وتم استدعاء الواجهة بالكود الرئيسي :main
;'import 'package:flutter/material.dart
;'import 'package:my_project/EnterSlideAnimation.dart
;'import 'package:my_project/FlipAnimation.dart
;'import 'HideShowAnimation.dart
;))void main() => runApp(MaterialApp(home: Flip(),
سنستعرض اآلن أمثلة وعلى عجالة مع العلم أن كل واجهة يمكن استدعاءها بالصنف mainكما شاهدنا
لذلك لن نكرر كود التابع mainكل مرة.
مثال تطبيقي:
اكتب الكود اللازم لبناء الواجهة التالية:
StackOverFlow Arabi
117
مع إمكانية تحريكها دخولا للشاشة كما يلي:
ببساطة سوف نستخدم التابع translateوالذي يقوم بتحريك الواجهة كما في الكود:
transform: Matrix4.translationValues(animation.value*width, 0.0, 0.0),
StackOverFlow Arabi
118
مثال تطبيقي:
اكتب الكود اللازم لبناء الواجهة التالية بحيث عندما نضغط ضغطة واحدة على الصورة سوف تنزل
كما في الشكل.
StackOverFlow Arabi
119
}
class _HideShow extends State<HideShow> with
SingleTickerProviderStateMixin{
Animation hideShowAnimation;
AnimationController animationController;
@override
void initState(){
super.initState();
animationController=AnimationController(duration: Duration(seconds:
1),vsync: this);
hideShowAnimation=Tween(begin: 0.0,end: -
0.15).animate(CurvedAnimation(parent: animationController, curve:
Curves.fastOutSlowIn));
animationController.forward();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context,Widget chile){
return UiWithoutAnimation();
},
);
}
Widget UiWithoutAnimation()
{
final double width=MediaQuery.of(context).size.width;
return new Scaffold(
body: new Center(
child: new Stack(
children: <Widget>[
new Center(
child: Container(
padding: EdgeInsets.all(10.0),
width: 350.0,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0)
),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: Text("Hello"),
elevation: 7.0,
color: Colors.lightBlue,
textColor: Colors.white,
onPressed: (){},
),
new RaisedButton(
child: Text("World"),
elevation: 7.0,
color: Colors.lightBlue,
textColor: Colors.white,
onPressed: (){},
)
],
),
),
StackOverFlow Arabi
120
),
new Center(
child: GestureDetector(
child: Container(
alignment: Alignment.bottomCenter,
width: 350,height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15.0),
image: DecorationImage(image:
AssetImage('assets/images/apple.jpg'),fit: BoxFit.cover)
),
transform: Matrix4.translationValues(0.0,
hideShowAnimation.value*width, 0.0),
),
onTap: (){animationController.forward();},
onDoubleTap:(){animationController.reverse();} ,
),
)
],
),
),
);
}
StackOverFlow Arabi
121
رسائل التنبيه السريعة Toast
تعد الرسائل السريعة Toastمن ابسط الرسائل التي تقدمها التطبيقات للمستخدم وتكون غالباً على
شكل تنبيه يظهر على الشاشة مدة زمنية ويختفي.
فلاتر تقدم لنا في احدى مكتباتها طرق التعامل مع هذا النوع من الرسائل.
لكي نستطيع التعامل مع رسائل الـ Toastعلينا أولا استدعاء المكتبة الخارجية
fluttertoast: ^3.0.1
الى الملف Pubsec.yamlومن ثم packages getبالإضافة الى استدعاء المكتبة الى صنف العمل
الذي يريد استخدامه كما يلي:
;'import 'package:fluttertoast/fluttertoast.dart
يعد استخدام هذا النوع من الرسائل من ابسط الأشياء حيث يكفي فقط استدعاء الطريقة
showToastلإظهار الرسالة كما يمكن ضبط اعدادات خاصة لتلك الرسائل:
(Fluttertoast.showToast
msg: "This is Center Short Toast",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIos: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
;)
الجدول التالي يظهر اهم الخصائص المستخدمة لإظهار رسائل التنبيه السريعة
StackOverFlow Arabi
122
ولإلغاء كافة الرسائل الظاهرة يكون بالتعليمة:
)(Fluttertoast.cancel
gravity:ToastGravity.CENTER bgColor:Colors.red
gravity:ToastGravity.TOP gravity:ToastGravity.BOTTOM
StackOverFlow Arabi
123
قواعد البيانات Firebase
فاير بيز :Firebaseهي خدمة قدمتها Googleمنذ فترة وقد كانت تقتصر فقط على تخزين
البيانات وبعض الأشياء البسيطة ,ولكن في Google I/O 16تم الإعلان عن الكثير من المميزات
الجديدة والرائعة وأصبحت حديث الكثير من المطورين
مميزاتها:
التحقق : Authenticationوهي عملية تسجيل الدخول سواء عن طريق حساب Facebook
,Google, Twitter, Emailوفي نفس الوقت حماية البيانات الموجودة في Database
(بمعنى أنه يمكنك منع أي شخص من استخدام التطبيق دون عملية تسجيل الدخول وهو
الوضع الافتراضي(
قواعد بيانات متزامنة : Real-time Databaseوهي تفيد في تخزين البيانات على السيرفر
وأكثر شيء يميزها هي أنها Real-timeبمعنى أنه أي تغيير يحصل على الداتا بيز سيتغير
فوراً في التطبيق كما سنرى في هذا الشرح.
التخزين :Storageتخزين الملفات والصور.
الاستضافة :Hostingلاستضافة موقعك على. Firebase
الإشعارات :Notificationsإرسال إشعارات.
والعديد من المميزات يمكنك تفقدها عند الدخول الى حسابك في Firebaseمن الجدير بالذكر أن
هذه الخدمات مجانية (ولكن ببعض الحدود ,يمكنك رؤية ماهي الحدود عبر هذا الرابط
https://firebase.google.com
StackOverFlow Arabi
124
سنبدأ في هذا الدرس شرح عن Firebase Databaseوسنحاول مستقبلاً بإذن الله شرح باقي الأمور
بدايةً يجب أن يكون لديك حساب Googleأي حساب .Gmail
نذهب الى موقع Firebaseنضغط Get Startثم نختار Add project
ثم نضع اسم المشروع الذي نريد ونضع الدولة والموافقة على الشروط ثم انشاء
StackOverFlow Arabi
125
بعد ذلك سيتم إنشاء المشروع ,نضغط على Add Firebase to Your Android App
StackOverFlow Arabi
126
نقوم بإنشاء تطبيق اندرويد جديد ثم نتوجه الى AndroidManifest.xmlلالتقاط اسم الحزمة ونضعها
كما يظهر في الصورة ثم نضغط :Register App
بعد ذلك نضغط على Download google-services.jsonوهو ملف إعدادات الذي يربط مشروعنا
على Firebaseبمشروعنا فيAndroid Studio
StackOverFlow Arabi
127
سيتم تحميل هذا الملف قم بنسخه ثم نتوجه الى Android Studioونضغط بالزر الأيمن على
مجلد appونختارShow in Explorer
سيتم فتح مجلد المشروع في حاسوبك نقوم بلصق هذا الملف الذي حملناه في مجلدapp
وفي ملف الصنف الذي سنبني به واجه التخاطب مع Firebaseنضيف المكتبات كما يلي:
;'import 'package:firebase_database/firebase_database.dart
StackOverFlow Arabi
128
الان نعود الى واجهة السيرفر Firebaseونضغط على زر Databaseلإنشاء قاعدة جديدة
هنا عليك الاختيار بين قاعدة بيانات تدريبية وهي الخيار الثاني او قاعدة بيانات لمشروع حقيقي
ويفضل في حال كانت تجربتك الأولى مع فايربيز ان تبدأ بقاعدة بيانات تدريبية مع العلم انها غير
امنة
StackOverFlow Arabi
129
كما يمكننا ان نرى البيانات التي نملكها في قاعدة بياناتنا من التبويب Databaseفي واجهة
السيرفر Firebase
الان يجب ان نبدأ برمجياً بعمليات الاتصال بالــ فايربيز الخاصة بنا.
ما سنقوم به بالبداية هو تعريف كائن مهمته الاتصال مع قاعدة البيانات الخاصة بنا وذلك كما
يلي:
final
;)"studentReference=FirebaseDatabase.instance.reference().child("student
هذا التابع اتصل مع قاعدة البيانات الخاصة بنا وانشئ حقل هو studentيمثل جدول الطلاب.
الخطوة التالية هي ان ننشئ احداث وتوابع تستجيب لها لكي يكون الاتصال مع القاعدة متزامناً
بحيث أي تغير يحصل في القاعدة سوف ينعكس اثره على تطبيقنا مباشرة.
;StreamSubscription<Event> _onStudentAddedSubscription
;StreamSubscription<Event> _onStudentChangedSubscription
=_onStudentAddedSubscription
;)studentReference.onchildAdded.listen(_onStudentAdded
=_onStudentChangedSubscription
;)studentReference.onchildChanged.listen(_onStudentUpdated
في الكود السابق قمنا بإنشاء مراقبين لحدثين لمراقبة الإضافة والتعديل وعند حدوث أي تغيير بهما
سوف يستدعى التابعين الخاصين بالإضافة والتعديل كما يلي على الترتيب:
StackOverFlow Arabi
130
void _onStudentAdded(Event event){
setState(() {
itmes.add(new Student.fromSnapShot(event.snapshot));
});
}
}
final
studentReference=FirebaseDatabase.instance.reference().child("student")
;
class _ListViewStudent extends State<ListViewStudent>{
List<Student> itmes;
StreamSubscription<Event> _onStudentAddedSubscription;
StreamSubscription<Event> _onStudentChangedSubscription;
@override
initState(){
super.initState();
items=new List();
_onStudentAddedSubscription=studentReference.onchildAdded.listen(_onStu
dentAdded);
StackOverFlow Arabi
131
_onStudentChangedSubscription=studentReference.onchildChanged.listen(_o
nStudentUpdated);
}
@override
void dispose(){
super.dispose();
_onStudentAddedSubscription.cancel();
_onStudentChangedSubscription.cancel();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: new AppBar(title: new Text("Firebase"),
centerTitle: true,
backgroundColor: Colors.greenAccent
),
body: Center(
child: ListView.builder(itemCount: itmes.length,
padding: EdgeInsets.only(top: 12.0),
itemBuilder: (context,position){
return new Column(
children: <Widget>[
Divider(height: 6.5,),
ListTile(title: new Text("{$itmes[position].name}",
style: TextStyle(color: Colors.red,fontSize: 25.0),
),
subtitle:new Text("{$itmes[position].name}",
style: TextStyle(color: Colors.red,fontSize: 11.0),
),
leading: Column(
children: <Widget>[
CircleAvatar(backgroundColor: Colors.redAccent,
radius: 16.0,
child: new Text("{$itmes[position].name}"),)
IconButton(icon: Icon(Icons.delete),color:
Colors.lightBlueAccent,onPressed:
()=>_deleteStudent(context,items[position],position),),
IconButton(icon: Icon(Icons.delete),color:
Colors.lightBlueAccent,onPressed:
()=>_onStudentChanged())
],
),
onTap:
()=>_NavigateToStudent(context,items[position],position),
)
],
);
}
),
),
floatingActionButton: FloatingActionButton(onPressed:
(){_createNewStudent();},
child: new Icon(Icons.add),),
);
}
void _onStudentAdded(Event event){
setState(() {
itmes.add(new Student.fromSnapShot(event.snapshot));
});
}
void _onStudentChanged(Event event){
var
StackOverFlow Arabi
132
oldStudentValue=itmes.singleWhere((student)=>student.id==event.snapshot
.key);
setState(() {
items[items.indexOf(oldStudentValue)]=new
Student.fromSnapShot(event.snapshot);
});
}
void _deleteStudent(BuildContext context,Student student,int
position) async{
await studentReference.child(student.id).remove().than((_){
setState(() {
items.removeAt(position);
});
});
}
void _NavigateToStudent(BuildContext context,Student student,int
position){
await Navigator.push(context,MaterialPageRoute(builder: (context)
=>StudentScreen(student))
);
}
void _createNewStudent(BuildContext context){
await Navigator.push(context,MaterialPageRoute(builder: (context)
=>StudentScreen(new Student(null, "", "", "", "", "")))
);
}
}
_ مهمته هي الانتقال للشاشة التالية والتي تقوم بعرض مكان حقولcreateNewStudent التابع
.ادخال مستخدم جديد او العديل عليه
void _createNewStudent(BuildContext context){
await Navigator.push(context,MaterialPageRoute(builder: (context)
=>StudentScreen(new Student(null, "", "", "", "", "")))
);
}
:اما الشاشة التالية فهي بتصميم بسيط لإظهار حقول الادخال ويكون كما يلي
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:firebase_demo/model/Student.dart';
}
final
studentReference=FirebaseDatabase.instance.reference().child("student")
;
class _StudentScreen extends State<StudentScreen>{
TextEditingController _nameController;
StackOverFlow Arabi
133
TextEditingController _ageController;
TextEditingController _departmentController;
TextEditingController _descriptionController;
TextEditingController _cityController;
final Student student;
_StudentScreen(this.student);
@override
void initState(){
_nameController=new TextEditingController(text:
widget.student.name);
_ageController=new TextEditingController(text: widget.student.age);
_departmentController=new TextEditingController(text:
widget.student.department);
_descriptionController=new TextEditingController(text:
widget.student.description);
_cityController=new TextEditingController(text:
widget.student.city);
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(
appBar: AppBar(title: Text("Student"),
),
body: new Container(
margin: EdgeInsets.only(top: 12.0),
alignment: Alignment.center,
child: new Column(
children: <Widget>[
TextField(
controller: _nameController,
decoration: InputDecoration(icon:
Icon(Icons.person),labelText: "name"),
),
Padding(padding: EdgeInsets.only(top: 7.0)),
TextField(
controller: _cityController,
decoration: InputDecoration(icon:
Icon(Icons.location_city),labelText: "name"),
),
Padding(padding: EdgeInsets.only(top: 7.0)),
TextField(
controller: _departmentController,
decoration: InputDecoration(icon:
Icon(Icons.departure_board),labelText: "name"),
),
Padding(padding: EdgeInsets.only(top: 7.0)),
TextField(
controller: _descriptionController,
decoration: InputDecoration(icon:
Icon(Icons.description),labelText: "name"),
),
Padding(padding: EdgeInsets.only(top: 7.0)),
TextField(
controller: _ageController,
decoration: InputDecoration(icon:
Icon(Icons.data_usage),labelText: "name"),
),
Padding(padding: EdgeInsets.only(top: 7.0)),
FlatButton(
onPressed: (){
if(widget.student.id!=0){
studentReference.child(widget.student.id).set({
StackOverFlow Arabi
134
'name':_nameController,
'age':_ageController,
'city':_cityController,
'department':_departmentController,
'description':_descriptionController,
}).than((_){
Navigator.pop(context);
});
}
else
{
if(widget.student.id!=0){
studentReference.push.set({
'name':_nameController,
'age':_ageController,
'city':_cityController,
'department':_departmentController,
'description':_descriptionController,
}).than((_){
Navigator.pop(context);
});
}
},
child:
(widget.student.id!=null)?Text("Update"):Text("Add") ,
)
],
),
),
);
}
}
StackOverFlow Arabi
135
كذلك يوجد الكثير من الأشياء التي يمكن ان نقوم بها باستخدام قواعد البيانات فايربيز مثل
الدخول باستخدام الايميل او حساب الفيس بوك او حتى التعامل مع الصور ضمن قواعد البيانات
والصوتيات ولكن سنكتفي في كتابنا الى هذا الحد ولعل باقي الشروحات ستكون على القناة على
اليوتيوب.
StackOverFlow Arabi
5
136
النشر والربح من التطبيقات
Publish Applications
لنبدأ بشرح إضافة أنواع الإعلانات المختلفة لتطبيق فلاتر ,بالبداية علينا تحديد معلومات التطبيق
من نوع الجهاز الهدف ,الكلمات المفتاحية المتعلقة بفكرة التطبيق ,تاريخ الانشاء ,وإمكانية
الاستخدام من قبل الأطفال والعديد....
نعرف كائن معلومات تطبيق كما يلي:
static final MobileAdTargetingInfo targeInfo
(=new MobileAdTargetingInfo
textDevices:<String>[],
keywords:<String>['music','video','picture'],
;)(birthday:new DateTime.now
childDirected:true,
;)
الان سننشئ وحدتين اعلانيتين مختلفتين ,الأولى من نوع BannarAdوالتي تظهر كما في الشكل
التالي:
StackOverFlow Arabi
138
:يمكن تعريفها بالشكل
BannerAd createBannarAd(){
return new BannarAd(
adUnitId:BannerAd.testAdUnitId,
size:AdSize.bannar,
targetingInfo:targeInfo,
listener:(MobileAdEvent event)
{
print("Bannar event :$event");
}
);
}
anchor كما يمكن تغيير موضعها عن طريق الخاصة
targetingInfo:targeInfo,
listener:(MobileAdEvent event)
{
print("Interstitial event :$event");
}
);
}
بحيث نكتبinitState() الوحدة الاعلانية الأولى يمكن استدعائها عن طريق الطريقة الموروثة
:ضمنها
_bannerAd=createBannarAd()..show();
StackOverFlow Arabi
139
: كما يليdispose ونغلقها في الطريقة
_bannerAd.dispose();
:اما الوحدة الاعلانية الثانية يمكن استدعائها عن طريق أحد الازرار لتظهر كما يلي
child: InkWell(
onTap: (){
createInterstitialAd()
..load()
..show();
},
));
}
class _MyAppScreen extends State<MyAppScreen>
{
static final MobileAdTargetingInfo targeInfo=new
MobileAdTargetingInfo(
textDevices:<String>[],
keywords:<String>['music','video','picture'],
birthday:new DateTime.now();
childDirected:true,
);
@override
BannerAd _bannerAd;
InterstitialAd _interstitialAd;
BannerAd createBannarAd(){
return new BannarAd(
adUnitId:BannerAd.testAdUnitId,
size:AdSize.bannar,
targetingInfo:targeInfo,
تأليف فيصل الأسود | مهندس برمجيات
Youtube يمكنك الحصول على شرح لكامل محتويات الكتاب على قناة الـ
StackOverFlow Arabi
140
listener:(MobileAdEvent event)
{
print("Bannar event :$event");
}
);
}
InterstitialAd createInterstitialAd(){
return new InterstitialAd(
adUnitId:InterstitialAd.testAdUnitId,
targetingInfo:targeInfo,
listener:(MobileAdEvent event)
{
print("Interstitial event :$event");
}
);
}
void initState(){
_bannerAd=createBannarAd()..show();
}
@override
void dispose()
{
_bannerAd.dispose();
_interstitialAd.dispose();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return new Scaffold(body: new Container(
child: InkWell(
onTap: (){
createInterstitialAd()
..load()
..show();
},
),
)
) ;
}
StackOverFlow Arabi
141
ولكن يجب ان نعلم ان كل ما بنيناه هو لوحدات إعلانية تجريبية فيجب ان نضع رقم وحدتنا
الصحيح التي انشأناها في حسابنا في Admobوتوضع كما يلي:
adUnitId:"ca-app-pub-4245565566455613/641455455455",
الى هنا نكون قد اضفنا الإعلانات المطلوبة تبقى لدينا ان ننشر التطبيق في احد المتاجر.
StackOverFlow Arabi
142
نشر تطبيقات اندرويد
في هذه القسم سيتم شرح خطوات نشر التطبيق على متجر جوجل )(Google Play Store
يمكن تلخيص خطوات نشر التطبيق على متجر جوجل بما يلي:
تصدير التطبيق ) (Exportكملف من نوع ). APK (Android Package
عمل توقيع رقمي ) (Digital Signatureللتطبيق باستخدام شهادة )(Certificate
توقيع التطبيق رقمياً.
يساعد نظام اندرويد في تحديد هوية مالك التطبيق
رفع التطبيق على المتجر.
استخدام سوق اندرويد ) (Android Marketلاستضافة وبيع التطبيق.
في ما يلي سنقوم بشرح خطوات إعداد التطبيق للتوقيع ومن ثم سنقوم بشرح طريقة نشر
التطبيق:
مراجعة ملف ال:Manifest.xml
راجع ملف بيان التطبيقات الافتراضي AndroidManifest.xmlالموجود في
<app dir> / android / app / src / main
وتحقق من صحة القيم ,وخصوصًا:
)aفي الوسم applicationعدل الخاصة labelلتعكس الاسم النهائي للتطبيق.
)bإزالة إذن android.permission.INTERNETإذا كان التطبيق الخاص بك لا
يحتاج إلى الوصول إلى الإنترنت .يتضمن القالب القياسي هذه العلامة لتمكين
الاتصال بين أدوات flutterوالتطبيق الجاري تشغيله.
StackOverFlow Arabi
143
مراجعة ملف التكوين :Build
راجع ملف إنشاء ملف Gradleالافتراضي الموجود في
<app dir> / android / app
وتحقق من صحة القيم ,وخصوصًا:
في القسم defaultConfigحدد معرف التطبيق ApplicationIdواسم وكود النسخة
version code & version nameكذلك حدد اصدار النظام الأدنى المقبول او اصدار
جهاز الهدف للتطبيق .minSdkVersion & targetSdkVersion
اعد تشغيل تطبيقك لتتحقق من حقيقة استخدام ايقونتك الخاصة عوضاً عن تلك
الافتراضية.
انشاء المفتاح الخاص بتطبيقك :Keystore
في حال عدم تملكك لمفتاح خاص بتطبيقك عليك ان تولد واحداً بالتعليمة التالية في
الكونسول الخاص بـ .flutter
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -
keysize 2048 -validity 10000 -alias key
StackOverFlow Arabi
144
.مع العلم ان هذا الملف خاص بك وحدك ولا يجب ان يتطلع عليه الاخرون
:األساس
android {
:البديل
def keystoreProperties = new Properties()
def keystorePropertiesFile =
rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new
FileInputStream(keystorePropertiesFile))
}
android {
:األساس
buildTypes {
release {
// TODO: Add your own signing config for the
release build.
// Signing with the debug keys for now, so
`flutter run --release` works.
signingConfig signingConfigs.debug
}
}
:البديل
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
StackOverFlow Arabi
145
:Proguard تفعيل الـ
: في المسار التاليproguard-rules.pro بالبداية ننشئ ملفاً باسم
/android/app/proguard-rules.pro
:نضع فيه ما يلي
#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
الاعدادات السابقة هي اعدادات أساسية لحماية مكتبات فلاتر الأساسية وأي مكتبة خارجية
.ًمضافة عليك إضافة قاعدتها الخاصة ضمن الملف ايضا
بحيث نضيف ونعدل بعضbuild.gradle في ملف الProguard ثانياً علينا تفعيل الـ
:الاعدادات
android {
...
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
useProguard true
proguardFiles
getDefaultProguardFile('proguard-android.txt'), 'proguard-
rules.pro'
}
}
}
StackOverFlow Arabi
146
:تحرير التطبيق للنشر
: لكتابة التعليمات التاليةflutterاستخدم سطر الأوامر كونسول الخاص بـ
StackOverFlow Arabi
147
رفع التطبيق:
لكي تتمكن من رفع تطبيقك على متجر Google Storeيجب ان تملك حساب مطور وان يكون
لديك جاهزاً ما يلي:
ملف APKالخاص بالتطبيق والذي تم توقيعه رقمياً.
صور ) (screenshotsتوضح واجهات التطبيق ) صورتان على الأقل)
وصف مختصر للتطبيق ووظائفه.
من الصفحة الرئيسية للمطور قم بالنقر على الرابط Upload Applicationحيث ستظهر
الصفحة في شكل والتي من خلالها يتم رفع ملف APKوكذلك ملفات الصور .يجب ايض ًا إدخال
بعض البيانات الخاصة بالتطبيق مثل عنوان التطبيق ,وصف له ,آخر التعديلات على التطبيق نوع
التطبيق وتصنيفه.
بعد نشر التطبيق على متجر جوجل ,يمكن تتبع أي تعليقات يرسلها المستخدمون وكذلك أي
أخطاء محتملة في التطبيق وعدد مرات تنزيله.
StackOverFlow Arabi
148
المراجع
Moises Belchin & Patricia Juberias 2014 ."Web Programming With Dart"
StackOverFlow Arabi
149
النهاية
StackOverFlow Arabi
150
StackOverFlow Arabi