Adapter pattern


مستوى المقال: متوسط

بسم الله الرحمن الرحيم

كما ما بدأناه بالحديث عن أكثر  Design pattern استخداما في بيئة Android واليوم سنتحدث عن أحد أنواع Structural pattern , ألا وهو  Adapter  pattern ,قبل أن نبدأ, وبما أننا في أول pattern في هذه السلسة من نوع Structural فماذا نعني ب Structural pattern ؟

Structural pattern : هو تصميم للكود بطرق تسهل فهم العلاقة بين classes و objects والربط بينها.

جميل جدا, إذا ماهو Adapter  pattern ؟ من كتاب GoF

اقتباس

The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the "adaptee" with a class that supports the interface required by the client.

ولتبسيط الأمور, هذا pattern يستخدم لربط بين two classes لا يمكن الربط بينهما لعدم التوافق.

كثيراً ما تكون التعريفات غير مفيدة, لنبدأ بكتابة بعض الأكواد تشرح هذا pattern.

بداية دعنا نعرف المشكلة, لو فرضنا أن لديك class قديم قمت ببنائه, ومن ثم اردت الربط بينه وبين class جديد فلن تقوم بإعادة كتابته, وإنما تقوم ببناء interface يقوم بالربط بينهم كما سنقوم بذالك في المثال التالي.
لنفترض أن لدينا نظام إدارة فصول إلكتروني, وأردنا ربطه مع نظام حضور وانصراف جديد,فكان شكل interface الخاص بنا كالتالي.

public interface Xattendance {
    public String getStuedntName();
    public long getStuedntId() ;
    public String getTime();
    
    public void setStuedntName(String stuedntName);
    public void setStuedntId(long stuedntId);
    public void setTime(String time);
}

ثم قمنا بعمل implementation له داخل هذا class

public class XattendanceImpl implements Xattendance{

    String stuedntName;
    long stuedntId;
    String time;
    
    public String getStuedntName() {
        return stuedntName;
    }

    public void setStuedntName(String stuedntName) {
        this.stuedntName = stuedntName;
    }

    public long getStuedntId() {
        return stuedntId;
    }

    public void setStuedntId(long stuedntId) {
        this.stuedntId = stuedntId;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}

ولكن وقع الإشكال عندما أردنا ربط هذا class الخاص بنا نظام لتحضير والذي كان شكل interface الخاص به كالتالي:

public interface XnewAttendaceSystem {
    public String getName() ;
    public void setName(String name);

    public String getId();
    public void setId(String id);

    public String getMinute();
    public void setMinute(String minute);

    public String getHour();
    public void setHour(String hour);
}

وclass الذي سيقوم بعمل implementation له بهذا الشكل

public class XnewAttendaceSystemImpl implements XnewAttendaceSystem{
    String name;
    String id; 
    String minute;
    String hour;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getMinute() {
        return minute;
    }

    public void setMinute(String minute) {
        this.minute = minute;
    }

    public String getHour() {
        return hour;
    }

    public void setHour(String hour) {
        this.hour = hour;
    }
   
}

فكما ترى لدينا two classes من نوعين مختلفين, فيلزمنا إنشاء class لنسميه المحول, حيث يمكننا عن طريقه ربط two classes مع بعضهما.

وهو يقوم بعمل implementation ل XnewAttendaceSystem وفي constructor يستقبل object من نوع Xattendance ويقوم بالتحويل

public class fromOldClassToNewSystem implements XnewAttendaceSystem{

    String name;
    String id; 
    String minute;
    String hour;
    
    final Xattendance xattendance;

    public fromOldClassToNewSystem(Xattendance xattendance) {
        this.xattendance = xattendance;
        adapt();
    }
   
    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getId() {
        return this.id;

    }

    @Override
    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String getMinute() {
        return this.minute;

    }

    @Override
    public void setMinute(String minute) {
        this.minute = minute;
    }

    @Override
    public String getHour() {
        return this.hour;
    }

    @Override
    public void setHour(String hour) {
        this.hour = hour;
    }
    
    public void adapt(){
        setName(xattendance.getStuedntName());
        setId(xattendance.getStuedntName());
        setHour(xattendance.getTime().substring(0, 1));
        setMinute(xattendance.getTime().substring(3, 4));
    }
    
}

فكما ترى تم تحويل object من نوع إلى أخر, فدعنا نلقي نظرة عن طريقة استخدم هذا pattern.

سنقوم بإنشاء بيانات نقوم نحن بكتابتها- من الممكن أن تكون البيانات قادمة من API أو من Database, ولكن هنا لتبسيط المقالة ستكون مكتوبة يدوياً- ومن ثم نحولها من النظام الخاص فينا, إلى النظام الذي قمنا بالربط معه.

    public static void main(String[] args) {

      Xattendance x = new XattendanceImpl();
        x.setStuedntId(10110101);
        x.setStuedntName("ameen");
        x.setTime("12:30");
        XnewAttendaceSystem obj = new fromOldClassToNewSystem(x);
        //test 
        System.out.println(obj.getName());
        System.out.println(obj.getHour());
    }

وبهذا نلاحظ أنه تم إرسال البيانات من نظام إلى أخر باستخدام adapter pattern.

ختاما لتعلم أن لدينا نوعان من adapter الأول هو object adapter وهو الذي تعاملنا معه في المثال اّنف الذكر, والذي استخدمنا في بنائه مبدأ composition, والأخر هو adapter class والذي يعتمد في بنائه على multiple inheritance

وهو غير مدعوم في java تجنباً لمشكلة DDD  (deadly diamond of death), ولكن يمكنك تطبيقها باستخدام لغات تدعم multiple inheritance كلغة C++ ويكون شكل Digram كالتالي:

Screenshot from 2016-12-31 16-23-00.png

 

إلى هنا, هذا ما كان في الجعبة ووفق الله الجميع وصلى الله وسلم وبارك

 

المراجع:

- sourcemaking.com/design_patterns

- java design pattern , rohit joshi

- head first design pattern

 

3zcs

1


اراء المستخدمين


لاتوجد تعليقات لعرضها .



انشئ حساب جديد او قم بتسجيل دخولك لتتمكن من اضافه تعليق جديد

يجب ان تكون عضوا لدينا لتتمكن من التعليق

انشئ حساب جديد

سجل حسابك الجديد لدينا في الموقع بمنتهي السهولة .


سجل حساب جديد

تسجيل الدخول

هل تمتلك حساب بالفعل ؟ سجل دخولك من هنا.


سجل دخولك الان

Ads Belongs To 3zcs

  •  أهلا وسهلاً بكم في لمحة مبسطة عن الــ BroadcastReceiver  ، سأتكلم في هذي المقالة عن تعريف الـ BroadcastReceiver و طريقة اضافتها لبرنامجك .. 
    تنويه : اللغة المستخدمة في الشرح هي   kotlin
    ماهو  BroadcastReceiver  ؟
    هو عبارة عن أرسال و استقبال بين البرنامج و النظام ، عند حدوث event معين يكون معرف مسبقا في النظام او يتم تعريفه من قبل المبرمج  . 
    في البداية دعنا نلقي نظرة على المعرفة مسبقا في النظام - بعض منها : 
    Battery Low  WI-Fi connected  BATTERY_OKAY Incomming SMS  AIRPLANE_MODE  BATTERY_CHANGED ACTION_POWER_CONNECTED  
     
    الأن عندما تريد إنشاء حدث معين أنت أمام خيارين هما : 

      statically BroadcastReceiver * 
                      هو اضافة حدث في ملف الـــ AndroidMainfest.xml                               
     
    أو 

    Dynamic BroadcastReceiver  *
     تسجيل الحدث  بإستخدام ال جافا / الكوتلين داخل ال activity . 
     
    ملاحظة : 
    ال BroadcastReceiver لا يقبل عمليات تاخذ وقت في تنفيذها مثل استخرج بيانات أو ارسال بيانات او عمل مؤقت .... الخ  ، لانه يعمل في ال main thread . 
     
    لنأخذ مثال بسيطا لعمل برنامج يظهر نص عند الضغط على الزر   بإستخدام الــ  statically BroadcastReceiver   : 
    في البداية دعنا ننشي كلاس يظهر لي رسالة عند ضعط المستخدم على الزر وليكن اسم هذا الكلاس : MybroadcastReceiver 
    class MybroadcastReceiver:BroadcastReceiver() {    override fun onReceive(p0: Context?, p1: Intent?) {         Toast.makeText(p0!!," Hello form First receiver ",Toast.LENGTH_LONG).show()     } } الان ننتقل الى كلاس ال MainActivity 
    class MainActivity : AppCompatActivity() {   override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)         // send receiver when  clicked button          btn_sendReceiver.setOnClickListener({             // declared intent and pass MybroadcastReceiver  ...             var intent = Intent(this,MybroadcastReceiver::class.java)             sendBroadcast(intent)         })     }  

     

    الأن في ملف ال AndroidMainfest.xml نقوم بتسجل هذا الحدث : 
    // After activity tag .. <receiver android:name=".MybroadcastReceiver">                    </receiver>   النتيجة : 

     
     
     Pesudo Code   1 - craete subClass extends BroadcastReceiver .  2 -override the onReceiver method .  3- add receiver on AndroidMainfest.xml  4 - create event to send data .  5 - declaerd intent .  6 - sendBroadcastReceiver(intent) .   
     
     #### عمل  ( InnerClass BroadcastReceiver )
    بنفس عمل الآلية السابقة ،  نحتاج الى كلاس يراث من broadcastReceiver و أكشن يشير الى هذا الكلاس  ... 
    هذي المرة سأقوم بتعريف كلاس داخلي يشير الى الأكشن .. 
    MainActivity 
    class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // send receiver when clicked button btn_sendReceiver.setOnClickListener({ // declared intent and put action ... var intent = Intent("send.msg.receiver") sendBroadcast(intent) }) } /***** Start InnerClass **************/ public class MybroadcastInner:BroadcastReceiver(){ override fun onReceive(p0: Context?, p1: Intent?) { Toast.makeText(p0!!," Hello form InnerReceiver receiver ",Toast.LENGTH_LONG).show() } } /***** End InnerClass **************/ } لاحظ انه تم تمرير اكشن  - action -  في ال intent ، هذا الاكشن سيتم تعريفه في ملف الــ  AndroidMainfest.xml 
     
    ملاحظة : 
    عند كتابة الكود بالجافا تحتاج الى اضافة كلمة static قبل اسم الكلاس الداخلي مثلا : 
    public static class MybroadcastInner extends BroadcastReceiver{}  ملف ال AndroidMainfest.xml  : 
    // After activity Tag . <receiver android:name=".MainActivity$MybroadcastInner"> <intent-filter> <action android:name="send.msg.receiver"/> </intent-filter> </receiver>  
    لاحظ  انه تم تعريف الكلاس الاساسي ثم تم  وضع علامة ( $) قبل اسم الكلاس الداخلي لكي يتمكن ملف الاندرويد من التعرف على  الكلاس الداخلي  
    ثم بعد ذلك تم إنشاء intent-filter بداخله اكشن   - action -   
    لاحظ ان اسم الاكشن هو نفس الاسم الذي تم تمريره لل intent  . 
    فعند التنفيذ ستظهر نفس النتيجة . 
     
     
    في هذا الجزء تم التعرف على كتابة كلاس داخلي ، و الوصول له من خلال ملف AndroidMainfest.xml  
     
    تنويه : يمكنك تمرير أكشن الى الــ Intent حتى لو كان ال BroadcastReceiver في كلاس منفصل ، مثل ما عملنا في الجزء  السابق . 
    في المقالة القادمة ، سأتحدث عن Dynamic BroadcastReceiver . 
     
    دمتم بخير . 
     
    مستوى المقال: مبتدئ

    بواسطه abdulrahman-abdullah , في

  • بسم الله الرحمن الرحيم
     
    في إستمرارنا في الحديث عن أهم وأحدث مكتبات منصة الأندرويد سنتحدث اليوم عن واحدة من أشهر المكاتب المستخدمة مؤخراً في بعض التطبيقات المشهورة ومن ضمنها تطبيق Jodel الشهير. هذه المكتبة هي مكتبة Crouton. لنتعرف معاً على هذه المكتبة وما أهميتها وكيف تعمل.
    مكتبة Crouton هي مكتبة تتيح لك تنبيه المستخدم وإظهار بعض الإشعارات. تشبه في عملها مكتبة Toast الشهيرة ولكنها تختلف عنها بأنها تحل بعض المشكلات المتعلقة بـ Toast. واحدة من أهم مشاكل الـ Toast هي مشكلة out of context وهي بأن Toast تعمل وتظهر بغض النظر عن الـ context أو المضمون. قد تظهر في سياق مختلف تماماً عن المتوقع أي أنه عند الإنتقال لـ Activity أخرى سيستمر إشعار الـ Toast بالظهور كما أنها غير قابلة للتعديل وموحدة الشكل. كل هذه المشاكل من الممكن حلها بمكاتب مختلفة ولكن من أسهل هذه المكاتب هي Crouton. ولكن السؤال .. كيف تعمل ؟
     
    مكتبة Crouton تتيح لك التحكم الكامل بشكل الإشعارات ولونها وخصائصها بما تتناسب مع تطبيقك. تعطيك كبداية 3 أشكال ثابتة إذا أردت الإبقاء على أشكال Crouton دون أي تغيير. هذه الأشكال هي :
    1-      Alert Notification : باللون الأحمر والخط الأبيض ولمدة 3 ثواني تقريباً
    2-      Info Notification : باللون الأزرق والخط الأبيض ولمدة 3 ثواني تقريباً
    3-      Confirm Notification : باللون الأخضر والخط الأبيض ولمدة 3 ثواني تقريباً
    لنبدأ التطبيق ونرى كيفية إظهار هذه الأشكال.
     
    قبل بداية التطبيق وكما جرت العادة سنحتاج إلى إضافة Dependency لملف build.gradle ولكن هذه المرة سنحتاج لتعديل ملفي الـ gradle.
    بالنسبة لملف build.gradle "project" سنضيف 
    mavenCentral() داخل Block الـ repositories الموجود في buildscript
     
    بالنسبة لملف build.gradle "module" سنضيف
    compile 'de.keyboardsurfer.android.widget:crouton:[email protected]' نقوم بعمل Sync من الأعلى حتى تضاف المكتبة ونستطيع بعدها البدء في العمل.
     
    نقوم بإنشاء Layout بسيطة توضح عمل هذه المكتبة. تحتوي هذه الـ Layout على 3 أزرار خاصة بكل style وأيضاً على زر Toast وزر آخر لتوضيح الـ Custom Notification.
    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingTop="50dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="INFO" /> <Button android:id="@+id/alert" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Alert" /> <Button android:id="@+id/succ" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Success" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/toast" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Toast" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <Button android:id="@+id/custom" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="Custom" /> </LinearLayout> </LinearLayout>  
    صورة توضح الشكل النهائي للـ Layout

     
    نذهب الآن للجزء الأهم وهو ملف الـ Java.
    ملاحظة : سنقوم بتطبيق مفاهيم الـ ButterKnife. لمزيد من المعلومات نرجو زيارة الموضوع التالي:
    دليلك لأفضل مكاتب الأندرويد - الجزء الثاني - ButterKnife
     
    أولاً : سنقوم في البداية بتعريف المتغيرات والـ Buttons
    @BindView(R.id.alert) Button alert; @BindView(R.id.info) Button info; @BindView(R.id.succ) Button succ; @BindView(R.id.toast) Button toast; @BindView(R.id.custom) Button custom;  
    ثانياً : سنقوم بعمل Bind للمتغيرات داخل onCreate method
    ButterKnife.bind(this);  
    ثالثاً : سنقوم بإختيار كل زر للقيام بوظيفة معينة. نلاحظ عندما نريد إظهار Crouton أو إشعار لا نحتاج لتعريف أي متغيرات فهي تعمل بنفس طريقة عمل Toast. لإظهار الإشعارات بالأشكال السابقة التي سبق وتحدثنا عنها سنحتاج لتمرير 3 params فقط وهي الـ Context والنص والشكل سواء كان alert – info – confirm  والطريقة تبدو مشابهة تماماً لطريقة الـ Toast.
    @OnClick(R.id.info) void clicked1() { Crouton.showText(this, "INFO 3alamPro", Style.INFO); } @OnClick(R.id.alert) void clicked2() { Crouton.showText(this, "ALERT 3alamPro", Style.ALERT); } @OnClick(R.id.succ) void clicked3() { Crouton.showText(this, "SUCCESS 3alamPro", Style.CONFIRM); } @OnClick(R.id.toast) void clicked4() { Toast.makeText(getApplicationContext(), "3alamPro", Toast.LENGTH_LONG).show(); }  
    رابعاً : نقوم بإختبار التطبيق ونرى الفرق بين Crouton وبين Toast. وكيف أن Toast تستمر بالظهور حتى عند الخروج من التطبيق ولكن Crouton تختفي وهو المطلوب.

     
    ماذا لو أردنا البقاء على هذه الأشكال ولكن نريد تغيير الوقت المحدد مسبقاً. بكل بساطة سنحتاج لعمل Object من كلاس Configuration الموجود مسبقاً داخل مكتبة Crouton.
    Configuration croutonConfiguration;  
    ومن ثم نعرف الـ Object داخل onCreate method ونعطيه الوقت المطلوب ( الوقت المدخل يقاس بالـ milliseconds والثانية الواحدة تساوي 1000 ميلي ثانية ).
    croutonConfiguration = new Configuration.Builder().setDuration(1000).build(); // 1 sec  
    الآن سنقوم بتغيير الوقت لواحدة من الإشعارات المعرفة مسبقاً ولكننا سنحتاج لتمرير باراميتر إضافي وهو getTaskId() سنتكلم عليه فيما بعد ولكن في الوقت الراهن قد لا يهمنا كثيراً. بهذا التعديل سيتغير الوقت ليصبح ثانية واحدة فقط بدلاً من 3 ثواني.
    @OnClick(R.id.info) void clicked1() { Crouton.showText(this, "INFO 3alamPro", Style.INFO,getTaskId(),croutonConfiguration); }  
    نقوم بالتجربة والمقارنة بين INFO وبين ALERT ونستطيع رؤية الفرق في التوقيت.

     
    بعد الإنتهاء من أول قسم سنبدأ في تصميم الـ Notification الخاصة بنا. وطبعاً هذا الأمر متاح بكل سهولة مع Crouton. سنقوم في البداية بتغيير لون الخط ولون خلفية الإشعار فقط. نلاحظ بأننا كنا نمرر باراميتر من نوع Style داخل crouton. الباراميتر Style.INFO عبارة عن ستايل جاهز ولتغييره سنحتاج لعمل Style خاص بنا. لذلك سنحتاج إلى عمل Object من كلاس style وإضافة جميع الخصائص بشكل يدوي.
    Style style;  
    ونقوم بتعريف الـ Object داخل onCreate method.
    style = new Style.Builder() .setBackgroundColorValue(Color.parseColor("#000000")) // black .setGravity(Gravity.CENTER_HORIZONTAL) .setConfiguration(croutonConfiguration) .setHeight(100) .setTextColorValue(Color.parseColor("#ffffff")).build(); // white  
    للشرح بشكل أعمق للخصائص : 
     setBackgroundColorValue(Color.parseColor()) نستخدمها لإختيار لون خلفية الإشعار.
    setGravity نستخدمها لإختيار مكان النص سواء كان في المنتصف أو على اليمين أو على اليسار.
    setConfiguration نستخدمها لإختيار المدة الزمنية لإظهار الإشعار. (يجب إنشاء Object منفصل كالذي تم إنشاؤه في الخطوات السابقة )
    setHeight نحدد من خلالها إرتفاع الإشعار.
    setTextColorValue نحدد من خلالها لون خط النص داخل الإشعار.
    ومن ثم ننهي التعريف بـ .build
     
    نقوم الآن بتعديل واحدة من الإشعارات المعرفة سابقاً وإختيار الـ style الخاص بنا بدلاً من المعرف مسبقاً.
    @OnClick(R.id.alert) void clicked2() { Crouton.showText(this, "ALERT 3alamPro", style); }  
    ونرى النتيجة. تعمل بشكل مثالي.

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

     
    نقوم بتصميم Layout بسيطة تتضمن شعار الموقع فقط. نلاحظ بأن إرتفاع الـ Layout يمثل إرتفاع الإشعار نفسه لذلك إختيار الإرتفاع مهم جداً.
    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="#000000"> <ImageView android:layout_width="100dp" android:layout_height="30dp" android:layout_centerInParent="true" android:src="@drawable/pro"/> </RelativeLayout> </RelativeLayout>  
    الشكل النهائي للـ Layout.

     
    للاستمرار بذلك يجب علينا تعريف Object جديد من نوع View وعمل Inflate للـ Layout الخاصة بالإشعار. ومن ثم عمل Crouton جديد.
    @OnClick(R.id.custom) void clicked5() { View customView = getLayoutInflater().inflate(R.layout.crouton_custom, null); Crouton.show(this,customView); }  
    والنتيجة ..

     
    إلى هنا نكون قد وصلنا إلى نهاية الشرح الخاص بالمكتبة. وسنقوم في المواضيع القادمة بإستعراض المزيد من المكتبات الخاصة بمنصة الأندرويد بإذن الله.
    مستوى المقال: مبتدئ

    بواسطه Abdulrahman Hasan Agha , في

  • ماهي Firebase Cloud Functions؟
    هي خدمة قدمتها Google منذ بضعة أشهر ومازالت في المرحلة التجريبية BETA ,تساعدك Cloud Functions بتنفيذ أمور معينة عند حدوث أمر معين (عند الكتابة في قاعدة البيانات Firebase Realtime Database) . بعض الأمثلة:
    إرسال الإشعارات
    تعديل بعض النصوص على سبيل المثال (تغيير كلمة “Bad” الى “Not Good” بشكل أوتوماتيكي وبدون أي تدخل منك)
    توليد الصور المصغرة “Thumbnails” بمساعدة Firebase Storage
    والكثير من الأشياء الأخرى(ألقِ نظرة على الروابط في أسفل المقال)
    نأتي الى الأمر المهم وهو كيف سنبدأ  بتجهيز بيئة العمل و نبدأ بإرسال الإشعارات .
    الFirebase Cloud Functions مبنية على لغة Javascript وعلى Framework من نوع NodeJS
    ولهذا يجب علينا أن نقوم بتحميل NodeJS من هذا الرابط ,بعد تثبيته نقوم بفتح CMD او Terminal ونكتب الأمر التالي لتثبيت أدوات Firebase
     
    npm install -g firebase-tools عند نجاح التثبيت ستظهر بهذا الشكل


    ثم نكتب الأمر لتسجيل الدخول بحساب Google واختيار مشروع Firebase
    firebase login بعد ذلك ستُفتح صفحة ويب وتطلب منك تسجيل الدخول

     
    اختر Allow



     
    تمت عملية تسجيل الدخول


     

     
    نعود الى CMD ونقوم بإنشاء مجلد جديد وثم غير مسار CMD الى هذا المجلد (او إذا كنت على ويندوز فقم بفتح المجلد واضغط على زر Shift + زر الفأرة الأيمن واختر “Open Command Window Here” )
    ثم اكتب الأمر لبدء تجهيز مشروع Cloud Functions
     
    firebase init functions


    الآن سيتم عرض كافة مشاريع Firebase الموجودة في حسابك,قم باختيار المشروع الذي تريد



    ثم اختر Y

     
    تم تجهيز الملفات

     
    الآن اذا ذهبنا الى المجلد سنجد به بعض الملفات

     
    نتوجه الى مجلد functions وسنجد داخله ملف index.js هذا هو الملف الذي سنقوم بكتابة Cloud Functions بداخله
    قم بفتحه باستخدام أي محرر أكواد,سأستخدم VSCode يمكنك تحميله من هنا
    سنجد هذه الأكواد ,نقوم بمسحها

     
    سنقوم في مثالنا هذا إرسال إشعار للمستخدم عندما يقوم أحد بمتابعته(بنفس فكرة موقع تويتر) وسنعرض إسم الشخص الذي قام بمتابعته بالإضافة الى صورته في الإشعار
    ولنفترض أنه لديك مثل هذا الترتيب في قاعدة البيانات Realtime Database
    جدول users يحتوي على جميع المستخدمين ونسمي كل مستخدم بناء على UID الخاص بFirebase Auth .
    وداخل كل مستخدم نضع التالي:
    notificationTokens photo رابط صورة المستخدم userName اسم المستخدم
     
    سنقوم بإنشاء جدول نسميه followers والذي سيحتوي على الأشخاص الذين قاموا بمتابعة هذا المستخدم
     

     
    حان وقت العمل , فلنكتب بعض الأكواد
    ولكن قبل هذا سأذكرك بأن Firebase Cloud Fucntions تعمل على مبدأ تنفيذ أمر معين عند حدوث فعل(كتابة على Firebase Realtime Database مثلاً)
    //Cloud Functions Modules const functions = require('firebase-functions'); //Firebase Admin SDK Modules (it will send the Notifications to the user) const admin = require('firebase-admin'); //init Admin SDK admin.initializeApp(functions.config().firebase); السطر الثاني نقوم بتعريف او عمل import لمكتبة Firebase Cloud Functions
    السطر الرابع نقوم بتعريف مكتبة Firebase Admin وهي المسؤولة عن إرسال الإشعارات والكتابة في قاعدة البيانات ثم نقوم بتهيئة مكتبة Admin في السطر الأخير
    exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => { }  
    بعد ذلك نقوم بتعريف Cloud Function عبر exports.sendNotificationOnNewFollow 
    (sendNotificationOnNewFollow هو اسم الFunction يمكنك تسميته كما تشاء) ونجعلها تستمع الى الأحداث في جدول followers
    مايكتب بين هذين القوسين{} عند تعريف Cloud Function يسمى Wildcard وببساطة يعني أننا نريد الإستماع الى الأحداث داخل جدول followers داخل userId داخل followerId ,كما أنها أيضاً تعيد لنا قيمة الحدث (كuserId و followerId)
    وقد يحتوي على أي قيمة 
    كما أنه يمكنك تسميتهم بأي إسم تريد
    الصورة التالية ستوضح لك الفكرة

     
    .onWrite أي أنه عندما يتم الكتابة وهي تعيد لنا حدث event والذي يحتوي على الأمور التى كتبت او تغيرت في Realtime Database
    const userId = event.params.userId; const followerId = event.params.followerId هنا قمنا بالحصول على userId و followerId عن طريق event.params
    يجب عليك أن تكتب نفس الأسماء التى في البارامترز

    سنقوم بكتابة هذا السطر
    if (!event.data.exists()) { return; } وهذا يعني أنه اذا كانت لاتتوفر بيانات(تم عمل متابعة ثم الغاؤها فوراً) عندها قم بعمل return ولا تنفذ أي شيئ آخر
    ثم نقوم بتعريف ميثود getDeviceTokensPromise مهمتها جلب الnotificationTokens الخاصة بالمستخدم الذي تمت متابعته(“Ahmad”)
    const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value'); لاحظ أنه تم استخدام userId الذي عرفناه سابقاً
    ثم بنفس الطريقة نعرف ميثود أخرى تقوم بجلب اسم المستخدم وصورته
    const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value'); هذه المرة قمنا بجلب البيانات الخاصة بناءً على followerId وهو (“Sobhe”)
    الآن سنستدعي هذه الميثودز
    في Cloud Functions يجب علينا دائما أن نعود ب Promise وهو الذي سيقول ل Cloud Functions أنه قد انتهى من التنفيذ
    return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => { const tokensSnapshot = results[0]; const followerSnapshot = results[1]; } داخل Promise نعطيه الميثودز الذي أنشأناها ونضعها داخل مصفوفة Array.
    .then هذه تعني عندما ينتهي من تنفيذ الميثوذز وهي تعود لنا ب Snapshot أسميناها results 
    وبما أننا قد نفذنا أكثر من ميثود فقمنا بتعريف كل Snapshot على حدى وأعطيناها المكان من Array
    ثم سنتأكد من أنه يوجد tokens أم لا عبر السطر
    if (!tokensSnapshot.hasChildren()) { return console.log('There are no notification tokens to send to.'); } واذا لم يوجد سيقوم بطبع رسالة وسيتجاهل ماتبقى من الكود
    بعد ذلك نقوم بأخذ userName و photo من followerSnapshot ونقوم بطباعتهم في log
    const followerName = followerSnapshot.val().userName; const followerPhoto = followerSnapshot.val().photo; console.log('Follower Name is: ', followerName); console.log('Follower Photo is: ', followerPhoto); ثم نقوم بتعريف Payload وهو الإشعار  الذي سنستقبله في تطبيق Android او IOS او Web (سنشرح بشكل بسيط عن طريق الأندرويد)
    وهو يحتوي على:
    title عنوان الإشعار body مانريد كتابته داخل الإشعار(التفاصيل) imgUrl صورة المستخدم (“Sobhe”) // Notification details. const payload = { data: { title: 'you have a New Follower', body: `${followerName} has followed You.`, imgUrl: `${followerPhoto}` } }; ثم نقوم بأخذ notificationTokens من tokensSnapshot وبما أنه قد يحتوي على أكثر من عنصر قمنا بوضعهم في Array
    const tokens = Object.keys(tokensSnapshot.val()); أخيراً نقوم بإرسال الإشعار عبر messaging وهي تأخذ 2 بارامتر tokens و payload
    // Send notifications to all tokens. return admin.messaging().sendToDevice(tokens, payload).then(response => { }) وتعود لنا ب response 
    ثم نقوم بتعريف مصفوفة Array فارغة,سنملؤها لاحقا بNotification Tokens الغير صالحة لنقوم بحذفهم
    const tokensToRemove = []; وأخيراً نقوم بكتابة هذه الأكواد
    response.results.forEach((result, index) => { const error = result.error; if (error) { console.error('Failure sending notification to', tokens[index], error); // Cleanup the tokens who are not registered anymore. if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove()); } } }); return Promise.all(tokensToRemove); إذا كان هنالك خطأ سنقوم بطباعته في Log ثم نعود ب Promise لحذف الTokens الغير صالحة في حال وجودهم
    ليصبح الكود الكامل كالتالي
    //Cloud Functions Modules const functions = require('firebase-functions'); //Firebase Admin SDK Modules (it will send the Notifications to the user) const admin = require('firebase-admin'); //init Admin SDK admin.initializeApp(functions.config().firebase); exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => { const userId = event.params.userId; const followerId = event.params.followerId // If un-follow we exit the function. if (!event.data.exists()) { return; } // Get the list of device notification tokens. const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value'); // Get the follower Info. const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value'); //Execute the Functions return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => { const tokensSnapshot = results[0]; const followerSnapshot = results[1]; // Check if there are any device tokens. if (!tokensSnapshot.hasChildren()) { return console.log('There are no notification tokens to send to.'); } const followerName = followerSnapshot.val().userName; const followerPhoto = followerSnapshot.val().photo; console.log('Follower Name is: ', followerName); console.log('Follower Photo is: ', followerPhoto); // Notification details. const payload = { data: { title: 'you have a New Follower', body: `${followerName} has followed You.`, imgUrl: `${followerPhoto}` } }; // Listing all tokens. const tokens = Object.keys(tokensSnapshot.val()); // Send notifications to all tokens. return admin.messaging().sendToDevice(tokens, payload).then(response => { // For each message check if there was an error. const tokensToRemove = []; response.results.forEach((result, index) => { const error = result.error; if (error) { console.error('Failure sending notification to', tokens[index], error); // Cleanup the tokens who are not registered anymore. if (error.code === 'messaging/invalid-registration-token' || error.code === 'messaging/registration-token-not-registered') { tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove()); } } }); return Promise.all(tokensToRemove); }); }); });  
    نقوم بحفظ الملف Ctrl + S ونعود مرة أخرى الى CMD ونتوجه الى مجلد المشروع
    ونكتب الأمر
    firebase deploy --only functions سيتم بدء رفع الملفات الى Firebase وقد تأخذ العملية بعض الوقت

     
    عند الإنتهاء نذهب الى Firebase Console الى Functions وستجد ظهور Cloud Function الذي أنشأتها
     

     
    سنتوجه الآن الى Android Studio بشكل سريع لترى كيف يتم تنفيذ الإشعار (يمكنك تحميل السورس كود وتعديله كما تشاء)
    نقوم بإنشاء كلاس جديد نسميه MyFCMService والذي سيقوم بتلقى الإشعارات من Cloud Functions ونجعله extends FirebaseMessaginService ولاننسَ أن نقوم بتشغيله من MainActivity
    ثم نقوم بعمل Override لميثود onMessageReceived والتى تعود لنا ب remoteMessage
    ثم نقوم بتعريف title و body و imgUrl ونلاحظ أنه يجب علينا كتابة نفس الإسم المكتوب في Payload الذي كتبناه في Cloud Functions

     
    ثم استدعينا ميثود سنقوم بإنشاءها وهي sendNotification وتأخذ المتغيرات الثلاثة ك بارامترز
    ثم نقوم بتعريف هذه الميثود,وهي ميثود بسيطة تقوم بأخذ البارامترز وعرضهم في Notification
    ونلاحظ أنه قد وضعنا صورة الشخص عبر setLargeIcon وقمنا باستدعاء ميثود getProfilePhotoAsBitmap
    والتي تأخذ imgUrl ك بارامتر
    private void sendNotification(String title, String messageBody, String imgUrl) { Intent intent = new Intent(this, MyFCMService.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* FRequest code */, intent, PendingIntent.FLAG_ONE_SHOT); Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.star_on) .setLargeIcon(getProfilePhotoAsBitmap(imgUrl)) .setContentTitle(title) .setContentText(messageBody) .setAutoCancel(true) .setSound(defaultSoundUri) .setContentIntent(pendingIntent); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()); } أخيراً نقوم بإنشاء الميثود getProfilePhotoAsBitmap وهي ميثود تقوم بأخذ الرابط وتقوم بتحميل الصورة وإرجاعها ك Bitmap لعرضها في Notification,ولهذا قمنا بالإستعانة بمكتبة Glide المختصة بعرض الصور
    private Bitmap getProfilePhotoAsBitmap(String url) { Bitmap bitmap = null; try { bitmap = Glide.with(this).load(url).asBitmap().into(168, 168).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } return bitmap; } حان وقت التجربة  :]
    نقوم بتشغيل تطبيق الأندرويد ثم نجعل “Sobhe” يعمل Follow ل “Ahmad” (يمكنك ادخالهم بشكل يدوي من Firebase Console كما فعلت على سبيل التجربة!)

     
    رابط المشروع على Github
    ملاحظة:
    قد تم إرفاق ملف fcm-cloud-functions-export-Data Structure وهو ملف يحتوي على قاعدة البيانات البسيطة الذي أنشأناها,يمكنك استيرادها  من Realtime Database عبر خيار Import Backup (لا تنسَ عمل نسخ احتياطي لقاعدة البيانات لديك قبل تنفيذ عملية الاستيراد)
     
    بعض المصادر التي قد تهمك
    1,2,3
    مستوى المقال: مبتدئ

    بواسطه 3llomi , في

  • السلام عليكم ورحمة الله وبركاته.

    سنشرح في هذه المقالة طريقة الاستفادة من منصة Transifex لترجمة مشاريعك ترجمة "بشرية" دقيقة بعيدا عن استخدام المترجمات الآلية.
     
    نبذة عن موقع  Transifex: هي منصة متكاملة لترجمة المشاريع البرمجية، حيث يطرح المبرمج أو المسؤول عن ترجمة البرنامج ملف الترجمة الخاص بالبرنامج، ويمكنه تعيين فرق أو إدارتها لتسهيل عملية الترجمة إلى مختلف اللغات. 
     
    هل الموقع مجاني ؟ في حال أردت التسجيل في الموقع كمترجم متطوع فتسجيل الحساب مجاني ويمكنك طلب الانضمام إلى الفرق للبدء بالمساعدة بالترجمة، أما في حال أردت نشر مشروعك لطلب المساعدة على الترجمة من المتطوعين في الموقع أو لتسهيل عملية الترجمة على فريق الترجمة لديك باستخدام هذه المنصة فسيكون نشر المشروع مجانيًا في حال كان مشروعك مفتوح المصدر.
     
    من يستخدم هذه المنصة؟ العديد من المواقع والبرامج المعروفة تستخدم  Transifex لترجمة مشاريعها مثل Trello, Soundcloud, VLC وغيرها. 
     
    كيف أنشر مشروعي (المفتوح المصدر) على المنصة؟ بعد تسجيل حساب جديد في الموقع إضغط على القائمة المجاورة لصورة المستخدم، هذه القائمة هي قائمة المنظمات التي اشتركت بها وتساعد في ترجمة مشاريعها، إضغط على Create Organization لإنشاء منظمة جديدة:



     
    ثم املأ النموذج بالمعلومات الصحيحة لمنظمتك أو فريقك، وضع علامة ☑️ على خيار Yes, my content has Open Source license ويجب أن يكون مشروعك في هذه الحالة مفتوح المصدر ومرخصا بإحدى الرخص المتعارف عليها للمشاريع مفتوحة المصدر (مثل GPL, MIT, Apache وغيرها) وسيُطلب منك إدخال رابط المستودع الخاص بمشروعك لاحقا.
     

     
    تم الآن إنشاء المنظمة الخاصة بك، عليك أن تقوم بإنشاء مشروع جديد بالضغط على 
     
    سيظهر لك نموذج إنشاء المشروع، وسنشرح الخيارات واحدة واحدة:
     
    عليك أن تحدد في هذه الخانة نوع المشروع، private للمشاريع الخاصة التي لا تريد أن يطلع عليها إلا فريقك الخاص، public للمشاريع العامة، اختر public واختر My project is a non-commercial Open Source project في حال كان مشروعك مفتوح المصدر وغير ربحي (في حال لم يكن كذلك فلن يكون نشر مشروعك على المنصة مجانيا) ثم عليك تزويد Transifex برابط المستودع الخاص بمشروعك (على GitHub مثلا) حتى يتحققوا من الرخصة الخاصة بالمشروع:
     

     
     
    في هذه الخانة ستحدد نوع المشروع على Transifex من ناحية طريقة الترجمة، الطريقة الأولى هي File-based يجب عليك فيها أن ترفع ملف اللغات إلى الموقع ويقوم المترجمون بتصفح قائمة الكلمات وترجمتها، أما الطريقة الثانية (وهي طريقة جديدة في تاريخ كتابة هذه الكلمات) فهي تعتمد على Javascript حيث يقوم الموقع باستخراج جميع الكلمات من موقعك، ويعرض موقعك داخل نافذة مصغرة بحيث يمكنك الضغط على الكلمة التي تريد وترجمتها.
     

     
    وهذا مثال من موقع عالم البرمجة على طريقة عرض وترجمة الكلمات (لكن بالطبع لا يمكنني القيام بالترجمة فيجب إضافة مكتبة Javascript تزودني بها Transifex حتى تتمكن من تخزين الكلمات لديها مع الترجمات) :
     

     
    الخيار الأخير تحدد منه اللغة الأصلية لمشروعك واللغات التي تريد الترجمة إليها :
     

     
    كل ما بقي عليك فعله الآن هو دعوة المترجمين إلى المشروع وانتظار الترجمات الدقيقة  🙃
    مستوى المقال: مبتدئ

    بواسطه عمار الخوالدة , في

  • يسعى المُبرمج دائمًا لاختبار تطبيقاته بصورة مُكثّفة وبأكثر من حالة قبل إطلاقها بشكل رسمي. إلا أن هذا لا يعني أن المشاكل لن تحدث، وهنا يأتي دور فريق العمل الذي يحتاج لمراقبة كل شيء عن كثب لتقديم تجربة استخدام مُميّزة، وإلا سيُغادر المستخدم لو وجد الخطأ يتكرّر على فترة زمنية طويلة.
    في إطار عمل ExpressJS هناك أكثر من طريقة لمراقبة الطلبات والأخطاء الحاصلة في التطبيق، منها مكتبة Morgan التي تقوم بكتابة كل طلب وارد في الكونسول Console،  وبالتالي يُمكن معرفة الطلبات الناجحة وتلك التي عادت بـ 404، دلالة على أن المستخدم طلب صفحة غير موجودة. في هذه الحالة يُمكن للقائمين على التطبيق تحليل الطلب ومعرفة فيما إذا كان هناك خطأ بالفعل للتخلّص منه.
    استخدام المكتبة بسيط جدًا يبدأ بتثبيتها
    npm install morgan --save بعدها وفي الملف الرئيسي للتطبيق يتم استخدامها بالطريقة الآتية
    let morgan = require("morgan") app.use(morgan('combined')) وبعد إتمام تلك الخطوات يُمكن مراقبة الكونسول Console وستظهر الطلبات الواردة بهذا الشكل 

    ويُمكن بالمناسبة تغيير combined إلى dev أو tiny لتتغير البيانات التي يتم تدوينها في الكونسول.
    ماذا عن الأخطاء الأُخرى الحاصلة نتيجة لـ Throw؟ هل سيقضي المُبرمج وقته مُراقبًا ملفات الـ Log أو الكونسول طوال الوقت؟ بكل تأكيد لا لأن أداة AirBrake تأتي لمعالجة هذه المشكلة.
    تقوم هذه الأداة برصد الأخطاء ومن ثم إرسال تنبيه للمستخدم عبر بريده بشكل فوري، أو عبر حسابات فريق العمل في تطبيق سلاك مثلًا، وبالتالي يضمن المُبرمج أن يطّلع أولًا بأول على المشاكل الحاصلة.
    طريقة الاستخدام سهلة أيضًا ومُباشرة، تبدأ بالتوجه لموقع الأداة AirBrake.io ثم تسجيل حساب مجاني وإنشاء تطبيق جديد للحصول على مفتاح خاص. ثم يتم تثبيت مكتبة الأداة بالشكل التالي:
    npm install airbrake-js --save في الملف الرئيسي للتطبيق نقوم بالآتي
    let airBrake = require('airbrake-js') let ErrorHandler = require('./node_modules/airbrake-js/dist/instrumentation/express') // إنشاء كائن جديد let airBrakeClient = new airBrake({ projectId: 00000, //يتم استبدال الأصفار بالمعرف الخاص الذي ستحصل عليه بعد التسجيل في الموقع projectKey: 'MYKEY' // يتم استبدال المفتاح هذا بالمفتاح الذي يقوم الموقع بتوليده لك }) أخيرًا وقبل الـ Listen يتم إضافة السطر الآتي
    app.use(ErrorHandler(airBrakeClient)); في هذه الحالة وعند حدوث أي خطأ سيتم التبليغ عبر لوحة تحكّم الموقع أولًا كما هو موضّح في الصورة. مع إمكانية ربط الحساب مع حسابات أُخرى مثلما ذكرت سابقًا لتسهيل التنبيه على الأخطاء.
    مستوى المقال: مبتدئ

    بواسطه feras , في

  • Ads Belongs To 3zcs

    عالم البرمجة

    عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل