1.  أهلا وسهلاً بكم في لمحة مبسطة عن الــ 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 . 
     
    دمتم بخير . 
     
    مستوى المقال: مبتدئ
  2. بسم الله الرحمن الرحيم
     
    في إستمرارنا في الحديث عن أهم وأحدث مكتبات منصة الأندرويد سنتحدث اليوم عن واحدة من أشهر المكاتب المستخدمة مؤخراً في بعض التطبيقات المشهورة ومن ضمنها تطبيق 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); }  
    والنتيجة ..

     
    إلى هنا نكون قد وصلنا إلى نهاية الشرح الخاص بالمكتبة. وسنقوم في المواضيع القادمة بإستعراض المزيد من المكتبات الخاصة بمنصة الأندرويد بإذن الله.
    مستوى المقال: مبتدئ
  3. بسم الله الرحمن الرحيم
     
    إستكمالاً لسلسلة مكتبات الأندرويد سنستعرض اليوم مكتبة من أهم المكتبات التي يستخدمها المطورون بشكل أساسي في تطوير التطبيقات على منصة الأندرويد. هذه المكتبة هي مكتبة Butter Knife.
     
    ولكن السؤال الأهم هو ما فائدة هذه المكتبة ؟
    من المعروف لدى المطورين بأن طريقة ربط الـ View component مع الأوبجكت داخل كود الجافا هو باستخدام الأمر find view by id. ومن ثم ربط الأوبجكت عن طريق R.id.component. هذه الطريقة يوماً بعد يوم تثبت عدم فعاليتها وخصوصاً عند وجود الكثير من الـ components داخل الـ layout التي ستضطر لربطها بشكل يدوي. كما أن الربط العادي ستضطر في البداية إلى إنشاء المتغير في بداية الكلاس أما تعريفه فيجب أن يتم داخل onCreate method. هذه الطريقة باتت تثبت عدم فعاليتها يوماً بعد يوم. ButterKnife هي مكتبة تستطيع من خلالها ربط الأوبجكت مع الـ view component بشكل سريع وسلس وبشكل مباشر. كما تمكنك المكتبة من ربط الـ drawables والـ strings وغيرها وليست مقتصرة فقط على الـ views. 
     
    كيف تعمل هذه المكتبة ؟ لنبدأ ...
    أولاً : وكما جرت العادة يجب إضافة هذه المكتبة داخل المشروع كـ dependency إضافية داخل ملف الـ gradle.build. من المهم أيضاً إضافة الـ annotationProcessor كي تعمل المكتبة بشكل صحيح. ومن ثم عمل sync لكي تتم الإضافة.
    compile 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'  
    ثانياً: بعد عمل sync يمكننا الآن البدء في تصميم الـ layout .. طبعاً سأقوم بتصميم layout بسيطة فقط لتوضيح عمل هذه المكتبة. من الممكن ملاحظة بأن ملف الـ xml لم يتغير بتاتاً ولم نقم بإضافة أي كود إضافي وهذه ميزة إضافية أيضاً لأن هناك الكثير من المكتبات الأخرى التي توفر ميزة الربط ستضطر حين إستخدامها إلى إضافة تعديل بسيط داخل ملف الـ xml.
    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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:padding="15dp"> <TextView android:id="@+id/key" android:layout_width="100dp" android:layout_height="50dp" android:gravity="center" android:text="WEBSITE" /> <TextView android:id="@+id/value" android:layout_width="100dp" android:layout_height="50dp" android:gravity="center" android:text="3alamPro" android:layout_alignParentEnd="true"/> </RelativeLayout> صورة توضح الشكل النهائي للـ layout

     
    ثالثاً: سنبدأ الآن العمل على ملف الـ Java حيث سنتعلم بشكل أكبر طريقة عمل المكتبة. في البداية سنقوم كما جرت العادة بتعريف المتغيرات ولكن هذه المرة وقبل تعريفها سنقوم بكتابة 
    @BindView(R.id.key)  
    ليصبح الشكل النهائي للمتغيرات كالتالي
    public class MainActivity extends AppCompatActivity { @BindView(R.id.key) TextView key; @BindView(R.id.value) TextView value; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); }  
    بهذه الأسطر البسيطة سنكون قد أنتهينا تماماً من الربط .. طبعاً للمقارنة هذا ما سنقوم بكتابته بدون butterknife .. قد يبدو الفرق بسيط .. ولكن مع وجود الكثير من الـ components التي تحتاج للربط سيكون الفرق واضح.
    public class MainActivity extends AppCompatActivity { TextView key; TextView value; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); key = (TextView) findviewbyid(R.id.key); value = (TextView) findviewbyid(R.id.value); }  
    رابعاً: لا يجب أن ننسى إضافة بعد تعريف onCreate method.
    ButterKnife.bind(this);  
    لتصبح onCreate method في شكلها النهائي
    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); }  
    هذا كان توضيح بسيط للميزة الأساسية التي تقدمها المكتبة .. ولكنها أيضاً تقدم المزيد من الإختصارات منها مثلاً [email protected] التي توفر لكن عناء ربط الـ strings الموجودة داخل ملف الـ values بدلاً من getResources().getStrings(R.strings.whatever) أيضاً توفر [email protected] وسنستعرض كيفية عملها.
     
    سنقوم الآن بإنشاء ملف Drawable عبارة عن إطار سنقوم بوضعه على key textview. 
    نقوم بإنشاء ملف Drawable جديد ونسميه background على سبيل المثال

     
    بعد ذلك نقوم بإضافة الكود المرفق ( خلفية صفراء وحواف سوداء )
    <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#eee1"></solid> <stroke android:color="#000" android:width="10dp"></stroke> </shape>  
    بالعودة لملف الجافا سنقوم بإنشاء أوبجكت جديد لملف الـ Drawable بطريقة الـ Bind.
    @BindDrawable(R.drawable.background) Drawable drawable;  
    ومن ثم وضعه كخلفية للـ
    textview داخل onCreate method بعد ButterKnife.bind طبعاً
    key.setBackground(drawable);  
    سنقوم أيضاً بتغيير الـ Text الموجودة داخل الـ textviews للتأكد بأن الربط يعمل بشكل صحيح
    key.setText("Website Bind"); value.setText("Pro3alam");  
    نقوم بتشغيل البرنامج والتأكد من النتيجة .. كل شي على ما يرام 👍

     
    هل إنتهينا ؟ ليس بعد .. مازال هناك ميزة قوية تقدمها هذه المكتبة .. وهي الإستغناء التام عن الـ inner-classes الناتجة عن الـ Listener عند عمل setOnClickListener . عن طريق ButterKnife يمكنك بكل بساطة كتابة [email protected] ومن ثم الـ id الخاص بالـ view . 
    على سبيل المثال .. سنقوم بإظهار Toast بسيط عند الضغط على أحد الـ textviews من خلال [email protected]
    @OnClick(R.id.key) void clicked1() { Toast.makeText(getApplicationContext(), "key", Toast.LENGTH_LONG).show(); } @OnClick(R.id.value) void clicked2() { Toast.makeText(getApplicationContext(), "value", Toast.LENGTH_LONG).show(); }  
    عند تشغيل البرنامج وعند الضغط على الـ textviews سنرى بأن كل شي يعمل بشكل صحيح

     
    بهذا نكون قد إنتهينا من موضوعنا بإستعراض واحدة من أهم وأقوى المكاتب وسنقوم بإستعراض المزيد من المكتبات في قادم المواضيع بإذن الله
    مستوى المقال: متوسط
  4. بسم الله الرحمن الرحيم
    كثيراً ما يقدم مطورو الأندرويد بعض المكتبات التي تساعد المبرمجين على إنشاء تطبيقاتهم الخاصة وتضيف بعض الميزات التي قد لا تجدها في بيئة البرمجة الأساسية. هذه المكتبات قد تقدم إضافة جمالية لتطبيقك أو ميزة قد تحتاجها.
    سنحاول في هذه السلسلة بإذن الله تسليط الضوء على بعض المكتبات الممتازة والتي بإمكانك إضافتها لتطبيقك الخاص بكل سهولة.
     
    مكتبة اليوم هي مكتبة ستمكنك من إضافة خاصية الـ QR Scanning لتطبيقك الخاص وتعتبر من أبسط المكتبات من حيث سهولة الدمج مع تطبيقك الخاص. 
    أغلب التطبيقات الحالية باتت تستخدم الـ QR Scanner في تطبيقاتها ومن أشهرها تطبيق الـ WhatssApp والـ SnapChat وغيرها
     
    ولكن كيف من الممكن دمج مثل هذه الميزة في تطبيقك ؟
     
    هناك الكثير من الـ Libraries المنتشرة لنظام الأندرويد والتي تسمح بإضافة هذه الخاصية إلى تطبيقك ولكن سنختار اليوم واحدة من أسهل هذه المكاتب وأقلها تعقيداً وهي مكتبة ZXingScanner.
    لنبدأ في فهم طريقة عمل هذه المكتبة.
     
    أولاً : يجب عليك إضافة الـ Dependency داخل ملف الـ Build.gradle داخل Block الـ Dependencies ومن ثم عمل Sync للتطبيق من الأعلى كيف تتم إضافة هذه المكتبة.
    compile 'me.dm7.barcodescanner:zxing:1.9' لتشغيل هذه المكتبة نحتاج إلى Activity خاصة نقوم بالانتقال إليها في كل مرة أراد المستخدم عمل مسح لـ QR Code . طبعاً بالإمكان عمل Activity ومن ثم تشغيل الـ QR Scanner كـ Fragment داخل هذه الـ Activity مثل برنامج الـ Whatsapp ولكن سنستخدم Activity كاملة في هذا الشرح كونها أقل تعقيداً.
    نلاحظ من الصورة أدناه شكل الـ QR Scanner داخل Fragment من تطبيق الـ Whatsapp. أما في حالة الـ Activity فسيغطي القارئ كل الشاشة.

     
    نقوم الآن بإنشاء Activity جديدة داخل التطبيق الذي تريد إضافة هذه الخاصية له ونختار Empty Activity وتختار الإسم الذي يناسبك

     
     
    بالنسبة للـ Layout الخاصة بالـ Activity الجديدة فـ ليس هناك أي داعي لتغيير أي شيء فيها وبإمكانك طبعاً تغيير الـ Parent Layout سواء Linear أو Relative فلا يوجد أي فرق
    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout 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"> </RelativeLayout>  
    ننتقل بعد ذلك إلى الجزء الأهم وهو كلاس الـ Java الخاص بهذه الـ Activity ونبدأ العمل عليه.
    في البداية يجب علينا عمل Implementation لـ Interface خاص بالمكتبة التي تم إستيرادها مسبقاً 
    public class <Your Class Name> extends AppCompatActivity implements ZXingScannerView.ResultHandler  
    سنقوم بعد ذلك بعمل Override لـ Method خاصة ستقوم بعمل Handle للنتيجة المقروءة من قبل الـ Scanner. هذه الـ Method موجودة داخل الـ Interface السابق.
    public void handleResult(Result result){}  
    ومن ثم سنحتاج إلى إضافة View خاص كمتغير من نوع ZXingScannerView لتشغيل الـ Scanner من خلاله داخل الـ Activity وإلى متغير من نوع String كي نستطيع التحقق من الـ Read Value أو النص المقروء من الـ QR Code. 
    private ZXingScannerView mScannerView ; private String QRresult= "";  
    نقوم بعد ذلك بإضافة بعد الأكواد داخل onCreate method لكي يتم تشغيل الـ Scanner مباشرة بعد الإنتقال للـ Activity. في البداية سنقوم بعمل Instantiate لمتغير الـ ZXingScannerView وإختياره كـ Content View. 
    mScannerView = new ZXingScannerView(this); setContentView(mScannerView);  
    بعد ذلك سيتم إختيار الـ Class الحالي بتولي مهمة الـ Handling للنتيجة المقروءة من قبل الـ Scanner. عن طريق handleResult method.
    mScannerView.setResultHandler(this);  
    وأخيراً سنقوم بتشغيل الـ Camera للقيام بمسح الـ QR Codes 
    mScannerView.startCamera();  
    لتصبح onCreate method في شكلها النهائي 
    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.<your layout name>); mScannerView = new ZXingScannerView(this); setContentView(mScannerView); mScannerView.setResultHandler(this); mScannerView.startCamera(); }  
    بعد قراءة الـ QR Code ستقوم handleResult method بتمرير Object من نوع result ومن أجل التأكد من أن المكتبة تعمل بشكل سليم سنقوم بتجريب QR Code لـ String معين. 
    لكن في البداية يجب أن نقوم بإغلاق الـ Camera كأول أمر داخل handleResult method.
    mScannerView.stopCamera();  
    ومن ثم إستخراج النص المقروء من الـ result object وحفظه داخل المتغير المنشأ مسبقاً وعرضه داخل Toast للتأكد ومن ثم إنهاء الـ Activity والعودة للـ Activity السابقة 
    QRresult = result.getText(); Toast.makeText(getApplicationContext(),QRresult,Toast.LENGTH_LONG).show(); finish();  
    للتأكد من عمل التطبيق بشكل سليم سنحتاج لـ جهاز حقيقي وذلك لأن الكاميرا لا تعمل داخل الـ Virtual Devices ومن ثم بإمكانك إستخدام أي موقع QR Codes Generator وتجربة التطبيق عليه ومن أشهر هذه المواقع موقع 
    QR Code Generator
     
    نقوم بإختيار Text وكتابة أي نص ومن ثم الضغط على Create QR code وتجربة التطبيق.

     
    وسنواصل الحديث بإذن الله تعالى في المواضيع اللاحقة عن بعض المكتبات الأخرى.
    مستوى المقال: مبتدئ
  5.  
    بسم الله الرحمن الرحيم
    كثير من التطبيقات تحتاج إلى تخزين في البيانات اندرويد, كمذكرة, أو أسماء وأرقام أو تواريخ وغيرها, في اندرويد يوجد عدد من الخيارات لتخزين البيانات
    كـ SharedPreferences لكنه كما هو مكتوب في Documentation .
    إذا فهو مخصص لبيانات بسيطة, كحفظ اللغة التي اختارها المستخدم في التطبيق, لكن إذا كان حجم البيانات كبير, كحفظ عدد من المذكرات, أو تسجيل طلاب -وهو المثال الذي سوف نستعرضه-, سنحتاج لتعامل مع قواعد البيانات -Database-,  في اندرويد نستخدم SQLite كقاعدة بيانات وفيها عدد من المميزات, فهي صغيرة الحجم, ولا تحتاج لأي إعدادت لاستخدامها, سريعة – فعلى سبيل المثال عند إدخال 25000 عنصر ستحتاج sqlite إلى 0.7, MySQL إلى 2.2,  PostgreSQL إلى 4.9 ثانية.
    لنبدأ إذا في المثال الخاص بنا وهو تطبيق فيه اسم الطالب والمادة المسجل فيها, وسنستعرض الأجزاء التي نحتاجها لكتابة هذا التطبيق.
    في Documentation قسم التعامل مع قواعد البيانات إلى ثلاثة أقسام كالتالي
    تعريف Schema and Contract بناء قاعدة البيانات باستخدام SQLiteOpenHelper تطبيق عمليات  CRUD على قاعدة البيانات سأبدأ بخطوة, وهي تعريف class student بعد ذلك سنمر على هذه الخطوات, سيكون شكل  class student كالتالي:
    private String name; private String course; private String id; public Student(String name, String course, String id) { this.name = name; this.course = course; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCourse() { return course; } public void setCourse(String course) { this.course = course; } public String getId() { return id; } public void setId(String id) { this.id = id; } } class بسيط جدا فيه ثلاث متغيرات, اسم الطالب, والمادة والمُعّرف.
    تعريف Schema and Contract
    بداية Schema نعني بها هيكل قاعدة البيانات, أو الشكل الخاص بها, ونعرفها داخل class نسميه contract بالطريقة التالية
    public class StudentContract { private StudentContract(){} public static class StudentEntry implements BaseColumns { public static final String TABLE_NAME = "student"; public static final String COLUMN_NAME = "name"; public static final String COLUMN_COURSE = "course"; } } هناك العديد من التساؤلات بخصوص التعريف بهذه الطريقة, لذا دعنا نمر عليها
    بداية تم تعريف private constructor , ببساطة لأنه  ليس الغرض من هذا class أنتنشئ منه object وبهذا الأسلوب من التعريف أمنعك من أن تنشئ object.
      inner class الغاية منه أن تعرف فيه اسم الجدول, وجميع الحقول الخاصه به, كم فعلنا مع Student Table, لماذا؟ أولا, يمكنك معرفة شكل الجدول بمجرد إلقاء نظرة على هذا class, ثانيا, تستخدم جميع المتغيرات التي عرفتها داخل هذا class في أي class أخر داخل التطبيق, وهذا يقلل من حدوث الخطأ, بالإضافة إلى أنه يسهل التعديل.
     
    في حال أردنا إضافة جدول أخر, سنقوم بعمل  inner class خاص به, لنفترض أننا نريد تعريف جدول لـ course سيكون شكله كالتالي
     
    public class StudentContract { private StudentContract(){} public static class StudentEntry implements BaseColumns { ... } public static class CourseEntry implements BaseColumns { public static final String TABLE_NAME = "course"; public static final String COLUMN_COURSE_NAME = "name"; public static final String COLUMN_COURSE_GRADE = "grade"; } }  
    BaseColumns عبارة عن interface يعطيك id لكل عملية إدخال تقوم بها, بالإضافة إلى عدد rows لديك في الجدول, شكل interface كالتالي public interface BaseColumns { /** * The unique ID for a row. * <P>Type: INTEGER (long)</P> */ public static final String _ID = "_id"; /** * The count of rows in a directory. * <P>Type: INTEGER</P> */ public static final String _COUNT = "_count"; } قد لا يكون واضح الغاية والغرض منه لكن ثق تماما أنه بسيط وسيتضح لاحقا.
    في حال أردنا تعريف أي متغير أو ثابت مشترك لجميع Tables فإنه يكون في class الـ contract, في حال أردنا تعريف أي متغير أو ثابت خاص بـ Table معين, فإنه يكون داخل inner class الخاص به.
     
    بناء قاعدة البيانات باستخدام SQLiteOpenHelper
    الان سوف ننتقل إلى بناء database الخاصة بنا, سنقوم في البداية بعمل extends لـ SQLiteOpenHelper وهو class سيساعدنا في بناء database, سيطلب منها عمل implement لثلاث دوال وهي
    onCreate(), والتي نستخدمها لإنشاء قاعدة البيانات الخاصة بنا. onUpgrade(), والتي نستخدمها في حال قمنا بإجراء أي تعديل على Schema الخاصة بالجدول. constructor يطابق super, سنمر عليه حالاً. ليكون شكل class كالتالي
    public class StudentHelper extends SQLiteOpenHelper { public StudentHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) {} @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} } الان سنقوم بعمل implementation لهم دالة دالة, ونحدث الكود لنبدأ بالـ constructor, نلاحظ أنه يطلب اسم database – وليس اسم table -, بالإضافة إلى رقم النسخة الخاصة بـ database – تبدأ غالبا من 1, ومع كل تحديث تجريه على Schema الخاصة بالجداول تقوم بزيادة رقم الإصدار وهكذا-, و context و factory, سنقوم بتعريف متغيرات static لاسم واصدار Database ونجعلfactory بقيمة null, والـ context نحصل عليه عن طريق constructor
    public class StudentHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "my_students.db"; public static final int DATABASE_VERSION = 1; public StudentHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) {} @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} } لان سننتقل لـ onCreate هذه الـmethod تستدعى لمرة واحدة في أول إنشاء لـ Database , وسننفذ فيها أمر واحد فقط وهو إنشاء database, وسنقوم بعمل  SQL query عادية جدا, ثم ننفذها داخل عن طريق  db
    public class StudentHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "my_students.db"; public static final int DATABASE_VERSION = 1; public StudentHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String SQL_CREATE_ENTRIES = "CREATE TABLE " + StudentContract.StudentEntry.TABLE_NAME + " (" + StudentContract.StudentEntry._ID + " INTEGER PRIMARY KEY," + StudentContract.StudentEntry.COLUMN_NAME + " TEXT," + StudentContract.StudentEntry.COLUMN_COURSE + " TEXT)"; db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} } أما onUpgrade فيتم استدعاؤها إذا تغير رقم VERSION في التطبيق, وببساطة فهي تقوم بتحديث شكل الجدول عن طريق حذف الجدول السابق وإعادة إنشاء الجدول
    public class StudentHelper extends SQLiteOpenHelper { public static final String DATABASE_NAME = "my_students.db"; public static final int DATABASE_VERSION = 1; public StudentHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String SQL_CREATE_ENTRIES = "CREATE TABLE " + StudentContract.StudentEntry.TABLE_NAME + " (" + StudentContract.StudentEntry._ID + " INTEGER PRIMARY KEY," + StudentContract.StudentEntry.COLUMN_NAME + " TEXT," + StudentContract.StudentEntry.COLUMN_COURSE + " TEXT)"; db.execSQL(SQL_CREATE_ENTRIES); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + StudentContract.StudentEntry.TABLE_NAME); onCreate(db); } } وبهذا  يكون اكتمل لدينا class StudentHelper.
     
    تطبيق عمليات  CRUD على قاعدة البيانات
    CRUD هو اختصار لعمليات الـDatabase الرئيسية, الإنشاء CREATE, القراءة READ, التحديث UPDATE, الحذف DELETE, بعد أن قمنا بتعريف وبناء شكل قاعدة البيانات الخاصة بنا سنبدأ هنا في التعامل معها داخل الـ MainActivity
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } نبدأ بـ CREATE والتي يقابلها INSERT في SQL, سنقوم بإضافة student لـ Database
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create database helper StudentHelper mDbHelper = new StudentHelper(this); // Gets the database in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(StudentContract.StudentEntry.COLUMN_NAME,"Ali"); values.put(StudentContract.StudentEntry.COLUMN_COURSE,"Android Programming"); long newRowId = db.insert(StudentContract.StudentEntry.TABLE_NAME,null,values); if (newRowId == -1) { // If the row ID is -1, then there was an error with insertion. Toast.makeText(this, "Error with saving Student", Toast.LENGTH_SHORT).show(); } else { // Otherwise, the insertion was successful and we can display a toast with the row ID. Toast.makeText(this, "Student saved with row id: " + newRowId, Toast.LENGTH_SHORT).show(); } } } في البداية قمنا بإنشاء object من نوع StudentHelper, بعد ذلك سننشئ object يستطيع القراءة والكتابة في database باستدعاء هذه الدالة getWritableDatabase, بعد ذلك نستخدم ContentValues لننشئ row  جديد, ContentValues بسيط جدا فهو يستخدم key/value, الـ Key دائما ما يكون أسم العمود, و value هو القيمة المراد تخزينها.
    ثم قمنا باستدعاء دالة insert التي تطلب منا اسم الجدول, و nullColumnHack سنجعل قيمته null, أخيرا ContentValues الذي قمنا بتجهيزه سلفا, ستعيد لنا دالة insert إما رقم row, أو -1 عند الخطا.
    وبهذا نكون أتممنا أول عملية في CRUD
    الان سنقوم بعملية READ ويقابلها SELECT في SQL, إذا سنقوم باسترجاع البيانات الموجودة داخل Database, سأزيل كود عملية Insert ليكون الكود أكثر وضوحا
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create database helper StudentHelper mDbHelper = new StudentHelper(this); // Gets the database in write mode SQLiteDatabase db = mDbHelper.getReadableDatabase(); // columns you want String[] projection = { StudentContract.StudentEntry._ID, StudentContract.StudentEntry.COLUMN_NAME, StudentContract.StudentEntry.COLUMN_COURSE }; // SELECT FROM student WHERE "name" = 'Ahmed' String selection = StudentContract.StudentEntry.COLUMN_NAME + " = ?"; String[] selectionArgs = { "Ali" }; // How you want the results sorted in the resulting Cursor String sortOrder = StudentContract.StudentEntry._ID + " DESC"; Cursor cursor = db.query( StudentContract.StudentEntry.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder ); if (cursor.moveToFirst()) { do { Toast.makeText(this, cursor.getString(cursor.getColumnIndex(StudentContract.StudentEntry._ID)), Toast.LENGTH_SHORT) .show(); } while (cursor.moveToNext()); } } } لدينا هنا عدة أمور لنوضحها
    قمنا بإنشاء object عن طريق استدعاء getWritableDatabase لنتمكن من قراءة البيانات. projection ببساطة هي Array of String, نقوم من خلالها بإختيار الحقول التي نريد استرجاعها, في مثالنا قمنا بإرجاع الإسم والمادة بالإضافة إلىid, لكن لو أردنا فقط  id فسنقوم بالتالي String[] projection = { StudentContract.StudentEntry._ID }; وبهذا سيرجع لنا فقط id. selection هي string نقوم فيه بتحديد الشرط الذي نريد أسترجاع الـrow بناء عليه, ففي المثال نقول إذا كان name يساوي  Ali رجع row  // SELECT FROM student WHERE "name" = 'Ahmed' String selection = StudentContract.StudentEntry.COLUMN_NAME + " = ?"; String[] selectionArgs = { "Ali" }; selectionArgs هي Array of String نضع فيه القيم التي نريد التحقق منها كما في الكود السابق فهي مكملة لـ selection, selection تقوم بوضع الشرط, و selectionArgs تحتوي القيم. sortOrder  هي جملة string الغاية منها ترتيب البيانات الراجعة من الجدول تنازلي -DESC-, أو تصاعدي -ASC-
    cursor هو عبارة عن مؤشر, يؤشر على البيانات الراجعة لنا, لتقريب الصورة, تخيل أن البيانات عبارة عن ملف نصي, والـ cursor عبارة عن ذاك المؤشر الذي يؤشر في بداية الملف.
    قمنا باستدعاء دالة query وأعطيناها جميع المعلومات التي تحتاج لتعطينا البيانات التي نحتاج, اسم الجدول, والشرط, والقيم الخاصة بالشرط, والترتيب الخاص بالبيانات.
    أخيرا قمنا بالتحقق إذا ما كان cursor يقف على بيانات أم لا, cursor.moveToFirst سترجع لنا قيمة false في حال لم يكن هناك أي بيانات, في حال كانت القيمة true, نقوم بأخذ row الأول وننتقل لثاني عن طريق cursor.moveToNext والتي في حال كنا في أخر row  ستعيد لنا قيمة false ونخرج من loop
    لقراءة البيانات من cursor استدعينا دالة getString والتي تحتاج int, فحصلنا عليها عن طريق دالة getColumnIndex والتي تعطيها أسم الحقل وترجع لك رقمه, لتوضيح أكثر لدينا ثلاث حقول _id, و name و course فيكون رقم index 0 للأول, 1 لثاني, 2 لثالث. وهكذا.
    ننتقل الان للأمر الثالث من CRUD, وهو update, ويقابله  UPDATE في SQL, الأمر في غاية البساطة, سنقوم بإضافة البيانات التي نرغب في تعديلها في ContentValues ثم نبحث عن row ونقوم بالتعديل عليه.
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create database helper StudentHelper mDbHelper = new StudentHelper(this); // Gets the database in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); // New value ContentValues values = new ContentValues(); values.put(StudentContract.StudentEntry.COLUMN_NAME, "Ahmed"); // Which row to update, based on the name String selection = StudentContract.StudentEntry.COLUMN_NAME + " LIKE ?"; String[] selectionArgs = { "Ali" }; int count = db.update( StudentContract.StudentEntry.TABLE_NAME, values, selection, selectionArgs); Toast.makeText(this, "Number of student updated is : " + count, Toast.LENGTH_SHORT).show(); } } لا يوجد العديد من الأشياء الجديدة فقط نلاحظ استخدام دالة update, والتي ترجع لنا قيمة rows التي تم تعديلها.
    أخيرا أمر DELETE, وقد يكون الأبسط, خصوصا بعد أن تعرفنا على بقية العمليات, ستحتاج إلى selection تعرف فيه query, ثم selectionArgs تعطيه القيم التي تحذف الـrow بناء عليها, وأخيرا تنفذ العملية بدالة delete التي تطلب من أسم الجدول و selection, و selectionArgs
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create database helper StudentHelper mDbHelper = new StudentHelper(this); // Gets the database in write mode SQLiteDatabase db = mDbHelper.getWritableDatabase(); // Define 'where' part of query. String selection = StudentContract.StudentEntry.COLUMN_NAME + " LIKE ?"; // Specify arguments in placeholder order. String[] selectionArgs = { "Ali" }; // Issue SQL statement. db.delete(StudentContract.StudentEntry.TABLE_NAME, selection, selectionArgs); } } نقطة أخيرة يجب التنبيه عليها, دالتي getReadableDatabase و  getWritableDatabase مكلفة جدا في الاستدعاء لذا مادمت تعتقد أنك ستحتاج إلى التعامل مع الـ Database , لا تستدعي دالة close, وجميل جدا أن تضع دالة close داخل onDestroy.
    @Override protected void onDestroy() { mDbHelper.close(); super.onDestroy(); } إلى هنا أكون قد وصلت إلى نهاية هذه المقالة, أتمنى أن أكون وفقت في تبسيط المعلومة, في حال كان هناك أي ملاحظات أو تساؤل يمكنك التواصل معي على حسابي في تويتر
    والسلام عليكم ورحمة الله وبركاته.
     
    المصادر 
    Android Doc stackOverflow Sqlite speedb udacity example on github wiki CRUD
    مستوى المقال: محترف
  6. أبو معتز مطور تطبيقات أندرويد وهو الآن في  منتصف بناء تطبيق أندرويد، وصل للميزة ص، برمجها واختبرها وتأكد من أنها تؤدي الغرض المراد منها مبدئياً، ولا مشاكل ظاهرة حتى اللحظة، وأثناء تلك اللحظة -وهو يرتشف الشاي الأحمر مع مسحوق أوراق النعناع الخضراء المجففة-  تبادر إلى ذهنه السؤال التالي: لماذا أداء ومظهر هذه الميزة لا يشبه مثيلاتها في تطبيقات المبرمجين الآخرين المُحملة على جوالي؟
    ملحوظة جانبية: هذه المقالة تتكون من جزئين: نظري وعملي.
    الجزء النظري:
    إذا مررت بالحالة السابقة فهذه المقالة -إن شاء الله- دليلك لاختيار المكتبة (Library) أو المكتبات المناسبة لمشروعك. لذا قبل أن تعيد اختراع العجلة أو تبدأ من الصفر أو تُهدر وقتاً وجهداً، دعنا نرجع إلى الميزة ص وقبل الشروع في برمجتها، اسأل نفسك الأسئلة التالية:
    هل هذه الميزة موجودة في تطبيقات أخرى؟ هل تقوم بنسخ ولصق كود هذه الميزة بشكل متكرر في تطبيقاتك؟ هل تظن باحتمالية برمجة هذه الميزة من قبل مبرمج آخر؟ إذا كانت الإجابة نعم لأحد أو كل هذه الأسئلة فهذا مؤشر على أن هناك مكتبة توفر هذه الميزة، والسؤال الذي ينبغي أن يُطرح: كيف أبحث عنها؟ وما هي مواصفات أفضل مكتبة مناسبة لتطبيقي؟
    أما طريقة البحث فيمكن من خلال المراجع التالية لاستكشاف المكتبات المناسبة:
    البحث في موقع: stackoverflow صفحة على GitHub بعنوان: Must Have Libraries موقع: android-arsenal.com لديك مصدر آخر؟ أرجو ذكره في التعليقات 😉 لكن كيف أتأكد أن المكتبة التي اخترتها هي المكتبة المناسبة لمشروعي؟ هناك مواصفات ونصائح للمكتبة المناسبة يمكن إجمالها في النقاط التالية:
    الصيانة Maintenance أندرويد نظام يتطور باستمرار وكذا ينبغي لمكتباتها، فبعض المكتبات تتوقف عن التطور عند إصدار نسخة جديدة من أندرويد، بالتالي تتوقف صيانتها وتُهجر [deprecated]، فإذا كنت تستخدم واحدة من هذه المكتبات المهجورة فسيتعطل تطوير تطبيقك بلا شك. من الذي يقوم بصيانة المكتبة؟ هل هو شخص واحد؟ أو مجموعة أفراد؟ أو شركة مثل: Google أو Square؟ المعنى أنه كلما كان مجتمع المكتبة أكبر كلما كانت صيانة المكتبة مستمرة.
    هل صيانة المكتبة مستمرة؟ ومتى كان آخر تحديث لها؟ بإمكانك معرفة ذلك من صفحة المكتبة على GitHub والبحث عن آخر الإصدارات [releases] وعن آخر إيداع [Latest commit]، وأيضاً من التبويب insights ثم Graphs.
    الحذر من سوء التوثيق [documentation] فأنت لا تريد المزيد من صداع الرأس. المكتبة الجيدة ستحتوي على دليل للبدء السريع، أمثلة، لقطات شاشة ،تطبيق فعلي في متجر التطبيقات يستعرض ميزات المكتبة وغير ذلك. أما سوء التوثيق فهو مؤشر على سوء الصيانة أو سوء المكتبة ككل.
    توثيق المكتبة ستجده في أحد أو كل ما يلي:
    صفحة المكتبة على GitHup (ملف README.md)
    صفحة المكتبة على GitHup تحت التبويب: Wiki
    صفحة أو موقع خاص بالمكتبة.
    القضايا المعروفة Known issues
    يمكن الاطلاع عليها من خلال صفحة المكتبة على GitHub من خلال التبويب Issues.
    تأكد أنه لا توجد مشاكل مفتوحة بإمكانها التأثير على تطبيقك.
    الميزات Features
    ابحث عن المكتبة في Google متبوعة بـ vs، مثل: picasso library vs.
    قارن المكتبات التي تؤدي نفس الغرض ثم اختر المكتبة الأنسب لمشروعك والتي لا تحتوي على ميزات إضافية لا تحتاجها، لأن ميزاتٍ أكثر تعني حجم تطبيق (apk) أكبر.
    الرخصة License
    أغلب الرخص تطالب بإدراج نسخة من حقوق النشر [copyright] إلى تطبيقك.
    بعضها يزيد فيطالب بمتطلبات أخرى، مثلاً Google Maps يطالب بإضافة: "Map data ©2015 Google" كعلامة مائية كما في الصورة التالية أسفل اليمين:

    وبعض رخص المكتبات يطالب بجعل المشروع مفتوح المصدر [Open Source].
    عدم التزامك بالتراخيص قد يؤدي إلى رفض تطبيقك من متجر Google Play بسبب انتهاك حقوق النشر، أو قد يؤدي إلى مشاكل قانونية.
    زبدة الموضوع اقرأ رخصة المكتبة التي تنوي استخدامها جيداً.
    مرجع مساعد لمعرفة أنواع الرخص: https://choosealicense.com
    الحذر من ازدياد حجم التطبيق متأثراً بكثرة المكتبات المستخدمة، حتى لا تواجه مشكلة: 64K Method Limit
    ماذا إذا لم تجد المكتبة المناسبة؟ وكنت تحتاج هذه الميزة بشكل متكرر في تطبيقاتك؟ الجواب هو: النسخ واللصق وأفضل من ذلك أن تُنشأ مكتبتك الخاصة وهذا: دليل إنشاء مكتبة أندرويد.
    نقطة أخيرة في هذا الجزء، إذا كنت في بدايات مشوارك لتعلم أساسيات البرمجة لنظام أندرويد وتطوير تطبيقات بغرض التعلم فلا يُنصح باستخدام مكتبات خارجية حتى تزداد فهماً للنظام وجزئياته ومكتباته الأساسية.
     
    الجزء العملي:
    في هذا الجزء سنعتمد على أحد تطبيقات المهام الموجودة هنا والمسمى: todo‑mvp، ولمتابعة التطبيق يلزمك التالي:
    برنامج Android Studio برنامج إدارة الإصدارات Git خطوات تنزيل مشروع المهام todo‑mvp:
    انسخ رابط المشروع: 
    https://github.com/googlesamples/android-architecture.git  
    افتح برنامج Git Bash.
    غير إلى المجلد الذي تريد استنساخ المشروع إليه، على سبيل المثال:
    cd D:\AndroidStudioProjects  
    استنسخ المشروع بالأمر git clone وألصق الرابط السابق:
    git clone https://github.com/googlesamples/android-architecture.git  
    انتقل للمشروع:
    cd android-architecture  
    ثم انتقل للتفريع todo-mvp كالتالي:
    git checkout todo-mvp  
    افتح أندرويد استديو ثم:
    Open an existing Android Studio project >> [path to android-architecture folder] >> todoapp  
    شغل التطبيق. إذا سارت الأمور على ما يرام ستظهر لك هذه الواجهة:

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

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

    سأتجه إلى موقع stackoverflow ، وأبحث عن android charts library. من ضمن النتائج الظاهرة لدي والمتعلقة بموضوعنا والأعلى تقييماً: Charts for Android Android charting libraries وكلاهما يرشحان المكتبة MPAndroidChart في المقام الأول. أيضاً في مرجعنا Must Have Libraries في قسم Drawing تظهر المكتبة في أعلى القائمة، وكذلك في موقع android-arsenal.com  تعتبر الأعلى تقييماً في قسم Graphics. لنطبق معايير التي ذكرنها على هذه المكتبة:
    الصيانة: مجموع المساهمين 57 منهم مؤسس المكتبة PhilJay و4 مساهمين رئيسيين وجه لهم المؤسس شكر خاص أسفل صفحة المكتبة. ما يهمنا أن مجتمع المكتبة كبير وهو مؤشر على استمرارية الصيانة. آخر إصدار للمكتبة بتاريخ Mar 23, 2017 وآخر تحديث للمكتبة كان في Jul 20, 2017 وهو تاريخ قريب، وأنشئت المكتبة بتاريخ Apr 25, 2014. المكتبة موثقة بشكل جيد في التبويب Wiki وتحتوي على أمثلة ونماذج وتطبيق لتجربتها. القضايا المعروفة: نحن سنستخدم [Pie Chart] أو المخطط الدائري ولا توجد أخطاء [bugs] قد تؤثر على عملنا حتى كتابة هذه المقالة. الميزات: ابحث في قوقل عن: MPAndroidChart vs، من ضمن النتائج هذه المقارنة والتي تعطي الأفضلية لهذه المكتبة. الرخصة: Apache License 2.0، ملخصها هنا. خطوات استخدام المكتبة في المشروع:
    إضافة الاعتماد في ملف build.gradle: على مستوى المشروع [project level]:  allprojects { repositories { jcenter() maven { url "https://jitpack.io" } //إضافة هذا السطر } }  
    وعلى مستوى التطبيق [app level]: 
    dependencies { ... compile 'com.github.PhilJay:MPAndroidChart:v3.0.2' }  
    ثم زامن ملفات gradle.
    انتقل إلى ملف statistics_frag.xml وأضف View من نوع المخطط الدائري: 
    <com.github.mikephil.charting.charts.PieChart android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="match_parent" /> وعدل طول TextView إلى: 
    android:layout_height="wrap_content" سنحصل على النتيجة التالي: 

    في ملف StatisticsFragment.java نضيف متغير من النوع PieChart: 
    private TextView mStatisticsTV; private PieChart mPieChart;  
    ثم في الإجراء [method] المسمى onCreateView نضيف: 
    mStatisticsTV = (TextView) root.findViewById(R.id.statistics); mPieChart = (PieChart) root.findViewById(R.id.chart); // هذا السطر  
    الآن كل ما نحتاجه هو تعديل الإجراء showStatistics في ملف StatisticsFragment.java: 
    ... mStatisticsTV.setText(displayString); // نضيف التالي بعد هذا السطر // لحساب النسبة المئوية للمهام النشطة والمكتملة float total = numberOfIncompleteTasks + numberOfCompletedTasks; float percentOfIncompleteTasks = numberOfIncompleteTasks / total * 100; float percentOfCompletedTasks = numberOfCompletedTasks / total * 100; // مصفوفة المدخلات وكلما أضفنا مُدخل جديد سيزيد عدد الحصص في المخطط الدائري List<PieEntry> entries = new ArrayList<>(); // إذا كان لدينا مهمة أو مهام غير مكتملة if (numberOfIncompleteTasks != 0) { // نضيف حصة إلى المخطط الدائري بعنوان نشط entries.add(new PieEntry(percentOfIncompleteTasks, "نشط")); } else { // وإلا سنضيف كلمة "أحسنت" في منتصف المخطط الدائري لأننا أنجزنا جميع المهام mPieChart.setCenterText("أحسنت"); } // إذا كان لدينا مهمة أو مهام مكتملة if (numberOfCompletedTasks != 0) { // نضيف حصة إلى المخطط الدائري باسم مكتملة entries.add(new PieEntry(percentOfCompletedTasks, "مكتملة")); } // الكائن التالي يحتوي المدخلات السابقة والتي يجب أن تكون من نفس النوع // ويسمح بإضفاء اللمسات التصميمية PieDataSet dataSet = new PieDataSet(entries, ""); dataSet.setColors(ColorTemplate.MATERIAL_COLORS); dataSet.setValueFormatter(new PercentFormatter()); dataSet.setValueTextSize(14); // لإضافة الوصف الذي يظهر أسفل اليمين Description description = new Description(); description.setText("إحصائية المهام"); mPieChart.setDescription(description); // الكائن التالي يحتوي كائن مجموعة البيانات السابق PieData data = new PieData(dataSet); mPieChart.setData(data); // للتحديث mPieChart.invalidate();  
    سنحصل على النتيجة التالي:

    هذا والله أعلم وصلى الله وسلم على نبينا محمد.
    المراجع:
    أحد دروس دورة Advanced Android App Development (يستلزم التسجيل) Android Architecture Blueprints مكتبة MPAndroidChart
    مستوى المقال: متوسط
  7. بسم الله الرحمن الرحيم
    مؤخرا, في google IO 17 تم الإعلان عن New Architecture Component وفي هذه المقالة سنتحدث عن اثنين منهم وهما ViewModel & LiveData.
    بداية لنفرض أن لدينا تطبيق بهذا الشكل

    عند الضغط على button Add one سيتم إضافة 1 للقيمة الموجودة في textView, وسيكون كود layout  كالتالي
    <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout 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" tools:context="me.a3zcs.mvp.architecurecomponent.MainActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:id="@+id/number" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_marginTop="0dp" app:layout_constraintTop_toBottomOf="@+id/number" /> </android.support.constraint.ConstraintLayout> أما Activity كالتالي
    public class MainActivity extends AppCompatActivity { TextView number; Button addOne; int i = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); number = findViewById(R.id.number); addOne = findViewById(R.id.button); setNumber(i); addOne.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { setNumber(i); } }); } private void setNumber(int value){ number.setText(String.valueOf(value)); } } الكود بسيط جدا, ويعمل بطريقة صحيحة, ولكن حينما تقوم بتغيير وضع الشاشة إلى landscape بدل portrait ستعود قيمة textView إلى 0, لأن Activity قد حصل لها destroy ثم create من جديد, سابقا يتم حل مثل هذه المشاكل التي يكون لها علاقة ب configChanges عن طريق استخدام onSaveInstanceState و  onRestoreInstanceState, جميل جدا, سنتحدث عن أحد الحلول الجديدة لهذه المشكلة وهو ViewModel.
    ViewModel:
    هو class تم تصمميه لتعامل مع data الخاصة ب view, والميزة المضافة أنه يخزن البيانات في حالة configChanges, جميل جدا, لنبدأ بعمل refactor للكود السابق بإنشاء Model يقوم بعمل extends ل ViewModel
    public class CounterViewModel extends ViewModel { private int count; public void setCount(int count) { this.count = count; } public int getCount() { return count; } } جميل جدا الان سنقوم باستخدام هذا class في MainActivity كـ Composition Object ثم نقوم بإنشائه في onCreate, ليصبح شكل Activity كالتالي:
    public class MainActivity extends AppCompatActivity { TextView number; Button addOne; CounterViewModel counterViewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); counterViewModel = ViewModelProviders.of(this).get(CounterViewModel.class); number = findViewById(R.id.number); addOne = findViewById(R.id.button); setNumber(counterViewModel.getCount()); addOne.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { counterViewModel.setCount(counterViewModel.getCount()+1); setNumber(counterViewModel.getCount()); } }); } private void setNumber(int value){ number.setText(String.valueOf(value)); } } نلاحظ أننا نستخدم في الإنشاء ViewModelProviders, وهي ستقوم بحفظ وتخزين ViewModel ل activity  المعطى, بمعنى, في حال حدثت حالة configChanges -كتغير orientation- ل activity سيقوم  class ViewModelProviders بتخزين بيانات ViewModel ولذلك نقوم بإعطائه هذا المعلومات أثناء التعريف
    ViewModelProviders.of(<UI controller>).get(<MyViewModel>.class) الان في حال قمنا بعمل rotation لن تتأثر البيانات حيث تم الإحتفاظ بها عن طريق ViewModelProviders
    جميل جدا, لنضيف ال component الأخرى للكود الخاص بنا.
    LiveData:
    LiveData تتيح ل lifeCycleOwner -سواء كان Activity or Fragment- أنه يستقبل الداتا من ViewModel عن طريق ال مراقبة -observing- أي تعديل يطرأ على ViewModel class, جميل جدا, إذا بدلا من أن نقوم بتحديث UI في كل مرة بإستخدام setters و getters, سنقوم بإسلوب جديد سنستعرضه في المثال التالي
    أولا لنقوم بعمل refactor ل viewModel الخاص بناء ليصبح كالتالي
    public class CounterViewModel extends ViewModel { private MutableLiveData<Integer> count = new MutableLiveData<>(); public CounterViewModel(){ count.setValue(0); } public void setCount(int count) { this.count.setValue(count); } public MutableLiveData<Integer> getCount() { return count; } } قمنا بتغيير نوع البيانات الذي نتعامل معه إلى MutableLiveData<Object> وتعديل set & get, جميل جدا لننتقل إلى Main
    //public class MainActivity extends AppCompatActivity public class MainActivity extends LifecycleActivity { TextView number; Button addOne; CounterViewModel counterViewModel; private final Observer<Integer> countObserver = new Observer<Integer>() { @Override public void onChanged(@Nullable Integer integer) { setNumber(integer); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); counterViewModel = ViewModelProviders.of(this).get(CounterViewModel.class); counterViewModel.getCount().observe(this,countObserver); number = findViewById(R.id.number); addOne = findViewById(R.id.button); //setNumber(counterViewModel.getCount()); addOne.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { counterViewModel.setCount(counterViewModel.getCount().getValue()+1); //setNumber(counterViewModel.getCount()); } }); } private void setNumber(int value){ number.setText(String.valueOf(value)); } } قمت بعمل comment قبل كل سطر تم حذفه ليسهل عليك مشاهدة التعديل, بداية قمنا بعمل Observer<Object> لديه دالة واحدة فقط وهي onChanged, يتم استدعاؤها إذا حصل أي تعديل للبيانات, بعد ذلك قمنا بعمل Observe عن طريق هذا السطر في onCreate
    counterViewModel.getCount().observe(<LifeCycleOwner>,<Observer>) كما تلاحظ لدينا Observer سيستدعي دالة onChanged في كل مرة يتم تعديل البيانات,بشرط أن يكون State ل lifeCycleOwner في STARTED أو RESUMED, ويكون LifecycleOwner في حالة STARTED في حالتين الأولى بعد استدعاء دالة onStart, الثانية, قبل استدعاء دالة onPause, ويكون LifecycleOwner في حالة RESUMED بعد استدعاء دالة onResume.
    ولذلك عندما قمنا بعمل Observe أعطينا الدالة Observer, بالإضافة إلى LifecycleOwner الذي هو مرتبط به.
    ينبغي الإشارة إلى نقطة في غاية الأهمية, في حال كان التطبيق يعمل لكنه في  Background وتعرض النظام لما نسميه Low Memory, سيقوم النظام بإعادة استخدام resources بالتخلص -kill-  من التطبيقات الموجودة في Background, وفي حال كنت تعتمد فقط على ViewModel ببساطة ستفقد بياناتك, على عكس onSaveInstanceState التي كانت تحل هذه المشكلة.
    إلى هنا أتمنى أن أكون وفقت في تبسيط المفهوم, ولاشك أنك ستحتاج لمزيد من القراءة لتعامل مع بيانات أعقد من المذكورة في المثال.
    والسلام عليكم ورحمة الله وبركاته.
    المصادر
    Android Architecture Components by Example
    Android Architecture Components
    ViewModels : A Simple Example
    Android ViewModel Architecture Component Considered Harmful
    مستوى المقال: محترف
  8. السلام عليكم و رحمة الله و بركاته.

    في هذه المقالة سنتعلم كيفية قراءة و استخراج بيانات الـJSON  في الـAndroid  Studio.
    ملاحظة: يفضل أن تكون قد قرأت مقالتي السابقة عن كيفية تحميل بيانات الـJSON في Android Studio من هنا.  
    قبل أن نبدأ باستخراج البيانات أود أن نلقي نظرة على ماهية الـJSON المتعلق ب Flicker Api. باستخدام موقع jsonlint حيث يمكننا أن نرى التكوين الداخلي للـJSON بالاضافة الى التأكد من أن الملف هوة عبارة عن JSON.
    رابط ملف الـJSON هو: https://api.flickr.com/services/feeds/photos_public.gne?format=json&nojsoncallback=1

    كما نلاحظ أعلاه، الأشياء المحاطة بـ{} هي عبارة عن object و الأشياء المحاطة بـ[] هي عبارة عن array، لذلك نستنتج بأن الـJSON هو عبارة عن objects و arrays.
    في هذا الدرس سوف نقوم باستخراج الـtitle و الـtags  من الـarray التي تدعى items.

    و الآن ننتقل الى التطبيق في الـ Android Studio...
     
    في البداية يجب علينا عمل التالي:
    ننشئ كلاس ونسميه photo و الذي يحتوي على متغيرات: title, tags و الذي بدوره سوف يخزن البيانات المطلوبة من الـJSON. إضافة getters and setters  لهذه المتغيرات. package org.example.rami.flickerbrowser; import java.io.Serializable; /** * Created by Rami on 5/2/2017. */ class Photo implements Serializable{ private static final long serialVersionUID = 1L; private String mTitle; private String mTags; public Photo(String title, String tags) { mTitle = title; mTags = tags; } String getTitle() { return mTitle; } String getTags() { return mTags; } @Override public String toString() { return "Photo{" + "mTitle='" + mTitle + '\'' + ", mTags='" + mTags + '\'' + '}'; } }  
    ثم ننشئ كلاس آخر و نسميه على سبيل المثال GetFlickerJsonData و الذي بدوره سوف يحتوي على الميثود onDownloadComplete التي تقوم بقراءة و استخراج المعلومات المطلوبة من الـJSON  بالاضافة الى تخزينها في الاوبجكت من نوع Photo.
     
    ملاحظة: الميثود onDownloadComplete يتم استدعاؤها من الكلاس GetRawData الموجود في الدرس السابق الذي قام بتحميل ملف الـJSON و تمرير بياناته على شكل String للميثود. package org.example.rami.flickerbrowser; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; /** * Created by Rami on 5/3/2017. */ class GetFlickerJsonData implements GetRawData.OnDownloadComplete { private static final String TAG = "GetFlickerJsonData"; private List<Photo> mPhotoList = null; public GetFlickerJsonData() { Log.d(TAG, "GetFlickerJsonData called"); //here you do somthing additional } @Override public void onDownloadComplete(String data) { mPhotoList = new ArrayList<>(); try { JSONObject jsonData = new JSONObject(data); JSONArray itemsArray = jsonData.getJSONArray("items"); for(int i = 0; i < itemsArray.length(); i++){ JSONObject jsonPhoto = itemsArray.getJSONObject(i); String title = jsonPhoto.getString("title"); String tags = jsonPhoto.getString("tags"); Photo photoObject = new Photo(title, tags); mPhotoList.add(photoObject); Log.d(TAG, "onDownloadComplete: " + photoObject.toString()); } }catch (JSONException jsone){ jsone.printStackTrace(); Log.e(TAG, "onDownloadComplete: Error processing json data " + jsone.getMessage()); } } Log.d(TAG, "onDownloadComplete ends"); } لقد قمنا بعمل التالي: 
    احاطة الكود بالكامل بtry و catch لمعالجة ظهور أي خطأ في ملف الـJSON. اضافة متغير  mPhotoList من نوع List و الذي سوف يتم تخزين فيه الـphotos و التي تحتوي على بيانات الـJSON المستخرجة. اضافة Constructor  لانشاء object من هذا الكلاس بحيث يتم اضافة أي شيء فيه على حسب تصميمك للبرنامج الكامل و طريقة عمله. اضافة الـmethod و هي onDownloadComplete  و التي بدورها سوف تستخرج المعلومات المطلوبة من الـJSON المحمل كما هو موضح أعلاه. و هذه الـmethod  تحتوي على:
    المتغير jsonData من نوع ـJSONObject و الذي يحتوي على كامل ملف الـJSON. المتغير itemsArray من نوع JSONArray  والذي يحتوي على الـarray  و التي تدعى items.  
    و الآن نقوم باضافة loop و التي بدورها تقوم بالتالي:
    استخراج الـtitle و تخزينه بالمتغير title من نوع String. استخراج الـtags و تخزينه بالمتغير tags من نوع String. في النهاية نقوم بانشاء اوبجكت من نوع Photo و اضافته الى الـmPhotoList و التي تحتوي على جميع الـobjects من نوع Photo.  
    لمزيد من المعلومات عن org.json package و الكلاسات الموجودة فيه: https://developer.android.com/reference/org/json/package-summary.html

    و هنا وصلنا الى نهاية هذا الدرس.😄
    أتمنى أني قد وفقت بايصال المعلومة.
    مستوى المقال: مبتدئ
  9. بسم الله الرحمن الرحيم 
    أطلقت google مؤخراً Android IoT وفي هذه المقالة البسيطة سنقوم بثبيت النسخة على ٌRaspberry Pi 3 وعمل مشروع بسيط.
    بداية عليك تحميل النسخة الخاصة بالـ Raspberry Pi 3 من هنا وبعد ذلك تثبيته على SD card حسب النظام الذي تستخدمه, بالنسبة لمستخدمي Ubuntu كالتالي 
    sudo dd if=Downloads/androidthings_rpi3_devpreview_4.img of=/dev/yourFlash  بعد ذلك ادخل SD card في Raspberry Pi وأوصل الكهرباء, وسلك Ethernet يفترض أن ترى مثل هذه الشاشة 
     
     
    لاحظ وجود IP في أسفل الصفحة فهو الذي سنستخدمه في التواصل مع Raspberry Pi وإيصاله بالشبكة.
    الان نفتح  Android studio وعن طريق terminal الموجود فيه نقوم بكتابة الأمر التالي 
    adb connect <ip-address> ip address هو الرقم المكتوب لديك في أسفل الشاشة بعد Ethernet IP, لو تم الاتصال بشكل صحيح سيظهر هذا السطر 
    connected to <ip-address>:5555 , بهذه الطريقة نكون متصلين بالـ Raspberry Pi لكن, نريد أن نجعل الأتصال عن طريق wifi 
     فنقوم بكتابة الأمر التالي 
    adb shell am startservice \ -n com.google.wifisetup/.WifiSetupService \ -a WifiSetupService.Connect \ -e ssid <Network_SSID> \ -e passphrase <Network_Passcode> ssid أسم الشبكة المراد الاتصال بها 
    passcode الرقم السري الخاص بالشبكة 
    انتهينا من فقرة التثبيت إن صح التعبير وقد يتخللها الكثير من الإشكالات وdebug وأرحب بأي استفسار لتجاوز هذه المهمة, الان لننتقل إلى برمجة الـ board.
     أولا علينا أن نقوم بتوصيل كما في الصورة 
     
     

     
    وهنا شرح لـ pins الخاصة بـ Rpi
    الكود كالتالي 
    public class MainActivity extends Activity { private Gpio mLedGpio; private boolean mLedState = false; Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.button); PeripheralManagerService service = new PeripheralManagerService(); //system service responsible for managing peripheral connections try { String pinName = "BCM4";//pin number mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);//Output pin ,start with 0v } catch (IOException e) { e.printStackTrace(); } mButton.setOnClickListener(v -> { try { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH);//5v mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW);//0v mLedState = true; } } catch (IOException e) { e.printStackTrace(); } }); } }  أما الـ UI فيحتوي button واحد id الخاص فيه هو button 

    بداية لدينا 
    PeripheralManagerService service = new PeripheralManagerService();  PeripheralManagerService هي Service من من نظام الأندرويد تسهل لك التعامل مع pin الخاصة بـ Rpi
    بعد ذلك قمنا بختيار pin عن طريق اسمه 
    String pinName = "BCM4"; mLedGpio = service.openGpio(pinName); mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW); وربطه بمتغير من نوع Gpio, ثم قمنا بضبط إعداده بأن يكون output ويبدأ بـ 0 فولت ويوجد العديد من Constant التي تساعد في هذه الاعدادت
    mButton.setOnClickListener(v -> { if (mLedState) { mLedGpio.setDirection(Gpio.ACTIVE_HIGH); mLedState = false; }else { mLedGpio.setDirection(Gpio.ACTIVE_LOW); mLedState = true; } هنا قلنا في حال تم الضغط على button تأكد من حالته, في حال كان  on اجعله off والعكس, ونلاحظ أننا قمنا بأستخدام المزيد من Constant  التي تحدثنا عنها في السطر السابق.
    ونلاحظ أيضا أننا كتبنا الدالة الخاصة بنا بطريقة lambda.
     
    لمعلومات أكثر عن أساسيات hardware
    واللاستزادة :
    https://developer.android.com/things/sdk/index.html
    مستوى المقال: مبتدئ
  10. السلام عليكم و رحمة الله و بركاته.
    في هذه المقالة سنتعلم كيفية تحميل الـJSON في الـAndroid  Studio.
    في البداية، ما هو الـJSON ؟
    JSON هو عبارة عن كائن JavaScript حيث يتم تمثيل البيانات و المصفوفات من خلالها. عندما تتخاطب المنصات أو البرمجيات أو الأنظمة مع بعضها البعض
    فهي تتبادل البيانات بهذا الشكل. تخيل لو كنت تود ارسال قائمة ببيانات الموظفين المحالين للتقاعد إلى جهة أخرى فحينها سترسلها مرتبة ومصاغة بهذا الشكل.

    و الآن ننتقل الى التطبيق في الـ Android Studio...

    في البداية نقوم بفتح Android Studio ثم نقوم بالضغط على Start a new Android Studio project و نبقي الخيارات الافتراضية على حالها و نقوم بتسميته و حفظه.

    و الآن حتى يتم تحميل الــJSON  يجب علينا:
    انشاء thread منفصل و لحسن الحظ يتوفر على الـ Android Studio كلاس يسمى بالـ AsyncTask يقوم بأغلب المهام. نقوم بانشاء class  و نسميه على سبيل المثال GetRawData و الذي بدوره يقوم بـ AsyncTask class" extends"،
    ثم نضيف الmethods وهي onPostExecute ،doInBackground كما هو موضح ادناه: /** * Created by Rami on 4/20/2017. */ public class GetRawData extends AsyncTask<String, Void, String> { private static final String TAG = "GetRawData"; @Override protected void onPostExecute(String s) { super.onPostExecute(s); } @Override protected String doInBackground(String... params) { return null; } } ملاحظة: الكلاس AsyncTask يحتوي على عدة functions مهمة يجب عمل implements لهما و منها: onPreExcute, doInBackground, onPostExcute.  
    onPreExcute: يتم استدعاؤها في الـUI thread قبل استدعاء الميثود doInBackground بحيث يتم من خلالها عمل مهمة معينة مثل اظهار الـprogress bar في الـinterface. doInBackground: يتم اسدعاؤها في thread منفصل (background thread) بحيث يتم من خلالها تحميل جميع بيانات الـJSON و بالنهاية يتم عمل pass للبيانات المحملة للميثود onPostExcute. onPostExcute: يتم اسدعاؤها تلقائيا في UI thread بعد الانتهاء من تحميل البيانات بحيث يتم من خلالها اشعار الـcaller بانتهاء التحميل.
      و لكن للتبسيط سوف نستخدم في هذا الدرس الميثود doInBackground و الميثود onPostExcute.
     
    الآن نقوم بانشاء interface في داخل GetRawData كلاس ونسميها على سبيل المثال OnDownloadComplete و التي بدورها سوف تحتوي على ميثود الـonDownloadComplete و التي  تقوم بإشعار الكلاس المستعدي (caller) بانتهاء تحميل البيانات.
    /** * Created by Rami on 4/20/2017. */ public class GetRawData extends AsyncTask<String, Void, String> { private static final String TAG = "GetRawData"; private final OnDownloadComplete mCallback; interface OnDownloadComplete{ void onDownloadComplete(String data); } public GetRawData(OnDownloadComplete callback) { this.mCallback = callback; } private static final String TAG = "DownloadData"; @Override protected void onPostExecute(String s) { super.onPostExecute(s); } @Override protected String doInBackground(String... params) { return null; } } كما هو موضح أعلاه الـ GetRawData تتلقى parameter و الذي هو عبارة عن اوبجكت يسمى بالـcallback و الذي يعتبر كـreference للكلاس المستدعي(caller) حيث أن الكلاس المستعدي يعمل على implements الـinterface والمسمى OnDownloadComplete.
     
    ملاحظة: بهذه الطريقة يتم فصل كلاس GetRawData عن بقية الكلاسات و الذي بدوره يعزز الـreusability و يتم اشعار الكلاس المستعدي عند انتهاء تحميل البيانات دون ايقاف أي عملية أخرى.  
    الآن نقوم بإضافة الكود في داخل الـميثود doInBackground و التي بدورها سوف تكون المسؤولة عن تحميل بيانات الـJSON.
    @Override protected String doInBackground(String... params) { HttpURLConnection connection = null; BufferedReader reader = null; if(params == null){ return null; } try{ URL url = new URL(params[0]); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.connect(); int response = connection.getResponseCode(); Log.d(TAG, "doInBackground: The response code is: " + response); StringBuilder result = new StringBuilder(); reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; while(null != (line = reader.readLine())){ result.append(line).append('\n'); } return result.toString(); }catch (MalformedInputException e){ Log.e(TAG, "doInBackground: Invalid URL" + e.getMessage()); }catch (IOException e){ Log.e(TAG, "doInBackground: IO Exception reading data" + e.getMessage()); }catch (SecurityException e){ Log.e(TAG, "doInBackground: Error connetino to internet. neeed permision" + e.getMessage()); }finally { if(connection != null){ connection.disconnect(); } if(reader != null){ try{ reader.close(); }catch (IOException e){ Log.e(TAG, "doInBackground: Error closing the reader" + e.getMessage()); } } } return null; } و لقد قمنا بعمل التالي:
    doInBackground تتلقى الـ URL الذي يوجد فيه ملف الـJson و المتمثل في الـparameter المسمى params. نسند قيمة مبدئية null للمتغيرين reader, connection. نتحقق من الـparameter  المسمى params على احتوائه على ملف JSON من عدمه. انشاء الإتصال بالإنترنت. انشاء StringBuilder object حتى يتم الحاق كامل ملف JSON بهذا object. قراءة كامل ملف JSON  عن طريق BufferReader و يمكن أن تتساءل ما الفائدة من المتغير response؟ عن طريقه يمكن معرفة نجاح الاتصال بالانترنت او فشله.
    حيث انه اذا ظهر لك المتغير response  بقيمة 200 هذا يعني نجاح الاتصال، غير ذلك يعني فشله. قراءة الملف سطر بعد سطر عن طريق المتغير line و الحاقها بالـتغير result حيث أن الـloop سوف تنتهى عند انتهاء ملفJSON. أحطنا الـ code بـ try and catch حتى نتعامل مع الأخطاء المحتملة منها: فقدان الاتصال بالانترنت، او خطأ في عنوان الرابط.. الخ. إذا حصل خطأ نقوم بـreturn null.
       نقوم بإضافة الكود في داخل الميثود onPostExecute و التي بدورها سوف تستعدي الميثود onDownloadComplete المتواجدة في كلاس المستعدي(caller)
    لإشعاره بانتهاء تحميل البيانات مع تمرير الـstring الخاص بالـJSON.
    @Override protected void onPostExecute(String s) { Log.d(TAG, "onPostExecute: The parameter is: " + s); if(mCallback != null){ mCallback.onDownloadComplete(s); } Log.d(TAG, "onPostExecute: Ends"); }  
     وبالنهاية نقوم بانشاء الكلاس caller وليكن على سبيل المثال الـMainActivity.
    public class MainActivity extends AppCompatActivity implements GetRawData.OnDownloadComplete{ private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate: starts"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); GetRawData getRawData = new GetRawData(this); getRawData.excute("https://api.flickr.com/services/feeds/photos_public.gne"); Log.d(TAG, "onCreate: ends"); } @Override public void onDownloadComplete(String data) { Log.d(TAG, "onDownloadComplete: starts"); //Do something here. Log.d(TAG, "onDownloadComplete ends"); } } كما موضح أعلاه:
    انشاء الكلاس MainActivity و قمنا بعمل implements للـinterface المسمى OnDownloadComplete. انشاء اوبجكت من النوع GetRawData. استدعاء الميثود excute و تمرير الرابط الذي يحتوي على بيانات الـJSON و التي بدورها سوف تقوم بانشاء الـthread المنفصل. قمنا بـimplements للميثود onDownloadComplete بحيث سوف يتم استدعاؤها تلقائيا عند انتهاء الكلاس GetRawData من تحميل البيانات و هذا هو المقصود من اشعار الـCaller.  
     لا ننسى الخطوة المهمة و التي لا يكتمل التطبيق إلا بها و هي أخذ الإذن للإتصال بالإنترنت. حيث تتم هذه العملية بإضافة السطر البرمجي الموضح أدناه في ملف AndroidManifest.xml المتواجد في مجلد Manifests.
     
    <uses-permission android:name="android.permission.INTERNET"/> و هنا وصلنا الى نهاية هذا الدرس.🙂
    في الدروس القادمة سوف أتناول كيفية معالجة و قراءة الـJSON المحملة.
    مستوى المقال: مبتدئ
  11. الـ Context يعد تقريباً من اكثر الامور المستخدمة في برمجة الاندرويد, وبالتأكيد قد تعاملت معه في تطبيقك ولكن كيف يعمل وماهو بالضبط؟
    مفهوم الـ Context
    افضل طريقة لشرح مفهوم الـ Context هو بالمثال, دعنا نفترض ان لديك مبنى وبداخل المبنى مجموعة من المخازن او المستودعات لتخزين الاغراض, وبداخل كل مستودع بعض الصناديق وكل صندوق بداخله بعض الاغراض, فاذا طلبت منك مثلا ان تحضر لي غرض ما من مستودع ما بداخل المبنى فهنا انا اخبرتك بالوجهة الاولى او المحيط الذي يجب عليك ان تبحث فيه وهو المبنى, ولكن لكي اسهل عملية البحث عليك ساحدد لك المستودع وبشكل اعمق ساحدد لك الصندوق الذي يجب ان تبحث بداخله, فبهذه الطريقة قمت بتضيق المحيط الذي يجب ان تبحث بداخله لكي اسهل عليك مهمة البحث .
    لنطبق نفس المثال السابق على الاندرويد, اعتبر ان جهازك هو المبنى وبداخل جهازك مجموعة من التطبيقات (مستودع) و كل تطبيق معزول تماماً عن بقية البرامج الاخرى, وبداخل كل تطبيق بعض المجلدات وكل مجلد مختص بامر ما, فبعض المجلدات قد تجد بداخله صور وبعضها الاخر قد تجد بداخله نصوص او غيرها.
    الان ان اردت مثلاً ان اصل الى صورة موجودة في مجلد الصور الخاصة بـ تطبيق X, فيجب علي ان احدد المحيط (Context)الذي يجب ان ابحث بداخله, فالمحيط  هنا هو التطبيق X, ويسمى هذا المحيط في الاندرويد بالـContext.
    من احد النقاط المهمة ايضا التي يجب ان نوضحها هي ان محيط الغرفة بداخله مجموعة من الصناديق, وهذه الصناديق تنتمي للغرفة وهذا يعني انه من محيط الغرفة استطيع الوصول الى اي صندوق اريده ولكن من محيط الصندوق لا استطيع الوصول الى بقية الصناديق الا عن طريق الغرفة.

    ماهو الـ Context
    ان اردنا تعريفه من منظور برمجي فالـ Context يعد Abstract Class تستطيع من خلاله الوصول الى ملفات وموارد التطبيق او تستطيع من خلاله استدعاء اوامر موجودة في النظام مثل  launching activities, broadcasting and receiving intents في نطاق المحيط الخاص بالتطبيق.
    انواع الـ Context

    الـ Context له العديد من الانواع وليست جميعها متشابهه اثناء انشائها, فنوعها يعتمد على الـ Component الذي قام بانشائها:
    Application
    الـ Context من هذا النوع يعد singleton وهذا يعني ان تطبيقك لديه Context واحد فقط من نوع application ومن خلال هذا الـ context تستطيع الوصول الى جميع المصادر (resuorese) والملفات الخاصة بالتطبيق سواء كانت صور او واجهات او غيرها, تستطيع استدعاء هذا الـ Context طريق الميثود  getApplication() من اي Activity او Service, وتستطيع استدعائه عن طريق getApplicationContext() من اي Object يرث من Context, من ناحية اخرى لايهم من اي Class قمت باستدعائهلانه في جميع الاحوال سيرجع لك نفس الـ Context.
    Activity/Service
    هذا النوع يرث من ContextWrapper وهو مايعرف بـ base context, فعند انشائك لـ Activity or Service جديدة, سيقوم النظام بانشاء Context خاص بها, وهذا يعني اذا قمت بانشاء two acttivities سيقوم النظام بانشاء Context لكل Acticity, و مايجب عليك الانتباه له هو ان كل context قام النظام بانشائه هو معزول تماماً عن الاخر اي ان الـ context الخاص بالـ Activity الاول ليس له الصلاحية للوصول الـ Context الاكتفتي الثانيه الا عن طريق Application Context وهو الذي يجمعهم سويه.
    Broadcast Receiver
    لايحتوي على Context خاص به ولايقوم النظام بانشاء واحد خاص به مثل الـ Activity, ولكن مايفعله النظام هو انه يمرر له Context عند استدعائه, والـ Context الذي سيقوم النظام بتمريره له هو الـ Context الذي قام باستدعائه, فمثلا لو قام Service بالتعامل مع الـ Broadcast Receiver سيقوم النظام بتمرير الـ Context الخاص بالسيرفس عن طريق ميثود  onReceive() في كل مرة يتم استدعائه فيها.
    Content Provider
    ايضا لاتحتوي على Context خاص بها, ولكن يعطى لها Context عند انشائها والتي يمكن الوصول لها عن طريق getContext().
    صلاحيات الـ Context
    كما قلنا سابقاً ان هناك انواع عديدة من الـ Context وبعضها معزول تماماً عن الاخر “baseContext” وبعضها لديه الصلاحية للوصول الى Context اصغر منه “ApplicationContext”, فبعضها تستطيع من خلاله الوصول الى بعض الصلاحيات وبعضها لاتستطيع

    تنبيهات للتعامل مع الـ Context
    لإستدعاء الـ Context الخاص بالـ Application , قم باستدعائه مباشرة عن طريق getApplicationContext() لاستدعاء الـ Context الخاص بالـ Activity, قم باستدعائه عن طريق getBaseContext() لاتقم ابداً بحفظ الـContext الخاص بالـ Application بداخل اوبجكت من Context وانما قم باستدعائه دائما عن طريق getApplicationContext() اخيراً
    الـ Context من احد اهم المفاهيم في برمجة الاندرويد والتي يجب عليك معرفتها ومعرفة انواعها وطريقة التعامل معها, فكل نوع لديه صلاحياته الخاصة وكل نوع يجب عليك استخدامه في مكانه المناسب والوقت المناسب.
    مستوى المقال: متوسط
  12. السلام عليكم و رحمة الله وبركاته.
     
    في هذه المقالة سنتعلم كيفية التعامل مع ListView في Android Studio و كيفية إنشائه وبنائه داخل تطبيقك.
     
    ملاحظة: يفضل أن تكون قد قرأت مقالتي السابقة عن كيفية التعامل مع RSS Feed في Android Studio من هنا.  
    في البداية، ما هو الـListView ؟
    الـListView هو  أداة من أدوات واجهة المستخدم (View) و التي تستخدم لعرض مجموعة من البيانات من خلالها و التي بدورها تتيح ميزة الـScroll Bar. و تحديدا تستخدم في عرض البيانات التي تكون على شكل مجموعة عناصر مثل قائمة لمجموعة من الصور أو أسماء فريق كرة القدم ..الخ. علما أنه لا يكاد يوجد تطبيق في Play Store إلا ويوجد فيه الـListView بما في تلك التطبيقات الشهيرة كتطبيق Facebook فكل البوستات الموجودة في الـFacebook و التي تشاهدها في الصفحة الرئيسية عند فتح التطبيق ما هي إلا شرائح من البيانات المعروضة باستخدام الـListView و كذلك في Twitter وInstagram و غيرها.. .
     
    و لذلك يعتبر الـListView من أهم الأدوات الموجودة في برنامج الـAndroid Studio  و من أكثر الأدوات استخداما في بناء التطبيقات إن لم يكن أكثرها لذلك من المهم لمبرمج التطبيقات أن يتعلم طريقة إنشائه و التعامل معه في التطبيق و ما سنقوم بعمله في هذه المقالة هو عرض قائمة للبيانات المحملة و المعالجة في المقالة السابقة و هي Top10Appsباستخدام الـListView.
     
    ما هي آلية عمل الـListView؟

    لإنشاء View لكل شريحة من البيانات يتطلب هذا الأمر الكثير من الوقت و المساحة من الـComputer حيث أن كل View تحتاج تقريبا اللى 1 Kb من RAM فتخيل لو تم انشاء الألاف من Views.
    فهنا تكمن فائدة الـListView، حيث أنها تعمل على إعادة استخدام الـView المنشأة سابقا عندما يقوم المستخدم بالنزول أو الطلوع بالقائمة (Scrolling).
     
     تتلقى الـListView بياناتها من Adapter و الذي بدوره يقوم بتوفير البيانات و تنسيق الـView المراد عرض البيانات عليها كما هو موضح أدناه.


     
    و الآن ننتقل للتطبيق في الـAndroid Studio:
     
    في البداية نقوم بفتح Android Studio ثم نقوم بالضغط على File ثم Open و ننتقل الى الـProject الذي يحتوي على الملفات و الكلاسات التي تم انشاؤها في المقالة السابقة.


    و الآن حتى يتم إنشاء ListView يجب علينا ما يلي:
     
    الانتقال الى ملف activity_main.xml. من قائمة Pallete ثم container نبحث عن الـListView و نقوم بسحبها الى Layout. نعدل الـID و لتكن على سبيل المثال xmlListView. نعدل الـMargins و التي هي: 16,16,16,16 كما هو موضح ادناه.
     
    و الآن نقوم بانشاء layout حيث ان هذا الـlayout يحتوي على الـViews المراد تنسيقها ووضع البيانات بداخلها عن طريق الـAdapter:
    من قائمة File ثم new ثم نقوم بالضغط على Layout resource file. نقوم بتسميته على سبيل المثال list_record.xml. نضيف اليه ثلاثة Text View من قائمة Pallete. نعدل الـID لهذه الـText Views وليكن على سبيل المثال: tvName, tvArtist, tvSummary. نعدل الـMargins على عحسب الرغبة. نعدل الـConstraints  كما هو موضح أدناه.  

     
    و الآن حتى نقوم بانشاء الـAdapter يجب علينا ما يلي:
     
    انشاء كلاس و نسميه على سبيل المثال FeedAdapter.  حيث أن هذا الكلاس Extends الـArrayAdapter. و يحتوي أيضا على Inner Class ويسمى بالـViewHolder. package com.example.rami.top10apps; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import java.util.List; /** * Created by Rami on 2/19/2017. */ public class FeedAdapter extends ArrayAdapter { private static final String TAG = "FeedAdapter"; private final int layoutResource; private final LayoutInflater layoutInflater; private List<FeedEntry> applications; public FeedAdapter(Context context, int resource, List<FeedEntry> applications) { super(context, resource); this.layoutResource = resource; this.layoutInflater = LayoutInflater.from(context); this.applications = applications; } @Override public int getCount() { return applications.size(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = layoutInflater.inflate(layoutResource, parent, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } FeedEntry currentApp = applications.get(position); viewHolder.tvName.setText(currentApp.getName()); viewHolder.tvArtist.setText(currentApp.getArtist()); viewHolder.tvSummary.setText(currentApp.getSummary()); return convertView; } private class ViewHolder { final TextView tvName; final TextView tvArtist; final TextView tvSummary; ViewHolder(View v) { this.tvArtist = (TextView) v.findViewById(R.id.tvArtist); this.tvName = (TextView) v.findViewById(R.id.tvName); this.tvSummary = (TextView) v.findViewById(R.id.tvSummary); } } }
    و لقد قمنا بعمل التالي:
    إنشاء layoutResource variable  من نوع int و الذي يحتوي على Layout الذي أنشأناه و أسميناه بالـList_record.xml. إنشاء layoutInflator variable من نوع LayoutInflator و الذي يقوم بتحويل الـLayout الى اوبجكت يمكن التعديل عليه. إنشاء applications variable  من نوع <List<FeedEntry و الذي يحتوي على البيانات المراد عرضها. إنشاء Constructor يتلقى الباراميترز الخاصة بالـinstance variables. عمل override للـmethods التالية:() getCount(), getView. الميثود getCount تقوم بإرجاع عدد العناصر الموجودة بالـList. الميثود getView يتم استدعاؤها تلقائيا من ListView عند الحاجة الى بيانات جديدة و يتم تمرير الـParameters التالية اليها: position, convertView, parent. و تقوم هذه الميثود بعمل التالي:
    إنشاء اوبجكت من الـViewHolder بحيث يتم تخزين الـTextViews بهذا الاوجكت لمرة واحدة. حفظ هذا الاوبجكت في الـTag الخاص بالـconvertView.  يتم تعديل الـTextViews وفقا للبيانات المستخرجة من الاوبجكت currentApp.  
    بالنهاية، يتم إنشاء Inner class و تسميته بالـViewHolder.  
    من الممكن أن تتساءل ما فائدة الـViewHolder؟
    كما تلاحظ يتم استدعاء الميثود findViewById لمرة واحدة في بداية تشغيل التطبيق بينما من دون هذا الكلاس سوف يتم استدعاء هذه الميثود كل ما احتاج الـListView الى بيانات جديدة.
     
    و الآن نذهب الى الـInner class في الـكلاس MainActivity و الذي يسمى DownloadData، و نقوم بوضع هذا الكود ادناه في الميثود onPostExcute.
    @Override protected void onPostExecute(String s) { super.onPostExecute(s); Log.d(TAG, "onPostExecute: parameter is " + s); ParseApplications parseApplications = new ParseApplications(); parseApplications.parse(s); FeedAdapter feedAdapter = new FeedAdapter(MainActivity.this, R.layout.list_record, parseApplications.getApplications()); appList.setAdapter(feedAdapter); } كم نلاحظ أعلاه:
    تم إنشاء اوبجكت من الكلاس ParseApplications و الذي بدوره يقوم بمعالجة البيانات المحملة. إنشاء اوبجكت من الكلاس FeedAdapter و تمرير الـarguments اللازمة للـAdapter و أخيرا يتم استدعاء الميثود setAdapter على الاوبجكت appList و الذي يمثل الـxmlListView.  
    ملاحظة: الكلاس DownloadData تم إنشاؤه في المقالة السابقة.

    و أخيرا نقوم بتشغيل البرنامج وهذه هي النتيجة النهائية.


     
    و هنا وصلنا الى نهاية هذا الدرس.😄
    في الدروس القادمة سوف أتناول كيفية التعامل مع ملفات الـJson في الـAndroid.
    مستوى المقال: متوسط

عالم البرمجة

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