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

    في هذه المقالة سنتعلم كيفية قراءة و استخراج بيانات الـ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

    و هنا وصلنا الى نهاية هذا الدرس.😄
    أتمنى أني قد وفقت بايصال المعلومة.
    مستوى المقال: مبتدئ
  4. بسم الله الرحمن الرحيم 
    أطلقت 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
    مستوى المقال: مبتدئ
  5. السلام عليكم و رحمة الله و بركاته.
    في هذه المقالة سنتعلم كيفية تحميل الـ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 المحملة.
    مستوى المقال: مبتدئ
  6. الـ 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 من احد اهم المفاهيم في برمجة الاندرويد والتي يجب عليك معرفتها ومعرفة انواعها وطريقة التعامل معها, فكل نوع لديه صلاحياته الخاصة وكل نوع يجب عليك استخدامه في مكانه المناسب والوقت المناسب.
    مستوى المقال: متوسط
  7. السلام عليكم و رحمة الله وبركاته.
     
    في هذه المقالة سنتعلم كيفية التعامل مع 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.
    مستوى المقال: متوسط
  8. بسم الله الرحمن الرحيم
    سنتحدث اليوم بإذن الله عن الـ Geofence, وهو من المواضيع الشيقة في التطوير لأندرويد, جميل جدا, لنبدأ.
    لنفترض جدلا أن لدينا محل تجاري يقدم عروض خاصة عن طريق التطبيق الخاص به, فقط لزبائنه الذين هم جلوس داخل المحل أو قريبين منه بمسافة 50 م, عن طريق إظهار notification مكتوب فيها خصم 10%, وعند الضغط على notification تتجه لصحفة لا يمكن الوصول إليها إلا عن طريق هذا notification, سيكون هذا المثال الذي نطبق عليه.
    بداية ما هو الـ Geofence, هو عبارة عن API يتيح لك معرفة ما إذا كان المستخدم قريب من أماكن تود أنت كمبرمج أن تقدم له تنبيه أو Action معين.
    قبل البدأ سنقوم بتجهير ملفي gradle و manifest.
    بالنسبة لملف gradle سنحتاج لأن نضيف google play services لتطبيق, عن طريق إضافة  هذا السطر في dependencies - هذه المكتبة بأحدث إصدار وقت كتابة هذه المقالة -
    compile 'com.google.android.gms:play-services:10.0.1' أما في ملف manifest سنضيف رقم النسخة كـ meta-data, ورقم النسخة يضاف مباشرة في string.xml بعد عمل sync إذا تم تحديث gradle
     
    <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> ولا ننسى أن نطلب صلاحية الوصول لـ Location
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> الاّن التطبيق جاهز من ناحية المكتبات لنبدأ في كتابة الكود
    سيكون لدينا intent service تقوم بإضافة المواقع التي نرغب أن ننبه المستخدم إذا كان بمقربة منها, و service أخرى لإظهار notification
    سنبدأ بـ intent service ونسميها AddLocationService, أولا نقوم بتعريفها في ملف manifest
    <service android:name=".AddLocationService" /> نبدأ بعمل implements لثلاث Interfaces وهي
    ConnectionCallbacks  وهي تطلب منك عمل override لـ onConnected
    OnConnectionFailedListener وتطلب من عمل override لـ onConnectionSuspended و onConnectionFailed
    ResultCallback<T> وتطلب من عمل override لـ onResult والتي تخبرك عن حالة الطلب الخاص بك من حيث النجاح أو الفشل
    ثم نحتاج لـ object من نوع GoogleApiClient حيث سيكون الاتصال عن طريقه ويقوم بشكل ذاتي بإدارة الإتصال, ونسند له API الذي نريد استخدامه ألا وهو LocationServices
    لنلقي نظرة على شكل الكود حتى هذه اللحظة
    public class AddLocationService extends IntentService implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, ResultCallback<Status>{ GoogleApiClient mGoogleApiClient; /** * Creates an IntentService. Invoked by your subclass's constructor. * * String AddLocationService important only for debugging. */ public AddLocationService() { super("AddLocationService"); } @Override protected void onHandleIntent(@Nullable Intent intent) { mGoogleApiClient = new GoogleApiClient.Builder(this) .addOnConnectionFailedListener(this) .addConnectionCallbacks(this) .addApi(LocationServices.API) .build(); mGoogleApiClient.connect(); } @Override public void onConnected(@Nullable Bundle bundle) {} @Override public void onConnectionSuspended(int i) {} @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {} @Override public void onResult(@NonNull Status status) {} جميل جدا, في حالة كان هناك مشكلة في الاتصال أو تعطل الاتصال لفترة بسيطة سنضع log في onConnectionSuspended, onConnectionFailed كنوع من debug لمعرفة نوع الخطأ .
    أما في حالة onConnected سنبدأ في إنشاء geofence الخاص بنا كالتالي
    @Override public void onConnected(@Nullable Bundle bundle) { try { LocationServices.GeofencingApi.addGeofences( mGoogleApiClient, // The GeofenceRequest object. getGeofencingRequest(), getGeofencePendingIntent() ).setResultCallback(this); // Result processed in onResult(). } catch (SecurityException securityException) { // Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission. Log.i(getClass().getSimpleName(),securityException.getMessage()); } } كما تلاحظ استدعينا دالة addGeofences التي نبني فيها Geofence الخاص بنا, وتستقبل ثلاث متغيرات وهي google api client وهو الذي قمنا ببناءه سلفاً, وقلنا أنه مسؤول عن إدارة الاتصال, و geofencingRequest سنتحدث عنها بعد قليل, وPendingIntent سنتحدث عنها لاحقا في هذه المقالة هي الأخرى أيضا.
    فيكون شكل الدالة كالتالي
    PendingResult<Status> addGeofences(GoogleApiClient var1, GeofencingRequest var2, PendingIntent var3)
    وسيرجع لنا status التي ستبين لنا حالة الـ request من حيث النجاح أو الفشل.
    نلاحظ أن لدينا two function وهي getGeofencingRequest و getGeofencePendingIntent
    لنبدأ بطريقة عمل GeofencingRequest وهي المسؤلة عن إضافة geofence list التي نريد متابعتها وإظهار التنبيه بشكل مناسب, وهي كالتالي
    private GeofencingRequest getGeofencingRequest() { GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_DWELL); builder.addGeofences(getGeofecne()); return builder.build(); } طبعا هو يستخدم builder pattern ونلاحظ أنه يطلب setInitialTrigger و addGeofences
    لدينا ثلاث أنوع من Triggers
    INITIAL_TRIGGER_DWELL : نستخدمه عندما يكون المستخدم داخل المنطقة التي حددناها لبعض الوقت, ويكون لدينا GEOFENCE_TRANSITION_DWELL.
    INITIAL_TRIGGER_ENTER : نستخدمه عندما يكون المستخدم داخل المنطقة التي حددناها, ويكون لدينا GEOFENCE_TRANSITION_ENTER
    INITIAL_TRIGGER_EXIT : نستخدمه عندما يكون المستخدم خارج المنطقة التي حددناها, ويكون لدينا GEOFENCE_TRANSITION_EXIT
    جميل جداً تعرفنا على أنواع Triggers التي قد نضعها في setInitialTrigger, وفي حالتنا سنستخدم INITIAL_TRIGGER_DWELL و INITIAL_TRIGGER_ENTER
    في الحقيقة نحن نريد GEOFENCE_TRANSITION_DWELL فقط لكن لاستخدامه تكون دائما مضطر لاستخدام INITIAL_TRIGGER_ENTER معه, لانه يمكننا من معرفه لحظة دخول الشخص إلى المنطقة المحدده والبدء بحساب الوقت الذي نقدره, قبل إظهار التنبيه.
    بالنسبة لـ addGeofences فهي list of geofence وهو من أهم الجزئيات في geofencing
    getGeofecne تبنى كالتالي
    private List<Geofence> getGeofecne(){ List<Geofence> mGeofenceList = new ArrayList<>(); //add one object mGeofenceList.add(new Geofence.Builder() // Set the request ID of the geofence. This is a string to identify this // geofence. .setRequestId("key") // Set the circular region of this geofence. .setCircularRegion( 25.768466, //lat 47.567625, //long 50) // radios // Set the expiration duration of the geofence. This geofence gets automatically // removed after this period of time. //1000 millis * 60 sec * 5 min .setExpirationDuration(1000 * 60 * 5) // Set the transition types of interest. Alerts are only generated for these // transition. We track entry and exit transitions in this sample. .setTransitionTypes( Geofence.GEOFENCE_TRANSITION_DWELL) //Time before fire notification .setLoiteringDelay(3000) // Create the geofence. .build()); return mGeofenceList; } قمنا بإنشاء list of geofence وأضفنا Object واحد ولإنشاء object ستحتاج إلى عدة أمور
    Id نعرف به geofence ويكون من نوع string
    تحديد المحيط الخاص بالمنطقة عن طريق ثلاث أمور setCircularRegion( LATITUDE, LONGITUDE, RADIUS)
     تحديد الوقت الذي بعده يتم حذف geofence من list والوقت يحدد بالجزء من الثانية  في setExpirationDuration(1000 * 60 * 5) حالة وضعنا NEVER_EXPIRE سيتم إشعار اليوزر في كل مرة حسب نوع الحالة التي نضعها, عند كل دخول, أو عند بقائه فترة محدد, أو عند خروجه.
    تحديد متى يتم تنبيه المستخدم, حال الدخول, أو الخروج أو عندما يكون داخل المنطقة لمدة من الزمن عن طريق setTransitionTypes() وتستخدم أحد هذه الثوابت أو جميعها
    GEOFENCE_TRANSITION_ENTER تحدثنا عنها في getGeofencingRequest أنها تستخدم مع INITIAL_TRIGGER_ENTER
    GEOFENCE_TRANSITION_DWELL فتستخدم مع INITIAL_TRIGGER_ENTER و INITIAL_TRIGGER_DWELL
    GEOFENCE_TRANSITION_EXIT تستخدم مع INITIAL_TRIGGER_EXIT
    لذلك يجب التنبه لوضع trigger في request مع transition الصحيح في geofecne.
    أضفت هنا object واحد لتبسيط المثال لكن بإمكانك إضافة الكثير وبطريقة dynmic أيضا
    الاّن أكلمنا GeofenceRequest
    لننتقل لـ PendingIntent
    private PendingIntent getGeofencePendingIntent() { // Reuse the PendingIntent if we already have it. if (mGeofencePendingIntent != null) { return mGeofencePendingIntent; } Intent intent = new Intent(this, GeofenceTransitionsIntentService.class); return PendingIntent.getService(this, 0, intent, PendingIntent. FLAG_UPDATE_CURRENT); } قمنا ببناء pending intent بسيطة جدا الغرض منها إخراج notification للمستخدم, عن طريق استدعاء GeofenceTransitionsIntentService class وهو class الثاني الذي تحدثنا عنه في بداية المقالة
    جميل جدا
    public class GeofenceTransitionsIntentService extends IntentService { protected static final String TAG = "GeofenceTransitionsIS"; /** * This constructor is required, and calls the super IntentService(String) * constructor with the name for a worker thread. */ public GeofenceTransitionsIntentService() { // Use the TAG to name the worker thread. super(TAG); } /** * Handles incoming intents. * @param intent sent by Location Services. This Intent is provided to Location * Services (inside a PendingIntent) when addGeofences() is called. */ @Override protected void onHandleIntent(Intent intent) { } الشكل الأولي لـ service, سنضيف logic الخاص بنا في onHandleIntnet, وهو قراءة البيانات القادمة في intent عن طريق دالة GeofencingEvent.fromIntent(intent) وسنحفظها في geofencingEvent
    ثم نتأكد من أنها سليمة عن طريق code التالي
    if (geofencingEvent.hasError()) { Log.e(TAG, getErrorString(geofencingEvent.getErrorCode())); return; } public String getErrorString(int errorCode) { switch (errorCode) { case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE: return "not Available"; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES: return "Too many Geofences"; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS: return "Too many Pending Intents"; default: return "unknown geofence error"; } } لكل خطأ في geofence رقم معين وفي حال حدوثه سيظهر هنا في هذا log, فأحد الأخطاء مثلا, هو أن تضيف أكثر من 100 geofecne في geofence list, فسيظهر لك خطأ GEOFENCE_TOO_MANY_GEOFENCES, وبقية الأخطاء على نفس النسق
    بعد ذلك نتعرف على نوع Transition القادم والذي تم اسناده سابقا عن طريق دالة setTransitionTypes
    وعلى ضوءه نحدد notification التي نخرجها فيكون شكل class النهائي, كالتالي
    public class GeofenceTransitionsIntentService extends IntentService { protected static final String TAG = "GeofenceTransitionsIS"; /** * This constructor is required, and calls the super IntentService(String) * constructor with the name for a worker thread. */ public GeofenceTransitionsIntentService() { // Use the TAG to name the worker thread. super(TAG); } /** * Handles incoming intents. * @param intent sent by Location Services. This Intent is provided to Location * Services (inside a PendingIntent) when addGeofences() is called. */ @Override protected void onHandleIntent(Intent intent) { GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); if (geofencingEvent.hasError()) { Log.e(TAG, getErrorString(geofencingEvent.getErrorCode())); return; } // Get the transition type. int geofenceTransition = geofencingEvent.getGeofenceTransition(); // Test that the reported transition was of interest. if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER || geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) { // Get the transition details as a String. String geofenceTransitionDetails = "Discount 10% for you"; // Send notification and log the transition details. sendNotification(geofenceTransitionDetails); Log.i(TAG, geofenceTransitionDetails); } else { // Log the error. Log.e(TAG, getString(R.string.geofence_transition_invalid_type + geofenceTransition)); } } public String getErrorString(int errorCode) { switch (errorCode) { case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE: return "not Available"; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES: return "Too many Geofences"; case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS: return "Too many Pending Intents"; default: return "unknown geofence error"; } } /** * Posts a notification in the notification bar when a transition is detected. * If the user clicks the notification, control goes to the MainActivity. */ private void sendNotification(String notificationDetails) { // Create an explicit content Intent that starts the main Activity. Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class); // Construct a task stack. TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); // Add the main Activity to the task stack as the parent. stackBuilder.addParentStack(MainActivity.class); // Push the content Intent onto the stack. stackBuilder.addNextIntent(notificationIntent); // Get a PendingIntent containing the entire back stack. PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); // Get a notification builder that's compatible with platform versions >= 4 NotificationCompat.Builder builder = new NotificationCompat.Builder(this); // Define the notification settings. builder.setSmallIcon(R.drawable.common_google_signin_btn_icon_dark_normal) // In a real app, you may want to use a library like Volley // to decode the Bitmap. .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.cast_abc_scrubber_primary_mtrl_alpha)) .setColor(Color.RED) .setContentTitle(notificationDetails) .setContentText(getString(R.string.geofence_transition_notification_text)) .setContentIntent(notificationPendingIntent); // Dismiss notification once the user touches it. builder.setAutoCancel(true); // Get an instance of the Notification manager NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); // Issue the notification mNotificationManager.notify(0, builder.build()); } } وقد أضفت فيه جزئية إظهار notifiction ولن أتطرق لها بالشرح في هذه المقالة لكيلا تطول فيمل القارئ, في الـ Main Activity أضفت سطر واحد يشغل service
    @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); startService(new Intent(this,AddLocationService.class)); } طيب بكذا يكون تطبيقنا أكتمل وقابل لتجربة لكن بعطيك معلومة سريعة بما إننا استخدمنا IntentService, فهي راح تستقبل الأوامر عن طريق onHandleIntent حتى تنهيها وبعد كذا راح تسوي stop وبعدها destroy, طبعا قريب حلول كثيرة أشهرها استخدام broadcast receiver, اختبر نفسك وحاول تطبق geofence معاه, ورفعت هنا على github الكود الخاص بالمثال هذا مع  broadcast receiver.
     
    تأكد من أن تكون قد قمت بإعطاء التطبيق الصلاحيات المطلوبة قبل عمل اختبار لتطبيق, عن طريق الإعدادات -> التطبيقات -> الصلاحيات
     
    هذا وصلى الله وسلم وبارك
    المراجع:
    https://developer.android.com/training/location/geofencing.html
    https://code.tutsplus.com/tutorials/how-to-work-with-geofences-on-android--cms-26639
      Android programming big nerd ranch guide 
    Android Application Development Cookbook
    واستفدت كثيرا من الأسئلة المطروحة في stack overflow
    مستوى المقال: مبتدئ
  9. السلام عليكم و رحمة الله و بركاته.

    في هذه المقالة سنتعلم كيفية التعامل مع RSS Feed عن طريق تحميلها و قرائتها بالإضافة الى استخراجها في الـAndroid  Studio.
    في البداية، ما هي RSS ؟

    خدمة RSS هي خدمة لمتابعة آخر الأخبار بشكل مباشر وبدون الحاجة إلى زيارة الموقع ، ستقدم لك خدمة RSS  الكثير من المعلومات منها: عنوان الخبر ، ومختصر لنص الخبر ، ووصلة أو رابط لنص الخبر الكامل على الموقع ، و صورة عن الخبر، بالإضافة إلى عدد التعليقات الموجودة ... الخ.
    و الآن ننتقل الى التطبيق في الـ Android Studio...

    في البداية نقوم بفتح Android Studio ثم نقوم بالضغط على Start a new Android Studio project و نبقي الخيارات الافتراضية على حالها و نقوم بتسميته و حفظه.
     
    و الآن حتى يتم تحميل XML file  يجب علينا:
    انشاء thread منفصل و لحسن الحظ يتوفر على الـ Android Studio كلاس يسمى بالـ AsyncTask يقوم باغلب المهام. نقوم بانشاء Inner class  في MainActivity.java و الذي بدوره يقوم بـ AsyncTask class" extends"، ثم نضيف الmethods وهي onPostExecute ،doInBackground كما هو موضح ادناه:  
    public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } private class DownloadData extends AsyncTask<String, Void, String> { private static final String TAG = "DownloadData"; @Override protected void onPostExecute(String s) { super.onPostExecute(s); } @Override protected String doInBackground(String... params) { return null; }  
    الآن نقوم بانشاء method في داخل DownloadData كلاس ونسميها على سبيل المثال downloadXML و التي بدورها سوف تقوم بتحميل ملف XML  كاملا حيث سيتم استدعاء هذه الـmethod في doInBackround
    @Override protected String doInBackground(String... params) { String rssFeed = downloadXML(params[0]); if (rssFeed == null) { Log.e(TAG, "doInBackground: Error downloading"); } return rssFeed; } private String downloadXML(String urlPath) { StringBuilder XMLResult = new StringBuilder(); try { URL url = new URL(urlPath); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); int response = connection.getResponseCode(); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); int charRead; char[] inputBuffer = new char[500]; while (true) { charRead = reader.read(inputBuffer); if (charRead < 0) { break; } if (charRead > 0) { XMLResult.append(String.copyValueOf(inputBuffer, 0, charRead)); } } reader.close(); return XMLResult.toString(); } catch (MalformedURLException e) { Log.e(TAG, "downloadXML: Invalid URL " + e.getMessage()); } catch (IOException e) { Log.e(TAG, "downloadXML: IO Exception reading Data: " + e.getMessage()); } catch (SecurityException e) { Log.e(TAG, "downloadXML: Permesion denied. " + e.getMessage()); } return null; }  
    كم هو موضح أعلاه الـ downloadXML تتلقى parameter و الذي هو عبارة عن URL لملف XML. 
    ملاحظة: سوف يتم استخدام RSS Feed  الموجودة في apple.com  و المتعلقة Top10FreeApps. و لقد قمنا بعمل التالي:
    انشاء StringBuilder object حتى يتم الحاق كامل ملف XML بهذا object. انشاء الإتصال بالإنترنت. قراءة كامل ملف XML  عن طريق BufferReader و يمكن أن تتساءل ما الفائدة من المتغير response؟ عن طريقه يمكن معرفة نجاح الاتصال بالانترنت او فشله. حيث انه اذا ظهر لك المتغير response  بقيمة 200 هذا يعني نجاح الاتصال، غير ذلك يعني فشله. قراءة عدد الأحرف الموجودة في ملف XML كما هو موضح أعلاه و إلحاقها بالمتغير XMLResult  حيث أن الـloop سوف تنتهى عند انتهاء ملف XML. أحطنا الـ code بـ try and catch حتى نتعامل مع الأخطاء المحتملة منها: فقدان الاتصال بالانترنت، او خطأ في عنوان الرابط.. الخ. بالنهاية اذا حصل خطأ نقوم بـreturn null.  
    قبل أن نبدأ باستخراج المعلومات أود أن نلقي نظرة عن ماهية ملف XML المتعلق ب Top10FreeApp. باستخدام موقع code beautify يمكننا أن نرى التكوين الداخلي لملف XML.

    كما نلاحظ أعلاه، ال Entry TAG يحتوي على  name TAG ,summary TAG , artist TAG و التي تمثل بعضا من المعلومات الممكن استخراجها.
    ننتقل الآن الى كيفية استخراج المعلومات المطلوبة من ملف XML.
    في البداية يجب علينا عمل التالي:
    انشاء كلاس يحتوي على fields و التي سوف تخزن بها المعلومات المطلوبة من ملف XML. ننشئ كلاس ونسميه FeedEntry و الذي يحتوي على متغيرات: name, Artist, Summary. إضافة getters and setters  لهذه المتغيرات.  
    public class FeedEntry { private String name; private String artist; private String summary; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getArtist() { return artist; } public void setArtist(String artist) { this.artist = artist; } public String getSummary() { return summary; } public void setSummary(String summary) { this.summary = summary; } }  
    ثم ننشئ كلاس آخر و نسميه على سبيل المثال PareseApplication و الذي بدوره سوف يحتوي على method التي تقوم بقراءة و استخراج المعلومات المطلوبة من XML file  و تخزينها في array  تحتوي على objects  من الكلاس FeedEntry.
    public class ParseApplications { private ArrayList<FeedEntry> applications; public ParseApplications() { this.applications = new ArrayList<>(); } public ArrayList<FeedEntry> getApplications() { return applications; } public boolean parse(String xmlData) { boolean status = true; FeedEntry currentRecord = null; boolean inEntry = false; String textValue = ""; try { XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser(); xpp.setInput(new StringReader(xmlData)); int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { String tagName = xpp.getName(); switch (eventType) { case XmlPullParser.START_TAG: if ("entry".equalsIgnoreCase(tagName)) { inEntry = true; currentRecord = new FeedEntry(); } break; case XmlPullParser.TEXT: textValue = xpp.getText(); break; case XmlPullParser.END_TAG: if (inEntry) { if ("entry".equalsIgnoreCase(tagName)) { applications.add(currentRecord); inEntry = false; } else if ("name".equalsIgnoreCase(tagName)) { currentRecord.setName(textValue); } else if ("artist".equalsIgnoreCase(tagName)) { currentRecord.setArtist(textValue); }else if ("summary".equalsIgnoreCase(tagName)) { currentRecord.setSummary(textValue); } } break; default: //Nothing else to do. } eventType = xpp.next(); } } catch (Exception e) { status = false; e.printStackTrace(); } return status; } }  
    لقد قمنا بعمل التالي: 
    اضافة متغير  applications من نوع ArrayList و الذي سوف يتم تخزين فيه objects من نوع FeedEntry. اضافة Constructor  لانشاء object من هذا الكلاس و احتوائه على Getter للوصول الى المتغير applications. انشاء method  و نسميها parse  و التي بدورها سوف تستخرج المعلومات المطلوبة من ملف XML المحمل كما هو موضح أعلاه. هذه الـmethod  تحتوي على اربع متغيرات:
    متغير Status من نوع boolean و الذي يدل على نجاح الـmethod  او فشلها. و متغير currentRecord  من نوع FeedEntry و الذي سوف يتم تخزين المعلومات المطلوبة فيه. و متغير InEntry  من نوع boolean الذي سوف يعطينا إشارة بوصولنا الى Entry TAG. بالإضافة الى متغير textValue من نوع String الذي سوف يقوم بتخزين النص الموجود في الـTAG.  
    ملاحظة: لحسن الحظ  Android Studio توفر لنا كلاس اسمه XmlPullParserFactory  و الذي بدوره سوف يقوم بكثير من مهام معالجة ملف XML. هذا الكلاس يعتمد على قيم ثوابت(Constatns) معرفة في الكلاس و التي تسمى بالـevents. بعض الأمثلة عليها: END_DOCUMENT, START_TAG, TEXT , END_TAG.  
    انشاء اوبجكت من XmlPullParserFactory ثم نمرر إليه محتويات الـXML  كـparameter. نقوم باستخراج المعلومات المطلوبة من ملف XML و تخزينها في currentRecord ثم اضافة هذا object الى applications. احاطة الكود بـ try and catch لتلافي الأخطاء التي من الممكن وقوعها أثناء استخراج المعلومات. و بالنهاية لا ننسى الخطوة المهمة و التي لا يكتمل التطبيق إلا بها و هي أخذ الإذن للإتصال بالإنترنت. حيث تتم هذه العملية بإضافة السطر البرمجي الموضح أدناه في ملف AndroidManifest.xml المتواجد في مجلد Manifests.  
    <uses-permission android:name="android.permission.INTERNET"/>  
    و هنا وصلنا الى نهاية هذا الدرس.😄
    في الدروس القادمة سوف أتناول كيفية عرض المعلومات المستخرجة باستخدام ListViews.
    مستوى المقال: مبتدئ
  10. قامت شركة قوقل باطلاق انسخة ٧.١ من نظام نوقا ولم يكن التحديث لاصلاح المشاكل وانما جلب معه بعض المميزات الجديدة ولعل من ابرزها هو اختصارات التطبيق  App Shortcuts وسنقوم باذن الله في هذا الدرس بشرح هذه الميزه وطريقة استخدامها.
    ماهي App Shortcuts ومتى نستخدمها ؟!
    ببساطة تقوم باضافة قائمة على التطبيق تسمح للمستخدم بعرضها دون الدخول الى التطبيق وهي مشابهه لما يوجد في نظام ios  . ويمكن استخدامها لعرض مهمات او خصائص للمستخدم تسهل الوصول لها بسرعه وسلاسة !،
    تحتوي على نوعين :
    static : تضاف في ملفات الريسورس للتطبيق وتكون ثابته ولايمكن تغييرها الا بنشر التطبيق مره اخرى
    dynamic : نقوم باضافة في الوقت الفعلي ويمكن تحديثها دون الحاجة الى نشر التطبيق مره اخرى
    اضافة App Shortcuts :
    اضافة اختصارات التطبيق تكون بشكل سهل وسنبدأ باضافة القائمة الثابته والتي سنعرفها من ملفات resources .

     
    STATIC SHORTCUTS
    ساقوم بافتراض ان لديك مشروع في تطبيق اندرويد ستديو واحتاج منك الذهاب الى ملف AndroidManifest.xml وستقوم باضافة meta-data الموجود في الكود في الاسفل :
    <meta-data android:name="android.app.shortcuts" android:resource="@xml/shortcuts" /> تلاحظ في تاق meta-data يوجد android:resource وهذا المفتاح من يتعامل مع resource في التطبيق والذي عرف في res/xml/shourtscuts.xml هنا يمكنك تعريف اختصاراتك الثابته للتطبيق.
    الان سنقوم باضافة اختصار يفتح لنا مثلاً StaticShourtcutActivity كما سنلاحظ في الكود التالي :
    <?xml version="1.0" encoding="utf-8"?> <shortcuts xmlns:android="http://schemas.android.com/apk/res/android"> <shortcut android:enabled="true" android:icon="@drawable/ic_home_black_24dp" android:shortcutDisabledMessage="@string/static_shortcut_disabled_message" android:shortcutId="static" android:shortcutLongLabel="@string/static_shortcut_long_label" android:shortcutShortLabel="@string/static_shortcut_short_label"> <intent android:action="android.intent.action.VIEW" android:targetClass="com.example.ahmed.appshoutcut.StaticActivity" android:targetPackage="com.example.ahmed.appshoutcut" /> </shortcut> </shortcuts>  
    تلاحظ ان root للكود هو shortcuts والذي سيقوم بالتعامل مع اكثر من shourtcut في التطبيق :
    enabled هنا تقوم بتفعيل او الغاء تفعيل الاختصار
    icon الايقونة الخاصة بالاختصار
    shortcutDisabledMessage 
    تظهر رسالة في حال قام المستخدم بالضغط على اختصار غير مفعل
    shortcutLongLabel عنوان طويل يظهر في حال كان الانشر يستطيع عرضها “التابلت مثلاً”
    shortcutShortLabel هنا هو النص الذي سيظهر للمستخدم كعنوان للاختصار
    intent هنا المهمه او الامر الذي سيظهر في حال المستخدم اختار الاختصار

    DYNAMIC SHORTCUTS
    الان سنقوم بالتعامل مع النوع الثاني من الاختصارات وهي “المتغيره” والتي تتحدث مباشرة دون الحاةه الى نشر التطبيق مره اخرى وعلى عكس الاختصارات الثابته لن نحتاج الى اضافتها في xml او resource التطبيق ولكن سنقوم بكتابتها في كود Java .
    الان سنقوم باضافة اول اختصار متغير وسنتعامل مع ShortcutManager و ShortcutInfo.Builder وسنقوم ببناء اول اختصار متغير في الشاشة الرئيسية MainActivity.onCreate#
    ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); ShortcutInfo webShortcut = new ShortcutInfo.Builder(this, "shortcut_web") .setShortLabel("a7med.name") .setLongLabel("Open a7med.name web site") .setIcon(Icon.createWithResource(this, R.drawable.ic_web_black_24dp)) .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://a7med.name"))) .build(); shortcutManager.setDynamicShortcuts(Collections.singletonList(webShortcut)); الان عند تشغيل التطبيق سنلاحظ اضافة الاختصار الثاني الخاص بنا .
    هناك الكثير عن App Shortcuts سأتكلم عنه في تدوينات لاحقة باذن الله
    شكرا لكم جميعاً وعذراً على الانقطاع
    مستوى المقال: متوسط
  11. بسم الله الرحمن الرحيم
    نكمل ما بدأناه بالحديث عن أكثر  Design pattern استخداما في بيئة Android واليوم نتم الحديث عن Creational patterns بعد تكلمنا عن Singleton و dependency injection
    اليوم سكون حديثنا عن Builder Design patterns
    نقتبس التعريف من كتاب GoF
    جميل جداً كما ذكر في التعريف إذ أننا سنقوم بتقسيم بناء Object إلى عدة method نبدء بالأساسية التي لا يمكن التعامل مع Object بدونها, ثم ننتقل إلى الاختيارية حتى نتم بناء Object على الوجه الذي نريد.
    لنبدأ بمثال يوضح المشكلة .
    لنفترض أننا نريد إنشاء class لفريق معين كالتالي :
    public class Player { private String name ; private String team ; private double height ; private int salary ; private String phone ; private String twitterAccount ; public Player(String name, String team, double height, int salary, String phone, String twitterAccount) { this.name = name; this.team = team; this.height = height; this.salary = salary; this.phone = phone; this.twitterAccount = twitterAccount; } } نحن هنا نتعامل مع six class data member فقط فلو فرضنا أن لدينا 10 فسيكون شكل constructor رهيب جدا وفي هذا عدة مشاكل منها
    أنك لن تستطيع تذكر أماكن data member في constructor مما يسبب في وضع البيانات في غير مكانها وستحصل على نتائج غريبة قد لا يصنفها compiler ك Error ولتلافي هذه المشكلة
    سنستعرض أحد الحلول وهو Telescopin Design Pattern حيث سنقوم بكتابة شكل constructor فقط
    public Player(String name, String team) { this(name, team,0.0); } public Player(String name, String team, double height) { this(name, team,height,0); } public Player(String name, String team, double height, int salary) { this(name, team,height,salary,""); } public Player(String name, String team, double height, int salary, String phone) { this(name, team,height,salary,phone,""); } public Player(String name, String team, double height, int salary, String phone, String twitterAccount) { this.name = name; this.team = team; this.height = height; this.salary = salary; this.phone = phone; this.twitterAccount = twitterAccount; } كما تلاحظ نبدأ بالمعلومات الأساسية التي لا يمكن الاستمرار بدونها ثم بعد ذلك نبدأ بتوفير البيانات في حال كانت لدينا أو نسند قيم افتراضية.
    ولهذه الطريقة عيوبها أولا صعوبة قراءة code وأيضا صعوبة في كتابة client code (إنشاء object ), بالإضافة إلى أنك قد تجبر على كتابة parameters التي لا تريد كتابتها.
    لهذه قد يكون استخدام Telescopin ليس الحل المناسب, لكن يوجد حل أخر لنأخذ فرصة في تجربته ألا وهو javaBeans Pattern , لنطبقه على نفس المثال السابق.
    public Player() {} public void setName(String name) { this.name = name; } public void setTeam(String team) { this.team = team; } public void setHeight(double height) { this.height = height; } public void setSalary(int salary) { this.salary = salary; } public void setPhone(String phone) { this.phone = phone; } public void setTwitterAccount(String twitterAccount) { this.twitterAccount = twitterAccount; } فكما ترى نبدأ ب constructor وبعد ذلك setters بعدد data members , وهذا الحل يحل بعض الإشكالات السابقة ويخلق إشكالات جديدة, فأولا سيكون إنشاء Object مرتبط بعدد كبير من method التي يجب استدعاؤها وتذكرها 
    بالإضافة إلى أنه لا يمكنك جعل class immutable, وليس thread safe, فتجازونا بعض إشكالات Telescopin ووقعنا في إشكالات أخرى ,فسنجمع بينهم في Patterm وهو Builder Design Pattern.
    لنعيد كتابة constructor بإستخدام نفس المثال
    public class Player { private String name ; private String team ; private double height ; private int salary ; private String phone ; private String twitterAccount ; public static class Builder { private final String name ; private final String team ; private double height = 0; private int salary = 0; private String phone = ""; private String twitterAccount = ""; public Builder(String name, String team) { this.name = name; this.team = team; } public Builder height(double height) { this.height = height; return this ; } public Builder salary(int salary) { this.salary = salary; return this ; } public Builder phone(String phone) { this.phone = phone; return this ; } public Builder twitterAccount(String twitterAccount) { this.twitterAccount = twitterAccount; return this ; } public Player build(){ return new Player(this); } } public Player(Builder builder) { name = builder.name; team = builder.team; height = builder.height; salary = builder.salary;; phone = builder.phone;; twitterAccount = builder.twitterAccount; } } في البداية قمنا ببناء inner class  سميناه Builder حيث سنقوم بداخله ببناء Object الخاص بنا وبعد ذلك نرجعه عن طريق دالة build التي تقوم بعمل return ل object الذي قمنا ببناءه, نبدأ بالمتغيرات الأساسية في Object وهي التي كانت final ولا تأخذ قيم افتراضية, ثم الاختيارية التي قد نستخدمها فنستدعي method الخاصة بها, أو لا نستخدمها فنسند فيه القيم الافتراضية, فيكون شكل client كالتالي
    Player player = new Player.Builder("Aziz", "Android") .height(10.2) .phone("0555555555") .salary(100) .build(); كما نلاحظ قمنا بكتابة client سهل القراءة وحتى الكتابة, ومرن وبسيط جدا.
    اذا ف Builder Pattern خيار جيد جداً حينما يكون عدد Parameters ليس بالقليل ويكون فيه عدد من Parameters اختيارية
     
    المراجع ومصادر :
    Effective Java (2nd Edition)
    https://sourcemaking.com/design_patterns/builder
    http://www.blackwasp.co.uk/gofpatterns.aspx
    مستوى المقال: محترف
  12. بسم الله الرحمن الرحيم
    نكمل ما بدأنا به في شرح design pattern الأكثر استخداما في بيئة Android
    اليوم بإذن الله سيكون حديثنا عن مفهوم Dependency Injection.
    تعريف wikipedia
    فما هو Inversion of Control ؟ التعريف من wikipedia  أيضا
    قد تكون عملية الترجمة غير مجدية أو أن التعريفات ليست واضحة عموما بإذن الله سنفصل الموضوع تفصيلا بسيطاً يجعله واضحا بإذن الله.
    ببساطة مفهوما Dependency Injection و Inversion of Control (IoC) بدون الخوض في التفاصيل تعنى بحذف dependencies من code
    ماذا نعني ب dependencies (الاعتمادية) ؟
    خذ هذا المثال
    public class MSWord { private ErrorCorrection corrector; public MSWord() { this.corrector = new ErrorCorrection(); } } نلاحظ أنه لكي أقوم بإنشاء Object من class MSWrod فأنا مضطر لإنشاء Object من  class ErrorCorrection ونقطة الاعتمادية هنا هي إنشاء Object
    لكي تقوم بتنفيذ Dependency Injection لابد أن تعمل Inject (حقن) object بحيث يأتي إلى class ك parameter  مبني, فتتم عملية الإسناد فقط
    بالمثال يتضح المقال, فهذه مثال ننفذ فيه Dependency Injection على نفس code السابق
    public class MSWord { private ErrorCorrection corrector; public MSWord(ErrorCorrection corrector) { this.corrector = corrector; } } ففي Main أو مكان تنفيذ code ستقوم بالتالي
    public static void main(String[] args) { ErrorCorrection corrector = new ErrorCorrection() MSWord word = new MSWord(corrector); } جميل جدا, الأمر نفسه بتمرير dependencies ك parameter سواء كان في constructor أو method, يأتي السؤال الأهم لماذا أستخدم هذا pattern ؟
    لنفترض أن لدينا class A ولديه subclass B ويحتاج object من class C
    public class C { String s ; C(String s){ this.s = s ; } } public class A { private C c ; A(String s){ this.c = new C(s); } } public class B extends A{ B(String s){ super(s); } } جميل, في حال تم تغيير constructor في class C فأنت مضطر لتغيير constructor في class A وجميع من يرث منه, فلو قلنا فرضاً أننا قررنا إضافة Strimg أخر لدى C
    public class C { String s ,s1; C(String s ,String s1 ){ this.s = s ; this.s1 = s1 ; } } public class A { private C c ; A(String s,String s1){ this.c = new C(s,s1); } } public class B extends A{ B(String s,String s1){ super(s,s1); } } فكما ترى اضطررنا لتغيير جميع classes نظرا لأننا غيرنا في class يعتمدون عليه, لكن ماذا لو طبقنا Dependency Injection على هؤلاء classes
    public class C { String s ; C(String s){ this.s = s ; } } public class A { private C c ; A(C c){ this.c = c ; } } public class B extends A{ B(C c){ super(c); } } لنغير في C و constructor الخاص بها ونرى ماذا يحدث.
    public class C { String s ,s1; C(String s,String s1){ this.s = s ; this.s1 = s1 ; } } public class A { private C c ; A(C c){ this.c = c ; } } public class B extends A{ B(C c){ super(c); } } نلاحظ الفائدة Dependency Injection حيث أن التغيير تم في مكان واحد فقط.
    من فوائد Dependency Injection
    له فوائد جدا كثيرة ستلاحظها أثناء الكتابة أو حين تقرأ عنه ومنها
    - قابلية التعديل - maintainability
    - قابلية الاختبار -  testability
    - قابلية الاستفادة من نفس code عوضاً عن إعادة كتابته - reusability
    - بالإضافة إلى أنه يقلل من coupling في code
     
    للاستزادة هنا بعض المراجع التي قرأت منها وفيها الكثير من المعلومات التي فضلت عدم إيرادها لجعل الموضوع أكثر بساطة
    https://en.wikipedia.org/wiki/Dependency_injection
    http://frogermcs.github.io/dependency-injection-with-dagger-2-introdution-to-di/
    http://stackoverflow.com/questions/3058/what-is-inversion-of-control
    مستوى المقال: محترف

عالم البرمجة

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