1. بسم الله الرحمن الرحيم
    السلام عليكم ورحمة الله وبركاته
    هذه المقالة تمهيديه في شرح و كتابة اختبارات الـ Junit للغة الـ Java. والتي من خلالها اصبو الى كتابة المزيد من المقالات في مجال الاختبارات للجافا و للإندرويد في المستقبل ان شاء الله. لإني ارى ان هناك شح كبير جداً في هذا المجال و ان شاء الله اوفق ولو بسد جزء بسيط به, ولا ارى ضرر في كتابة الاخوان في هذا المجال لمن يريد, فالقارئ ربما يفهم من هذا المقال او من ذلك المقال.
     
    ماذا سوف تقرئ في هذه المقالة
    ماهي مكتبة الـ Junit وماهي متطلباتها؟ ماهي الطريقة التي تتم بها كتابة هذا النوع من الاختبارات؟ ماهي اهم الـ Annotations, و الـ Assertions المستخدمة في هذه المكتبة. مع امثلة برمجية وبعض الملاحظات لتقريب المفهوم. وسوف نرى كيفية انشاء مجلد خاص بهذه الاختبارات, وانشاء اول كلاس لكتابة الاختبارات به, ثم كيفية تشغيلها.
     
    ماهو الـ JUnit
    عبارة عن مكتبة لكتابة وتشغيل الاختبارات المتكرره بشكل سهل وسلس.
     
    الموقع الرسمي
    JUnit
     
    المتطلبات
    مكتبة الـ JUnit للإختبارات, الاصدار الرابع (مميزة هذا الاصدار انه مدعوم في الاندرويد كذلك, فجميع هذه الاكواد تعمل في الـ Android Studio بطلاقه). معرفة كيفية استخدام الـ Annotations الخاص بالـ JUnit.  
    طريقة كتابة هذ النوع من الاختبارات
    يقوم على كتابة دالات اختبار Unit Test في كلاس خاصة بها, ومن خلالهم تقوم باختبار دالاتك الحقيقه في مشروعك. وكل من هذه الدالات الاختبارية يأتي فوقها Annotation خاص بالـ JUnit تحدده انت حسب الغرض.
     
    اهم الـ Annotations المستخدمة في اجراء اختبارات الـ JUnit
    هذا جدول يوضح اهم الـ Annotations التي سوف تحتاجها في كتابة دالات اختباراتك. من الافضل حفظهم عن ظهر قلب.

     
    مثال برمجي لتوضيح تسلسل عمل هذه الـ Annotations على دوال الاختبارات
    package com.company; import org.junit.*; public class MainTest { @BeforeClass public static void preSetup(){ System.out.println("preSetup() Runs!"); System.out.println("-----------------"); } @Before public void setup(){ System.out.println("setup() Runs - Preparing Object"); } @Test public void sum1_test(){ System.out.println("sum1_test() Runs"); } @Test public void sum2_test(){ System.out.println("sum2_test() Runs"); } @Ignore @Test public void sum3_test(){ System.out.println("sum3_test() Runs"); } @After public void clean(){ System.out.println("clean() Runs = Cleaning test environment"); } @AfterClass public static void postClean(){ System.out.println("-----------------"); System.out.println("postClean() Runs!"); } }  
    الناتج
    preSetup() Runs! ----------------- setup() Runs - Preparing Object sum1_test() Runs clean() Runs = Cleaning test environment setup() Runs - Preparing Object sum2_test() Runs clean() Runs = Cleaning test environment Test ignored. ----------------- postClean() Runs!  
    ملاحظات
    الدالتين setup و clean تم تشغيلهم اكثر من مره, لكل دالة اختبار, وهذا يعود لتطبيق النوتيشن After و Before اعلاهم. الدالتين preSetup و postClean تم تشغيلهم فقط مره واحده, لهذه الكلاس, وهذا يعود لتطبيق النوتيشن BeforeClass و AfterClass اعلاهم ويجب عندها ان يكونان static. دالة الاختبار المسمية بالـ sum3_test قد تم تجاهلها, وهذا يعود الى استخدام النوتيشن Ignore اعلاها. اما دوال الاختبار لدينا المسماه sum1_test و sum2_test فقد تم تشغيلهم بسلاسة ونجاح.  
    انشاء بيئة الاختبارات الخاصة بالـ JUnit على مشروع جافا
    الان لنقم بإنشاء بيئة الاختبارات الخاصة بنا, والتي من خلالها سوف نقوم باختبار مشروعنا التجريبي (عباره عن حاسبة اطفال بلغة الجافا). هذه الطريقة تمكنك من انشاء بيئة الاختبار يدوياً Best Practice.
    قم بتشغيل برنامجك المفضل للجافا (في هذا الشرح IntelliJ IDEA). انشئ مشروع جافا جديد 1.8 ولاتنسى اختيار الـ Command Line App او اختر مشروع سابق. انشئ مجلد جديد في مسار مشروعك وسمه مثلاً test. علم هذا المجلد بانه خاص للإختبارات, حتى يفهم الـ IntelliJ IDEA. اذهب الى اسم كلاسك الرئيسية واضغظ عليها بالزر الايمن للماوس ومن القائمة اختر Go To ثم Test. اضغظ على Create New Test... في النافذة المنبثقة. ستظهر لك نافذة جديده للإختبارات, اختر مكتبة الـ JUnit 4 المتطابقة مع هذا الشرح. انتباه: اذا رأيت الرسالة JUnit4 library not found in the module اضغظ على Fix. ثم استخدم المكتبة الموجودة بمسار الـ IntelliJ او قم بتحميلها وتحديد مسار اخر لها.  
    الخطوات بالصور حتى يسهل الأمر
    انشاء مجلد الاختبارات
     
    تعليم هذا المجلد وجعله خاص للإختبارات
     
    انشاء اول اختبار للكلاس الرئيسية
     
    ثم انشاء اختبار جديد
     
    تعديل الاعدادات واختيار مكتبة الاختبارات كأول مره فقط (لاتنسى اصلاح خلل ايجاد مسار المكتبة)
     
    اصلاح Fix خلل ايجاد مسار مكتبة JUnit على جهازك
     
    النتيجة, كلاس جاهزة انشئت لكتابة الاختبارات في المجلد التي قمت بتخصيصة
     
    الـ Assertions في الـ JUnit
    يأتي مع الـ JUnit دوال تسمى Assertions من خلالهم تستطيع اجراء الاختبارات, ويجب عليك حفظ وفهم هذه الدوال, كما هو الحال مع الـ Annotations. يمكنك الاطلاع عليهم من خلال هذا الرابط.
    اهم هذه الدوال هي:

     
    شرح المشروع
    قبل كتابة الاختبارات لنفهم هذا المشروع قليلاً. فأسم المشروع هو KidCalculator. وتستطيع ايجاد نسخة منه على رابط الجيت هوب لدي.
    يتكون من عدة كلاسات (حالياً) وهي:
    Main.java وهي الكلاس الرئيسية للمشروع. MainTest.Java وهي كلاس الاختبارات التي انشئناها بالخطوات السابقة. Calculator.Java وهي عبارة عن Model لكلاس الحاسبة لدينا, تحتوي على دوال الحاسبة كالجمع والطرح والقسمة والضرب الخ... والتي من خلالها سيبنى المشروع. BigNumberException كلاس exception خاص بالمشروع, حتى نستخدمها لاحقاً. سيكون عملنا فقط كتابة اكواد داخل كلاس الـ MainTest.Java بما ان الاختبارات هي الهدف من هذه المقالة.
     
    كتابة الاختبارات
    اولاً لنلقي نظرة على كلاس الـ Calculator حتى نفهم هذه الموديل package com.company; public class Calculator { private String mColor; private boolean mPower; public Calculator(String color) { mColor = color; } public String getColor() { return mColor; } public void setColor(String color) { mColor = color; } public boolean checkPower() { return mPower; } public void setOn() { mPower = true; } public void setOff() { mPower= false; } public int addition(int firstNumber, int secondNumber) { return firstNumber + secondNumber; } public int subtraction(int firstNumber, int secondNumber) { return firstNumber - secondNumber; } public int multiplication(int firstNumber, int secondNumber) { return firstNumber * secondNumber; } public int division(int firstNumber, int secondNumber) { return firstNumber / secondNumber; } }  
    والان لنقم بكتابة الاختبارات لهذه الموديل لإختبار المنطق في اجراء العمليات الحسابية حسب ماتم برمجته في الكود السابق. package com.company; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class MainTest { private Calculator calculator; @Before public void setUp() { calculator = new Calculator("Red"); } @Test public void addition_test() { int result = calculator.addition(2, 2); assertEquals(result, 4); } @Test public void subtraction_test() { int result = calculator.subtraction(10, 4); assertEquals(result, 6); } @Test public void multiplication_test() { int result = calculator.multiplication(5, 5); assertEquals(result, 25); } @Test public void division_test() { int result = calculator.division(10, 2); assertEquals(result, 5); } }  
    ملاحظات:
    استخدمنا هنا assertEquals وهي عباره عن دالة JUnit تاخد قيمة متوقعة (result) ثم تقارنها بالقيمة الحقيقة اي الصحيحة. قمنا بأنشاء عنصر من موديل حاسبتنا Calculator في دالة الـ setUp والتي تعمل قبل كل الدوال حتى تنشئ عنصرنا ونتمكن من استخدامه في باقي دوال الاختبارات. وتشغيل هذه الاختبار من خلال الضغظ بالزر الايمن على اسم الكلاس واختيار Run 'MainTest' او من خلال الايقونة الصغيره الخضراء كشكل السهم المستدير بجانب اسم الكلاس, كما بالصورة التالية:

     
    نتيجة الاختبار

    نجاح جميع الاختبارات كما هو متوقع.
    ملاحظة: لاتعير اهتمام للسطر الاحمر فهو خلل برمجي في الجافا, يحدث للماك وهذه اقتباس من احد العاملين بشركة اوراكل:
     
    المزيد من الاختبارات
    سوف نقوم بانشاء دالة تغير لون حاسبتنا وتتأكد من تغيير اللون. و أيضاً سنقوم بانشاء دالة اخرى تتاكد من هذا اللون, ثم نشغل جميع الدوال.

     
    ملاحظات:
    قمنا بكتابة و تشغيل ٦ اختبارات, ٥ نجحو, وواحد فشل. المهم هو ان الدالة check_color_and_change_it_test قد نجحت في تغيير اللون, ونجحت ايضاً في التأكيد من ان اللون قد تغير الى الازرق. اما الدالة check_color_is_still_changed قد فشلت في التأكيد من ان اللون قد اصبح ازرق؟ لماذا؟ اليست الدالة التي قبلها قد قامت بتغيير هذا اللون؟  

    وهكذا قمنا بأنشاء مجلد اختبارات + انشاء كلاس اختبارات + كتابة ٦ دوال اختبارات باستخدام بعض من الـ Annotations و Assert واحده وهي assertEquals. واتوقف هنا حتى لايصبح للقارئ overwhelming و burnout, واكمل في المقالات اللاحقة ان شاء الله.
    مستوى المقال: متوسط
  2. ماهو Firebase Auth
    هي طريقة لربط مستخدمي تطبيقك في Firebase ولها 6 طرق في تسجيل الدخول:
    Email/Password Google باستخدام حساب Facebook باستخدام حساب Twitter Github Anonymous (تسجيل الدخول بدون إعطاء أي معلومات) تسجيل الدخول باستخدام رقم الهاتف  
    وسنحاول شرح معظم هذه الطرق في الدروس القادمة,أما في هذا الدرس سنشرح أسهل طريقتين لتسجيل الدخول إما باستخدام البريد الإلكتروني أو Anonymous 
     
    تسجيل الدخول باستخدام Anonymous Login
    هذه الطريقة لاتطلب من المستخدم إعطاء أي معلومات بل ولن يشعر المستخدم بأنه قد تم تسجيله بالفعل,وتفيد هذه الطريقة في حالة أنك تريد ربط هذا المستخدم وحفظ بيانات اللعبة على سبيل المثال لديك على السيرفر والمستخدم لايريد إعطاء أي معلومات .
    نبدأ بإنشاء مشروع جديد على Firebase ومشروع جديد في Android Studio ونربط مشروع الAndroid Studio بمشروع الFirebase (كما فعلنا في الدرس السابق)
    ثم نضيف مكتبة Firebase Auth الى مشروع الأندرويد  في (build.gradle:app) ثم نعمل Sync
    compile 'com.google.firebase:firebase-auth:10.2.1' ثم نذهب الى Firebase Console ونفعل Anonymous

     
    بعد ذلك نذهب الى MainActivity ثم نعرف اوبجكت من FirebaseAuth  ونسميه mAuth ونعطيه قيمة في onCreate
     

     
    ثم ننشئ ميثود signInAnonymously ونستدعي الميثود في onCreate داخل هذه الميثود استخدمنا ميثود من  FirebaseAuth اسمها signInAnonymously وأضفنا لها addOnCompleteListener وهي interface تعيد لنا نتيجة عملية تسجيل الدخول وتعيد لنا task وقمنا بعمل log لنتيجة عملية التسجيل,وإذا كانت العملية غير ناجحة(!Succesful) نقوم بإظهار رسالة Toast

    نجرب تشغيل التطبيق وإذا نجحت العملية سنجد true في logcat

    نتوجه الى Firebase Console  داخل Authentication ثم Users وسنجد أنه تم تسجيل مستخدم جديد وتظهر لنا User UID وهو ID ينشئ لكل مستخدم موجود على Firebase

     
    تسجيل الدخول باستخدام بريد الكتروني Email & Password
    بدايةً يجب علينا تفعيل تسجيل الدخول باستخدام البريد الإلكتروني من Firebase Console

    بعد ذلك في activity_main.xml سننشئ 2EditTexts الأول للبريد الإلكتروني والثاني لكلمة المرور  بالإضافة الى زر LOGIN لتسجيل الدخول وأخيراً textView للإنتقال الى أكتفتي تسجيل حساب جديد Signup  

    نبدأ أولاً بعملية تسجيل حساب جديد ونقوم بإنشاء أكتفتي جديد لهذا الأمر وسيكون نفس الأكتفتي السابق ولكن بدل زر LOGIN سيكون SIGNUP ونغير عنوان textView للانتقال الى أكتفتي تسجيل الدخول.
    ثم داخل أكتفتي SignupActivity ننشئ ميثود SignupUser ونستدعيها عند الضغط على زر SignupBtn ونعطيه قيمة الEditTexts.
    داخل ميثود SignupUser استخدمنا ميثود من Firebase Auth اسمها createUserWithEmailAndPassword وبالطبع تأخذ 2 بارامتر email و password 
    ثم أضفنا ميثود addOnCompleteListener التي تعيد لنا نتيجة العملية وقمنا بالتحقق اذا تمت العملية بنجاح أم لا بنفس فكرة المثال السابق

    نجرب تشغيل التطبيق ونضع أي إيميل وأي باسوورد ونضغط على Signup وإذا نجحت العملية سنرى رسالة Toast User Created

    نذهب الى Firebase Console وسنجد أنه تم إنشاء حساب جديد

    ننتقل الآن الى عملية تسجيل الدخول بحساب حالي(الحساب الذي أنشأناه) في أكتفتي MainActivity وننشئ ميثود SignInUser يأخذ نفس البارامترات ونفس onCompleteListener ولكن هذه المرة signInWithEmailAndPassword 

    إذا نجحت عملية تسجيل الدخول سنرى رسالة Toast

     
    معرفة هل تم تسجيل الدخول  باستخدام Firebase Auth State Listener
    في بعض الأحيان عند تشغيل التطبيق نريد معرفة هل تم تسجيل الدخول أم لا (فمثلا إذا تم تسجيل الدخول قم بتشغيل أكتفتي جديد  وإلا أظهر أكتفتي تسجيل الدخول..)
    في MainActivity نعرف اوبجكت من FirebaseAuth.AuthStateListener 

    وننشئ ميثود جديد اسمه initAuthStateListener ونستدعيه داخل onCreate ,هذا الميثود يقوم بتعريف mAuthListener .
    الmAuthListener تعيد لنا firebaseAuth, الذي يحتوي على ميثود getCurrentUser  والتي بدورها تقوم بجلب المستخدم الحالي ,ثم نتحقق اذا كان user لا يساوي null (بالتالي المستخدم قد قام بتسجيل الدخول )عندها نقوم بعمل log ل uid الuser وإلا else المستخدم لم يقم بتسجيل الدخول او قد قام بتسجيل الخروج

    أخيراً نقوم بعمل override ل onStart و onStop , ونضيف mAuthListener ل mAuth في onStart وفي onStop نقوم بإزالة هذا Listener

     
    نجرب تشغيل التطبيق وإذا كان المستخدم قد قام بتسجيل الدخول عندها ستظهر الرسالة في logcat مع uid.

    تُوفر Firebase أيضاً ميزة تفعيل البريد الإلكتروني(إرسال رسالة تأكيد من Firebase الى البريد الذي تم إدخاله) وإستعادة كلمة المرور في حال نسيانها وتغيير البريد الإلكتروني وإمكانية تخصيص شكل الرسالة ,يمكنك تفقدها في خانة Email Templates في Firebase Console.
    هذه كانت مقدمة بسيطة عن Firebase Authentication وسنحاول التطرق لها أكثر في الدروس القادمة
     
    المشروع كاملاً على Github
    ملاحظة:المشروع على Github للمعاينة فقط ولايمكنك تجربته على Android Studio لعدم وجود google-services.json الخاص بك  
    مستوى المقال: مبتدئ
  3. بسم الله الرحمن الرحيم 
    السلام عليكم ورحمة الله 
    تكلمت في مقالتين سابقتين عن الـ Broadcast 
    في هذي المقالة سأتكلم عن أنواع الـ Broadcast و الفرق بين كل نوع و أخر . 
    كما تعلم عزيز القارئ ان الـ Broadcast ينقسم الى أربع أنواع- تكلمت عنها في المقالة السابقة-  وهي مقسمة كما في الصورة التالية : 

     
    النوع الأول وهــو الـ Normal : 
    يستخدم دالة الــ sendBroadcast  غير متزامن عند عملية التنفيذ .   في المقالة السابقة ، شرحت هذا النوع بالتفصيل . 
     
    النوع الثاني وهــو الـ Ordered : 
    يستخدم دالة الــ sendOrderedBroadcast  متزامن عند علمية التنفيذ ، اي يقوم بتنفيذ العملية على حسب الأولوية .        كيف يعني ؟؟ 
    نفرض ان لديك - Action - واحد فقط  يتم إستدعائه من ثلاث كلاسات مختلفة و ليكن اسم الكلاسات هي Class A & Class B & Class C .. 
    الأن تريد تنفيذ الحدث الموجود في كلاس C قبل كلاس A أو كلاس B ، أو ربما تريد تنفيذ الحدث B ثم C ثم A .. كما في الصور التالي : 

     
    تم تعريف - Action - واحد في ملف - Mainfest - كما في الصورة : 

    لاحظ عزيز المبرمج انها تم تعريف اكشن واحد بنفس الاسم ، وكذلك تم تحديد الأولوية في عملية التنفيذ من خلال - android:priority - 
    أعلى قيمة يتم تنفيذها أولاً ،ثم القيم الأقل منها ..  وعند تنفيذ الطلب يتم تمرير اسم الاكشن وهو يتولى المهمة عنك 
    MainActivity.java
    public class MainActivity extends AppCompatActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent("uniq.action"); sendOrderedBroadcast(intent,null); } } لاحظ انه تم تمرير اسم الــ Action عند إنشاء أوبجكت من كلاس الــ Intent .
     
     
    النوع الثالث  الــ Sticky Broadcast  : 
    يعمل هذا النوع من الـ Broadcast بنفس آلية عمل الـ Normal Broadcast  و لكن الفرق 
    الــ Normal ينفذ الحدث و ينتهي عمل الــ Broadcast أي يعمل بمبدأ الــ 
    Dies Quickly Broadcast 
    أما الــ Sticky Broadcast يبقى قريب من التطبيق حتى بعد تنفيذ الحدث - event - ويراقب- أو ينتظر بمعنى-  أخر وقوع الحدث مرة أخرى 
    أي انه ينفذ ثم ينتظر .. ينفذ ينتظر .. وهكذا .
    في الأصدارات ما قبل 21 كأن يستخدم دالة sendStickyBroadcast و لكن من إصدار 21 و ما فوق تم التجاهل و التخلي عن هذي الدالة 
    لأسباب تتعلق بضعف الحماية . 
    ربما تقول بما أنه تم تجاهل هذا النوع لماذا نتطرق له ؟ 
          مازال توجد بعض الــ Actions تستخدم هذا النوع وهي : 
    Action_Battery_Changing 
       Battery_Status_Charging  Battery_Status_Discharging Battery_Status_Full Battery_Status_Plugged_USB Action_Device_Storage_Low 
    Action_Dock_Event 
    هذا جزء من الــ Documentation  : 

    هذي الــ Actions تبقى قريبة من النظام وكذلك من التطبيقات فعند حدوث أي منها يتم إرسال تنبيه الى التطبيقات 
    فالنفرض ان لدي تطبيق يظهر حالة التغير في البطارية وعند كل تغير يقوم بتنفيذ حاجة معينة 
    مثال عملي : تطبيق يظهر نص التغير في حالة البطارية ويظهر لي نص بذلك . 
    public class MainActivity extends AppCompatActivity { private TextView mText ;    private int ctr = 0  @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mText = findViewById(R.id.txv_show_mes); }    // object of BroadcastReceiver. BroadcastReceiver receiver = new BroadcastReceiver() {      @Override       public void onReceive(Context context, Intent intent) { int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,-1); displayStatus(status); } }; // call registerReceiver method . @Override protected void onResume() { super.onResume(); IntentFilter mIntent = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); registerReceiver(receiver,mIntent); } // call unregisterReceiver method . @Override protected void onPause() { super.onPause(); unregisterReceiver(receiver); } private void displayStatus(int status){ switch (status){ // تستطيع العودة الى الارقام المذكرو من كلاس الــ // BatteryManager.java case 1 :               setmText("Battery Status Unknown "); break; case 2: setmText("Battery Status Charging"); break; case 3 : setmText("Battery Status Discharging"); break; case 4: setmText("Battery Status Not Charging."); break; case 5: setmText("Battery Status Full ."); break; default: setmText(""); } } private void setmText(String str){ mText.setText(str); } }  
     تغير وضع حالة البطارية  من خلال : 

    الأن عند تغير كل قيمة يظهر لي نص بحالة البطارية : 

     
     
     النوع الرابع  وهــــو الــ LocalBroadcast : 
    هـــو عبارة عن ارسال و استقبال بيانات محلية - local - من داخل التطبيق أو من تطبيق الى تطبيق أخر . 
    أي بمعنى -  لا يستقبل بيانات من النظام و لا يرسل بيانات الى النظام . 
               من مميزات الـــ LocalBroadcast هي التواصل : 
    بين الــ Activities  بين الــ Activity  و Service  بين الــ Activity  و الــ Broadcast  بين الــ Service و  الــ Broadcast ، وكذلك العكس  بين التطبيقات مثلا تطبيق A يرسل بيانات الى التطبيق B -  إذا تم تمهيد  وقبول التصاريح .   
     يعتبر  هذا النوع من الـ Broadcast  الأكثر حماية لأنه :
     يعمل داخل التطبيق فقط .  الـ Broadcast لا يخرج من التطبيق .  وكذلك التطبيقات الأخر لا يمكنها الوصول الى الــ receiver لأن نوع الـ Action يكون خاص بالتطبيق فقط .  أكثر فعالية لعدم تسريب البيانات خارج التطبيق.   
    صورة لتوضيح طريقة عمل هذا النوع : 

     
    أخيراً ،  مثال عملي لهذا النوع من الــ Broadcast ، هـــو عمل برنامج ارسال بيانات من الــ Activity الى BroadcastReceiver -   كما هو موضح في الصورة السابقة 
    MainActivity.java 
    public class MainActivity extends AppCompatActivity { LocalBroadcastManager mLocal ; EditText num1,num2 ; TextView txvResult ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); num1 = findViewById(R.id.edt_num1); num2 = findViewById(R.id.edt_num2); txvResult = findViewById(R.id.txv_result); mLocal = LocalBroadcastManager.getInstance(this); } public void sendLocalBroadcast(View view) { Intent sendIntent = new Intent(this,MyReceiver.class); // set two numbers .. int a1 = Integer.valueOf(num1.getText().toString()); int a2 = Integer.valueOf(num2.getText().toString()); sendIntent.putExtra("a1",a1); sendIntent.putExtra("a2",a2); // Send Normal Broadcast .. sendBroadcast(sendIntent); } @Override protected void onResume() { super.onResume(); IntentFilter intentFilter = new IntentFilter("my.result.sum"); mLocal.registerReceiver(receiver,intentFilter); } @Override protected void onPause() { super.onPause(); mLocal.unregisterReceiver(receiver); } // Dynamic Receiver ... private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int sum = intent.getIntExtra("sum",0); txvResult.setText(String.valueOf(sum)); } }; } MyReceiver.java 
    public class MyReceiver extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { int a1 = intent.getIntExtra("a1",-1); int a2 = intent.getIntExtra("a2",-1); int sum = a1 + a2 ; LocalBroadcastManager localBroadcast= LocalBroadcastManager.getInstance(context); // declare new intent to put in send broadcast . Intent sendIntent = new Intent("my.result.sum"); sendIntent.putExtra("sum",sum); // Now this value of sendIntent send to localBroadcast, And in MainActivity, I'll declare new Dynamic receiver // To take this value. localBroadcast.sendBroadcast(sendIntent); } } AndroidMainfest.xml 
    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".MyReceiver"/> </application>  
     

     
    ما تم عمله : 
    إنشاء  حلقين من نوع EditText  لكتابة الارقام . عمل دالة لانشاء حدث عند الضغط على زر الارسال  .   في داخل هذا الاكشن تم إنشاء اوبجكت من كلاس Intent ويشير الى كلاس الــ MyReceiver  .  بعد ذلك يتم ارسال البيانات من خلال استخدام دالة الــ sendBroadcast   كلاس الــ MyReceiver  يرث من كلاس الــ BroadcastReceiver ويستقبل عددين مرسل له من كلاس الــ MainActivity .  إنشاء متغير جديد يقوم بعملية جمع المتغيرين ، ثم يرسلها الى MainActivity من خلال LocalBroadcastManager  .  في كلاس ال MyReceiver تم إنشاء اوبجكت من كلاس الــ Intent و وضع اسم للاكشن ، بحيث يتم استقبال هذا الاكشن في كلاس الــ MainActivity  عن طريق عمل dynamic receiver .  في كلاس الــ MainActivity  تم إنشاء اوبجكت من كلاس الــ LocalBroadcastManager  ، بحيث يكون هذا الاوبجكت هو المسؤول عن تسجيل receiver عند تشغيل التطبيق  في الأخير تم إنشاء حقل من نوع TextView لعرض النتيجة المرسلة من كلاس MyReceiver  في الــ MainActivity .   
    "في الأخير لاحظ ان هذا النوع من الــ Broadcast يستخدم الــ طريقة الــ statically broadcast مع طريقة الــ dynamic broadcast   " 
     
     
     
    أتمنى أني وفقت في تبسيط إيصال المفاهيم ،  وأعتذر على الاطالة .      
    هذا وإن كان من صواب فمن الله ،، وإن كان من خطأ فمن نفسي و الشيطان . 
    سبحانك اللهم وبحمدك أشهد أن لا إله إلا أنت أستغفرك وأتوب اليك . 
     
     
    مستوى المقال: محترف
  4. ماهي Firebase ؟
    Firebase هي خدمة قدمتها Google منذ فترة وقد كانت تقتصر فقط على تخزين البيانات وبعض الأشياء البسيطة,ولكن في Google I/O 16 تم الإعلان عن الكثير من المميزات الجديدة والرائعة وأصبحت حديث الكثير من المطورين
    مميزاتها:
    Authentication:وهي عملية تسجيل الدخول سواء عن طريق حساب Facebook,Google,Twitter,Email وفي نفس الوقت حماية البيانات الموجودة في Database (بمعنى أنه يمكنك منع أي شخص من استخدام التطبيق دون عملية تسجيل الدخول وهو الوضع الإفتراضي)
    Realtime Database: وهي تفيد في تخزين البيانات على السيرفر وأكثر شيئ يميزها هي أنها Realtime بمعنى أنه أي تغيير يحصل على الداتابيز سيتغير فوراً في التطبيق كما سنرى في هذا الشرح)
    Storage:تخزين الملفات والصور
    Hosting:لإستضافة موقعك على Firebase
    Notifications: إرسال إشعارات
    والعديد من المميزات يمكنك تفقدها عند الدخول الى حسابك في Firebase,من الجدير بالذكر أن هذه الخدمات مجانية(ولكن ببعض الحدود,يمكنك رؤية ماهي الحدود عبر هذا الرابط) وهو مايميز Firebase
    سنبدأ في هذا الدرس شرح عن Firebase Database وسنحاول مستقبلاً بإذن الله شرح باقي الأمور
    بدايةً يجب أن يكون لديك حساب Google بالطبع بعد ذلك قم بالتوجه الى رابط Firebase Console
    ثم نختار Add project


     
    ثم نضع اسم المشروع الذي نريد ونضع الدولة

    بعد ذلك سيتم إنشاء المشروع,نضغط على Add Firebase to Your Android App

     
    بعد ذلك سيطلب منا اسم الpackage الخاص بتطبيق الأندرويد
    نقوم بإنشاء تطبيق أندرويد جديد ثم نتوجه الى ملف build.gradle وننسخ applicationId

    ونلصقه في Firebase ونضغط Register App
    بعد ذلك نضغط على Download google-services.json وهو ملف إعدادات الذي يربط مشروعنا على Firebase بمشروعنا في Android Studio

     
    سيتم تحميل هذا الملف قم بنسخه 

    ثم نتوجه الى Android Studio ونضغط بالزر الأيمن على مجلد app ونختار Show in Explorer

     
    سيتم فتح مجلد المشروع في حاسوبك نقوم بلصق هذا الملف في مجلد app

     
    الآن يجب علينا إضافة مكتبات Firebase الى مشروعنا في Android Studio

    نتوجه الى ملف build.gradle(project) ونلصق هذا السطر
    classpath 'com.google.gms:google-services:3.0.0'
    ثم نذهب الى build.gradle(app) ونضع سطر plugin
    apply plugin: 'com.google.gms.google-services'
     
     
    الآن تم ربط مشروع Firebase ,وهذه الخطوات لربط أي مشروع Firebase بشكل عام سواءً كان Auth,Notifications,Storage الخ..
     
    أما الآن سنقوم بإضافة مكتبة Firebase Realtime Database الى ملف build.gradle
     
    compile 'com.google.firebase:firebase-database:10.2.1'  

     
    أخيراً نقوم بالضغط على Sync now  ونشغل التطبيق لنتأكد من أن كل شيئ يعمل.
    إذا واجهتك هذه المشكلة

    تأكد من وضع سطر plugin في آخر ملف build.gradle.
     
    الآن نتوجه الى Firebase Console الى Database ثم Rules ونقوم بتغييره الى
    { "rules": { ".read": true, ".write": true } }  

    ثم نضغط على Publish لحفظ التغييرات لكي نستطيع الكتابة والقراءة لأي شخص يستخدم التطبيق(في الوضع الإفتراضي فقط المستخدمين الذين قامو بتسجيل الدخول باستخدام Firebase Auth يستطيعون قراءة أو كتابة البيانات ويمكنك تحديد هذه الأمور في Rules)
    وستجد هذه الرسالة وتفيد بأنه لا يفضل وضع هذه القواعد ويجب عليك فقط جعل المستخدمين الذين قاموا بتسجيل الدخول استخدام التطبيق

    عموماً سنبدأ بالتوجه الى التطبيق ونبدأ بتخزين البيانات

    السطر الأول لتعريف database
    اما السطر الثاني هو لأخذ Reference ال root
    سنضع قيمة child اسمها “Samsung” وقيمتها “S8”

    نشغل التطبيق وسنجد أنه تم إضافة هذه البيانات الى Firebase

     
    تعتمد الFirebase على مبدأ key و value

     
    ملاحظة: عند استدعاء ref.child فإنه يتحقق إذا كان child موجود مسبقاً فسيقوم بإنشاءه وإلا فسيقوم فقط بوضع القيمة
     
    ننتقل الآن الى كيفية عرض البيانات من Firebase وسنقوم بعرض كلمة S8 في TextView ونجرب تغييرها من Firebase Console لنرى سرعة التغيير في التطبيق
    قمنا بوضع ref.child(“Samsung”) وقمنا باستدعاء الميثود addValueEventListener ومهمة هذه الميثود هي الإستماع على أية تغيرات تحصل على child Samsung وبالطبع ال childs الموجودة داخل child Samsung
    وتقوم بعمل Override ل onDataChange وهي تنادى عند حدوث أي تغيير
    و onCancelled تنادى عند حدوث أي خطأ

     
    في onDataChange تقدم لنا الميثود DataSnapshot وهي البيانات الموجودة في Database ممكن ان تحتوي على String,int,long,double,boolean الخ..
     
    قمنا بوضع setText ل textView بالقيمة الموجودة داخل Firebase ,وبما أنني أعرف نوع المتغير الذي وضعته في Firebase وهو ("S8”) فقمت بعمل cast له ك String.class

    نشغل التطبيق وسنجد أنه قد قام بوضع كلمة S8 في ال TextView,وعند تغيير القيمة في Firebase Console سيتم تغييرها فوراً في التطبيق

     
    نلاحظ بأنه اذا  قمت بتغيير القيمة في Firebase Console الى 1 مثلا فسيعتبره  متغير من نوع int ويقوم التطبيق بعمل كراش وذلك لأنه قد قمنا بعمل cast له ك String


     
     
    هذه كانت مقدمة بسيطة عن كيفية حفظ وعرض البيانات
    سنقوم الآن بعمل تطبيق بسيط يخزن أسماء بعض الطلاب مع معدلاتهم وعرضهم في RecyclerView
    وكالعادة سنبدأ بإنشاء كلاس Model وسنسميه Student ونضع به  الاسم String name و المعدل int average

     
    الآن سنقوم بإدخال بعض الطلاب على Firebase

     
     
    قمنا بإنشاء اوبجكت من Student ووضعنا الاسم Khalid و average85
    ثم قمنا باستدعاء الchild الstudents وقمنا بعمل push وهذه الميثود تقوم بإنشاء key بشكل عشوائي (لكل student)
    ثم وضعنا setValue لوضع القيمة وهي اوبجكت student الذي أنشأناه

    سنقوم بإنشاء المزيد من الطلاب للتجربة

     
    نأتي الآن الى عرض البيانات في RecyclerView نقوم بإنشاء RecyclerView Adapter
    نعرف List<Student> و adapter ثم نستدعي ميثود addValueEventListener وداخل onDataChange نقوم بعمل for على DataSnapshot للحصول على جميع المعلومات
    وأنشأنا Student وقيمته تساوي snapshot وقمنا بعمل cast ك Student.class لأننا نعلم أن نوع المتغيرات الموجودة في Firebase هي نفسها الموجودة في Student Class وهي int و String
    ثم أضفنا هذا student الى studentList وأخيراً قمنا باستدعاء الميثود adapter.notifyDataSetChanged لتحديث البيانات في Adapter

    أخيراً نقوم بتعريف RecyclerView و adapter (خارج valueEventListener)

     
    نقوم بتشغيل التطبيق وستجد البيانات تم عرضها بهذا الشكل

    نجرب تغيير قيمة أي عنصر (من Firebase Console) على سبيل المثال نغير معدل Muhammad الى 98
    وستجد أنه قد تم تكرار هذه البيانات 😀

    وهذا لأن الميثود onDataChange يتم استدعاؤها عند كل حدث تغيير على قاعدة بيانات وبالتالي إعادة إضافة العناصر الList
    ولحل هذه المشكلة نقوم فقط بتفريغ List في onDataChange

    أما إذا أردنا عرض العناصر الحديثة من الأعلى (بمعنى أنه أي عنصر جديد يتم إضافته يظهر في الأعلى)
    فقط نقوم بعكس ال List عبر الميثود
    Collections.reverse(studentList);
     
     
    وستظهر بهذا الشكل



     
    Firebase Query عملية البحث داخل Firebase
    نبدأ بإنشاء SearchView في MainActivity ومن ثم نقوم باستدعاء الميثود setOnQueryTextListener وداخل onQueryTextSubmit(هذه الميثود تستدعى عندما يتم الضغط على زر البحث في لوحة المفاتيح)
    نقوم بتعريف Query
    Query fireQuery = ref.child("students").orderByChild("name").equalTo(query); ثم نبحث داخل الchild الstudents ونقوم بالترتيب على حسب child (orderByChild) في حالتنا هذه نود البحث عن الإسم فوضعنا name (يجب أن يكون نفس الإسم في Firebase)
    عندما يكون يساوي الQuery الذي كتبه المستخدم في SearchView query (.equalTo)
    بعد ذلك نقوم باستدعاء addListenerForSingleValueEvent هذه الميثود تشبه تماماً الميثود addValueEventListener ولكن الفرق أن هذه الميثود تستدعى مرة واحدة فقط بدلاً من الإستماع كل مرة.
    داخل onDataChange قمنا بإنشاء List<Student> جديد
    وقمنا بعمل for على snapshot وتنفيذ نفس الخطوات التي شرحناها سابقاً
    ثم عرفنا نفس adapter ولكن أعطيناه searchList الجديدة التي أنشأناها
    وأخيراً قمنا بعمل setAdapter

     
    نشغل التطبيق ونضع اسم Ahmad ونضغط على زر البحث لنرى النتيجة
     

     
     
    وللتأكد من وجود الاسم Ahmad ام لا في Firebase نقوم بالتحقق اذا كانت قيمة dataSnapshot.getValue == null عندها قم بإظهار رسالة Toast وإلا قم بوضع العناصر داخل list وأظهرها

    نجرب وضع كلمة ما مثلا test وسنجد أنه تم إظهار رسالة Toast  ERROR

     
    وللخروج من وضع البحث نقوم باستدعاء الميثود setOnCloseListener وداخل onClose
    نقوم بتعريف نفس Adapter وإعطاءه studentList التي يوجد داخلها كافة البيانات ثم setAdapter

     
    تعديل البيانات على Firebase
    الآن نذهب الى RecyclerView Row ونضيف 2Buttons واحد للتعديل والآخر للحذف
     

     
    ثم نذهب الى StudentAdapter وعند الضغط على زر editStudent سنقوم بإنشاء Intent الى أكتفتيEditStudentActivity.class الذي سنقوم بإنشاءه بعد قليل
    وأرسلنا name (اسم الطالب الذي تم الضغط عليه) عبر الintent وأخيراً شغلنا الأكتفتي

     
    الآن نقوم بإنشاء EditStudentActivity.class ونضع به 2EditText واحد لتعديل الإسم والآخر للمعدل ونضع 1Button لحفظ القيم الى Firebase

     
    بالطبع نقوم بتعريف String name القادم عبر intent ونعيد تعريف FirebaseDatabase و Reference و EditTexts

     
    بعدها نقوم بتنفيذ Query على name القادم عبر intent ,وداخل onDataChange نقوم بعمل for كما فعلنا سابقاً وأخيراً قمنا بعمل setText ل EditText
    (تم استخدام String.ValueOf لأن getAverage تعيد int ويجب تحويلها ل String لوضعها في EditText)
    وبهذا نكون قد وضعنا اسم الطالب ومعدله فيEditTexts
     

     
    ولحفظ القيم بعد تعديلها ,عند الضغط على زر Save نقوم بإنشاء Student جديد ونضع الإسم name قيمة editText الأول ,ووضعنا المعدل average قيمة EditText الثاني وقمنا بتحويله الى Integer
    ثم نفذنا Query جديدة وقمنا بعمل for على dataSnapshot
    ثم snapshot.getRef() هذه الميثود تعيد لنا key الموجود في الداتابيز (على سبيل المثال -Kgzq-ktAGSpAjJRqk2i) وجميع childs الموجودة داخله
    ثم استدعينا setValue وقمنا بإعطاءه student الذي أنشأناه,وبهذا يكون قد تم التعديل
     

     
     
    عملية الحذف من Firebase
    نطبق نفس الخطوات التي طبقناها في عملية التعديل  ولكن هذه المرة بدل من setValue نقوم بعملremoveValue() للحذف
     

     
     
    المشروع على Github
    ملاحظة:المشروع على Github للمعاينة فقط ولايمكنك تجربته على Android Studio لعدم وجود google-services.json الخاص بك
    مستوى المقال: محترف
  5. السلام عليكم ورحمة الله 
    تكلمت في مقالة سابقة عن عمل الـ BoradcastReceiver بشكل مبسط جدً و كيف يتم إنشاء Static broadcastReceiver 
    تستطيع العودة اليها من هنأ . 
    في هذه المقالة سأتكلم عن الـ boradcastReceiver بشي من التفصيل .... 
    🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹
     
    نبدأ بسم الله :  
    لو القينا نظرة عن محتوى التطبيق - مكونات التطبيق - في الأندوريد نجد أنه يكون بهذي الصورة 
     

     
    فتجد إن الـ BroadcastReceiver جزء من محتوى التطبيق - Application component -  نحن الأن بصدد شرح المكون الثالث وهو ال BroadcastReceiver  
    مفهوم الـــ BroadcastReceiver بشكل مبسط هــو عبارة عن ارسال أو استقبال حدث - event -  من النظام الى التطبيق او من التطبيق الى تطبيقات أخرى ..  

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

     
    🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹🌹
     
    ينقسم ال BroadcastReceiver الى أربع أنواع وهــي : 

     
    والأكثر استخداماً هي النوعين ال Normal  و  Ordered 
    الأن تريد تسجيل حدث - مستقبل - لتطبيقك ، مثل ما ذكرت في المقالة السابقة يمكنك إنشاء حدث جديد بطريقتين : 
    الطريقة الأولى وهي الـ statically تكلمت عنها في المقالة السابقة تسطتيع الرجوع إليها من هنأ .  الطريقة الثانية : هي الـ dynamic يقصد بها تسيجل الحدث برمجيا - programmatically -  عن طريق كتابة كود برمجي ، فعند عمل التطبيق يتم تنفيذ أو تسجيل هذا الحدث و عند الخروج من التطبيق لا ينفذ .   
    من مميزات هذه الطريقة : 
    يتم التنفيذ وقت الأستدعاء .  بعض ال - actions - لا يمكن تنفيذها الا بهذه الطريقة مثل Time_Tick  إذا كان الـ minSdkVersion 26 - اي اصدار الاندوريد 7 و ما فوق يجب استخدام هذه الطريقة إذا أردت عمل ConnectivityManager    
    ☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕☕
     
    تعتمد طريقة الـ dynamic  أو - Context.registerReceiver على دالتين أساسية وهي : 
    registerReceiver() unregisterReceiver()  
    #/ دالة الــ registerReceiver  : 
    تأخذ هذي الدالة بارامترين : 
    أوبجكت من كلاس الــ BroadcastReceiver . أوبجكت من كلاس الـ IntentFilter و بداخله يتم تمرير الـ action . مثلاً : 

     
    و دالة الــ unregisterReceiver يمرر لها الاوبجكت من كلاس الــ broadcastReceiver .
     
    #/ السؤال المهم  أين يتم كتابة هذي الدالتين ، registerReceiver و unregisterReceiver  ؟ 
    الأفضل أن تُكتب دالة الــ registerReceiver في الــ onResume  و  تكتب دالة الـ unregisterReceiver في الـ onPause .  ويمكنك أيضاً كتابة دالة الــ registerReceiver في الـ onCreate و  يجب كتابة الـ unregisterReceiver في الـ onDestory . ولكن  تجنب كتابة دالة الــ unregisterReceiver في الـ onSaveInstanceState  لأنه عند الرجوع الى الـ Activity لا يتم تنفيذها .   
    نأخذ مثال عملي عند التغير الى وضع الطيران :
    java Code 

     
    Kotlin Code 

     
     

     
    لاحظ أن عند الخروج من التطبيق لا يتم تنفيذ الـ receiver 
    ما تم عمله التالــي : 
    إنشاء اوبجكت - object - من كلاس الـ BroadcastReceiver .  في داخل دالة الــ onReceive  ، تظهر رسالة بدخول في وضع الطيران أو الخروج منه .  في دالة الــ onResume  ، انشائنا أوبجكت من كلاس الــ IntentFilter و تم تمرير الـ Action الى هذا الاوبجكت وهــو Intent.Action_AIRPLANE_MODE_CHANGED . <<  بالمناسبة تستطيع مشاهدة   جميع الــ actions الموجودة في النظام من ملف الــ broadcast_action.txt الموجود في المسار التالي : 
    << SDK-> platform->android-${api level} -> data -> broadcast_action.txt 
     
     تم تسجيل الــ receiver من خلال دالة الـ registerReceiver وكذلك مررنا لها الـ receiver و ايضا الـ mIntent   أخيرأ في الـ onPause كتابنا دالة إلغاء المستقبل - إن صح التعبير - و مررنا لها الـ receiver .  
    ⚠️ ملاحظة مهمة جداً : 
    يجب كتابة دالة الــ unregisterReceiver  في حال لم تقم بكتابتها سيظهر خطأ الـ Leaked Intent  فيحدث أغلاق مفاجى للتطبيق أو  يبدأ التطبيق في استهلاك البطارية . 
     
    🏋️  تمرين :
    حاول تطبيق الكود السابق بطريقة الـ  statically BoradcastReceiver - تم شرحها هنأ - هل يوجد فرق بين الطريقتين ؟ أترك لنا تعليق يوضح الفرق ؟ 
     
    والسلام خير ختام . 
    مستوى المقال: متوسط
  6.  أهلا وسهلاً بكم في لمحة مبسطة عن الــ 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 . 
     
    دمتم بخير . 
     
    مستوى المقال: مبتدئ
  7. بسم الله الرحمن الرحيم
     
    في إستمرارنا في الحديث عن أهم وأحدث مكتبات منصة الأندرويد سنتحدث اليوم عن واحدة من أشهر المكاتب المستخدمة مؤخراً في بعض التطبيقات المشهورة ومن ضمنها تطبيق 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); }  
    والنتيجة ..

     
    إلى هنا نكون قد وصلنا إلى نهاية الشرح الخاص بالمكتبة. وسنقوم في المواضيع القادمة بإستعراض المزيد من المكتبات الخاصة بمنصة الأندرويد بإذن الله.
    مستوى المقال: مبتدئ
  8. بسم الله الرحمن الرحيم
     
    إستكمالاً لسلسلة مكتبات الأندرويد سنستعرض اليوم مكتبة من أهم المكتبات التي يستخدمها المطورون بشكل أساسي في تطوير التطبيقات على منصة الأندرويد. هذه المكتبة هي مكتبة 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 سنرى بأن كل شي يعمل بشكل صحيح

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

     
    وسنواصل الحديث بإذن الله تعالى في المواضيع اللاحقة عن بعض المكتبات الأخرى.
    مستوى المقال: مبتدئ
  10.  
    بسم الله الرحمن الرحيم
    كثير من التطبيقات تحتاج إلى تخزين في البيانات اندرويد, كمذكرة, أو أسماء وأرقام أو تواريخ وغيرها, في اندرويد يوجد عدد من الخيارات لتخزين البيانات
    كـ 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
    مستوى المقال: محترف
  11. أبو معتز مطور تطبيقات أندرويد وهو الآن في  منتصف بناء تطبيق أندرويد، وصل للميزة ص، برمجها واختبرها وتأكد من أنها تؤدي الغرض المراد منها مبدئياً، ولا مشاكل ظاهرة حتى اللحظة، وأثناء تلك اللحظة -وهو يرتشف الشاي الأحمر مع مسحوق أوراق النعناع الخضراء المجففة-  تبادر إلى ذهنه السؤال التالي: لماذا أداء ومظهر هذه الميزة لا يشبه مثيلاتها في تطبيقات المبرمجين الآخرين المُحملة على جوالي؟
    ملحوظة جانبية: هذه المقالة تتكون من جزئين: نظري وعملي.
    الجزء النظري:
    إذا مررت بالحالة السابقة فهذه المقالة -إن شاء الله- دليلك لاختيار المكتبة (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
    مستوى المقال: متوسط
  12. بسم الله الرحمن الرحيم
    مؤخرا, في 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
    مستوى المقال: محترف

عالم البرمجة

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