3zcs

Members
  • عدد منشوراتي

    12
  • تاريخ الإنضمام

  • تاريخ اخر زياره

السمعه بالموقع

12 Good

عن العضو 3zcs

  • الرتبه
    مبدع مثابر

اخر الزوار

1093 زياره للملف الشخصي
  1. بسم الله الرحمن الرحيم عندما نريد إنشاء instance من class معين فإننا غالبا ما نقوم بإستخدام public constructor, لكن هنا طريقة أخرى, يجب أن يكون المبرمج ملماً بها, ألا وهي public static factory method. ببساطة شديدة هي دالة static تقوم بإرجاع -return- لـ instance من class, على سبيل المثال: public class Client { public static Client getInstance() { return new Client(); } } كما نلاحظ أن المفهوم في غاية البساطة, لكن متى أحتاج أن أستخدمه ؟ , سنقوم الان بالمقارنة بينه وبين Constructor, ونستعرض المميزات والعيوب. أولا, تتميز static factory method بكونها قابلة لتسمية, فعلى سبيل المثال لو كان لدينا مركز رياضي ونوعين من العملاء, مشترك وزائر, المشترك ندخل أسمه, أما الزائر فنكتب فقط زائر, فسيكون شكل class كالتالي public class Client { private String name; public static Client getVisitorInstance() { Client client = new Client(); client.name = "visitor"; return client; } public static Client getSubscribedInstance(String name) { Client client = new Client(); client.name = name; return client; } } دعنا نقوم بإستدعائها, واستدعاء constructor. Client client1 = Client.getVisitorInstance(); Client client2 = Client.getSubscribedInstance("Abdulaziz"); Client client3 = new Client(); Client client4 = new Client("Abdulziz"); كما نلاحظ, في static factory method من النظرة الأولى, تمكنت من معرفة نوع العميل, بالإضافة إلى أنه لو أراد شخص ما إكمال تطوير المشروع, سيتمكن من التعامل مع class الذي قمت ببنائه دون النظر فيه -Readability-, على عكس public constructor. ثانيا, من خلال static factory method يمكنك أن تجبر مستخدم الـ class على إنشاء instance واحد فقطلهذا الـ class, وهو ما نسميه بـ singleton, كما في المثال public class Client { private static final Client client = new Client(); private Client(){ } public static Client getClient() { return client; } } كما نلاحظ أن constructor private, بمعنى أنه لا يمكنك إنشاء instance عن طريقه, لكن تستخدم static factory method والتي دائما ستعيد لك نفس object الذي هو client, وبهذا لن تحصل إلا على object واحد فقط عن طريق هذا class. ثالثا, يمكن لـ static factory method أن يقوم بعمل return لـ instance من sub-type لـ class, على سبيل المثال لدينا class يقوم بإزالة الزوائد من القيم الداخلة إليه, مثلا لو تلقى قيمة 0010 يقوم بحذف الأصفار, ولو تلقى “محمد ” يقوم بحذف space وهكذا, لنبدأ في الكود. بداية سنقوم بإنشاء Interface بإسم trimmer public interface Trimmer<T> { T trim(); } الان, سنقوم بإنشاء class لتعامل مع String public class StringTrim implements Trimmer<String> { String value; StringTrim(String value) { this.value = value; } @Override public String trim() {...} } وأخر لتعامل مع Integer public class IntegerTrim implements Trimmer<Integer> { Integer value ; IntegerTrim(Integer value) { this.value = value; } @Override public Integer trim() {...} } نلاحظ أن access modifier لـ constructor من نوع package-private أي لا يمكن الوصول إليه من خارج package, ثم نقوم بإنشاء class نستخدمه إذا ما أردنا الإستفادة من classes السابقة public class Trimmers { private Trimmers(){} public static Trimmer<String> getTrimString(String s){ return new StringTrim(s); } public static Trimmer<Integer> getTrimInteger(Integer i){ return new IntegerTrim(i); } } نلاحظ هنا كيف استفدنا من static factory method, بحث أنه قام بإرجاع object, عبارة عن sub-type لـ return type. وفي main نستدعيه بالطريقة التالية: Integer i = Trimmers.getTrimInteger(0150).trim(); String s = Trimmers.getTrimString(" abdulaziz").trim(); وهذا الأسلوب يسمى interface-based API أو interface-based frameworks بعد استعراضات المميزات لـ static factory method, لنستعرض السلبيات, وهما اثنتين. الأولى, لا يمكن الوراثة -inheritance- من class لا يوجد فيه public or protected constructor, ففي هذه الحالة لا يمكن لـ static factory method أن تعوض constructor. الثانية, أن static factory method قد لا يمكن تمييزها بسهولة عن بقية static methods التي بداخل class, لذلك تستخدم عادة بعض التسميات المتعارف عليها, لتحل هذه المشكلة. الان, لنمثل علىstatic factory method في Android, فلو أردنا أن نقوم بإنشاء activity داخلها fragment سيكون الكود كالتالي public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = new BlankFragment(); manager.beginTransaction().add(R.id.fragment,fragment).commit(); } } } وفي Activity أخرى public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_secound); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = new BlankFragment(); manager.beginTransaction().add(R.id.fragment,fragment).commit(); } } } لنقوم الان بتحسين هذا الكود, سنقوم بالتالي, أولا بناء static factory method داخل fragment public static BlankFragment newInstance() { BlankFragment fragment = new BlankFragment(); return fragment; } ثم نقوم ببناء BaseActivity كالتالي public abstract class BaseActivity extends AppCompatActivity { protected abstract Fragment createFragment(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = createFragment(); manager.beginTransaction() .add(R.id.fragment,fragment) .commit(); } } } لاحظ هنا أن هناك دالة ستعيد لنا fragment سنستخدمها داخل oncreate, وستظهر فائدة هذا الشيء عندما نجعل MainActivityb و SecoundActivity ترث منها, فيكون شكل الكود كالتالي public class SecondActivity extends BaseActivity { @Override protected Fragment createFragment() { return BlankFragment.newInstance(); } } public class MainActivity extends BaseActivity { @Override protected Fragment createFragment() { return BlankFragment.newInstance(); } } إلى هنا أتمنى أن أكون وفقت في تبسيط هذا المفهوم, والسلام عليكم ورحمة الله وبركاته.المصادر: Big nerd ranch Android Effective java Static factory methods vs traditional constructors
  2. بسم الله الرحمن الرحيم عندما نريد إنشاء instance من class معين فإننا غالبا ما نقوم بإستخدام public constructor, لكن هنا طريقة أخرى, يجب أن يكون المبرمج ملماً بها, ألا وهي public static factory method. ببساطة شديدة هي دالة static تقوم بإرجاع -return- لـ instance من class, على سبيل المثال: public class Client { public static Client getInstance() { return new Client(); } } كما نلاحظ أن المفهوم في غاية البساطة, لكن متى أحتاج أن أستخدمه ؟ , سنقوم الان بالمقارنة بينه وبين Constructor, ونستعرض المميزات والعيوب. أولا, تتميز static factory method بكونها قابلة لتسمية, فعلى سبيل المثال لو كان لدينا مركز رياضي ونوعين من العملاء, مشترك وزائر, المشترك ندخل أسمه, أما الزائر فنكتب فقط زائر, فسيكون شكل class كالتالي public class Client { private String name; public static Client getVisitorInstance() { Client client = new Client(); client.name = "visitor"; return client; } public static Client getSubscribedInstance(String name) { Client client = new Client(); client.name = name; return client; } } دعنا نقوم بإستدعائها, واستدعاء constructor. Client client1 = Client.getVisitorInstance(); Client client2 = Client.getSubscribedInstance("Abdulaziz"); Client client3 = new Client(); Client client4 = new Client("Abdulziz"); كما نلاحظ, في static factory method من النظرة الأولى, تمكنت من معرفة نوع العميل, بالإضافة إلى أنه لو أراد شخص ما إكمال تطوير المشروع, سيتمكن من التعامل مع class الذي قمت ببنائه دون النظر فيه -Readability-, على عكس public constructor. ثانيا, من خلال static factory method يمكنك أن تجبر مستخدم الـ class على إنشاء instance واحد فقطلهذا الـ class, وهو ما نسميه بـ singleton, كما في المثال public class Client { private static final Client client = new Client(); private Client(){ } public static Client getClient() { return client; } } كما نلاحظ أن constructor private, بمعنى أنه لا يمكنك إنشاء instance عن طريقه, لكن تستخدم static factory method والتي دائما ستعيد لك نفس object الذي هو client, وبهذا لن تحصل إلا على object واحد فقط عن طريق هذا class. ثالثا, يمكن لـ static factory method أن يقوم بعمل return لـ instance من sub-type لـ class, على سبيل المثال لدينا class يقوم بإزالة الزوائد من القيم الداخلة إليه, مثلا لو تلقى قيمة 0010 يقوم بحذف الأصفار, ولو تلقى “محمد ” يقوم بحذف space وهكذا, لنبدأ في الكود. بداية سنقوم بإنشاء Interface بإسم trimmer public interface Trimmer<T> { T trim(); } الان, سنقوم بإنشاء class لتعامل مع String public class StringTrim implements Trimmer<String> { String value; StringTrim(String value) { this.value = value; } @Override public String trim() {...} } وأخر لتعامل مع Integer public class IntegerTrim implements Trimmer<Integer> { Integer value ; IntegerTrim(Integer value) { this.value = value; } @Override public Integer trim() {...} } نلاحظ أن access modifier لـ constructor من نوع package-private أي لا يمكن الوصول إليه من خارج package, ثم نقوم بإنشاء class نستخدمه إذا ما أردنا الإستفادة من classes السابقة public class Trimmers { private Trimmers(){} public static Trimmer<String> getTrimString(String s){ return new StringTrim(s); } public static Trimmer<Integer> getTrimInteger(Integer i){ return new IntegerTrim(i); } } نلاحظ هنا كيف استفدنا من static factory method, بحث أنه قام بإرجاع object, عبارة عن sub-type لـ return type. وفي main نستدعيه بالطريقة التالية: Integer i = Trimmers.getTrimInteger(0150).trim(); String s = Trimmers.getTrimString(" abdulaziz").trim(); وهذا الأسلوب يسمى interface-based API أو interface-based frameworks بعد استعراضات المميزات لـ static factory method, لنستعرض السلبيات, وهما اثنتين. الأولى, لا يمكن الوراثة -inheritance- من class لا يوجد فيه public or protected constructor, ففي هذه الحالة لا يمكن لـ static factory method أن تعوض constructor. الثانية, أن static factory method قد لا يمكن تمييزها بسهولة عن بقية static methods التي بداخل class, لذلك تستخدم عادة بعض التسميات المتعارف عليها, لتحل هذه المشكلة. الان, لنمثل علىstatic factory method في Android, فلو أردنا أن نقوم بإنشاء activity داخلها fragment سيكون الكود كالتالي public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = new BlankFragment(); manager.beginTransaction().add(R.id.fragment,fragment).commit(); } } } وفي Activity أخرى public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_secound); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = new BlankFragment(); manager.beginTransaction().add(R.id.fragment,fragment).commit(); } } } لنقوم الان بتحسين هذا الكود, سنقوم بالتالي, أولا بناء static factory method داخل fragment public static BlankFragment newInstance() { BlankFragment fragment = new BlankFragment(); return fragment; } ثم نقوم ببناء BaseActivity كالتالي public abstract class BaseActivity extends AppCompatActivity { protected abstract Fragment createFragment(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); FragmentManager manager = getSupportFragmentManager(); Fragment fragment = manager.findFragmentById(R.id.fragment); if (fragment == null){ fragment = createFragment(); manager.beginTransaction() .add(R.id.fragment,fragment) .commit(); } } } لاحظ هنا أن هناك دالة ستعيد لنا fragment سنستخدمها داخل oncreate, وستظهر فائدة هذا الشيء عندما نجعل MainActivityb و SecoundActivity ترث منها, فيكون شكل الكود كالتالي public class SecondActivity extends BaseActivity { @Override protected Fragment createFragment() { return BlankFragment.newInstance(); } } public class MainActivity extends BaseActivity { @Override protected Fragment createFragment() { return BlankFragment.newInstance(); } } إلى هنا أتمنى أن أكون وفقت في تبسيط هذا المفهوم, والسلام عليكم ورحمة الله وبركاته.المصادر: Big nerd ranch Android Effective java Static factory methods vs traditional constructors
  3. بسم الله الرحمن الرحيم مؤخرا, في 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
  4. بسم الله الرحمن الرحيم مؤخرا, في 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
  5. بسم الله الرحمن الرحيم أطلقت 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
  6. بسم الله الرحمن الرحيم أطلقت 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
  7. بسم الله الرحمن الرحيم سنتحدث اليوم بإذن الله عن الـ 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
  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. بسم الله الرحمن الرحيم استكمالا للحديث عن أكثر Design pattern استخداما في بيئة Android, وبعد أن كنا قد بدأنا في الحديث عن أنواع Structural pattern, اليوم سيكون حديثنا عن Facade Pattern, فنبدأ بالسؤال المعتاد ماهو Facade Pattern؟ لنبدأ بتعريفه: إذا فهو Pattern يستخدم لتبسيط interfacees لأجزاء النظام, الصورة التالية ستكون شارحة جدا لوظيفة واّلية عمل هذا Pattern. جميل جدا, أعتقد أن المفهوم بات متضحاً جدا, فلندلف إلى تطبيقه بالمثال التالي. لتعلم أنه Pattern بسيط وواضح جدا, فلو فرضنا أن لدينا interface اسمه line, ولديه three claases قاموا بعمل implementation لهذا interface. وهم straight و curve و snaky. public interface Line { void draw(); } class straight public class Straight implements Line { @Override public void draw() { System.out.println("straight"); } } class curve public class Curve implements Line { @Override public void draw() { System.out.println("curve"); } } snaky class public class Snaky implements Line { @Override public void draw() { System.out.println("snaky"); } } أصبح لدينا مجموعة من classes, لو كان النظام الذي سنقوم ببنائه أكبر من ذلك سيكون من الصعب جدا تذكر هذه classes, لذلك سنقوم ببناء Facade class يحل هذه المشكلة ويبسط التعامل معها. public class LineMaker { private Line straight; private Line curve; private Line snaky; public LineMaker() { straight = new Straight(); curve = new Curve(); snaky = new Snaky(); } public void drawStraight(){ straight.draw(); } public void drawCurve(){ curve.draw(); } public void drawSnaky(){ snaky.draw(); } } أصبح التعامل هذه المجموعة من classes غاية في البساطة ويمكن التعامل معها في main كالتالي public static void main(String[] args) { LineMaker lineMaker = new LineMaker(); lineMaker.drawStraight(); lineMaker.drawCurve(); lineMaker.drawSnaky(); } } بينما لو لم نقم باستخدام facade class سيكون شكل main كالتالي: public static void main(String[] args) { private Line straight = new Straight(); private Line curve = new Curve(); private Line snaky = new Snaky(); straight.drawStraight(); curve.drawCurve(); snaky.drawSnaky(); } } طبعا هذا المثال يبسط المشكلة لكن في حال كان حجم النظام كبير ستواجه مشكلة في التعامل مع هذه الكمية من classes. وصلى الله وسلم وبارك على نبينا محمد وعلى اّله وصحبه أجمعين. المراجع: - sourcemaking.com/design_patterns - java design pattern , rohit joshi - head first design pattern - tutorialspoint.com
  10. بسم الله الرحمن الرحيم كما ما بدأناه بالحديث عن أكثر Design pattern استخداما في بيئة Android واليوم سنتحدث عن أحد أنواع Structural pattern , ألا وهو Adapter pattern ,قبل أن نبدأ, وبما أننا في أول pattern في هذه السلسة من نوع Structural فماذا نعني ب Structural pattern ؟ Structural pattern : هو تصميم للكود بطرق تسهل فهم العلاقة بين classes و objects والربط بينها. جميل جدا, إذا ماهو Adapter pattern ؟ من كتاب GoF ولتبسيط الأمور, هذا pattern يستخدم لربط بين two classes لا يمكن الربط بينهما لعدم التوافق. كثيراً ما تكون التعريفات غير مفيدة, لنبدأ بكتابة بعض الأكواد تشرح هذا pattern. بداية دعنا نعرف المشكلة, لو فرضنا أن لديك class قديم قمت ببنائه, ومن ثم اردت الربط بينه وبين class جديد فلن تقوم بإعادة كتابته, وإنما تقوم ببناء interface يقوم بالربط بينهم كما سنقوم بذالك في المثال التالي. لنفترض أن لدينا نظام إدارة فصول إلكتروني, وأردنا ربطه مع نظام حضور وانصراف جديد,فكان شكل interface الخاص بنا كالتالي. public interface Xattendance { public String getStuedntName(); public long getStuedntId() ; public String getTime(); public void setStuedntName(String stuedntName); public void setStuedntId(long stuedntId); public void setTime(String time); } ثم قمنا بعمل implementation له داخل هذا class public class XattendanceImpl implements Xattendance{ String stuedntName; long stuedntId; String time; public String getStuedntName() { return stuedntName; } public void setStuedntName(String stuedntName) { this.stuedntName = stuedntName; } public long getStuedntId() { return stuedntId; } public void setStuedntId(long stuedntId) { this.stuedntId = stuedntId; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } } ولكن وقع الإشكال عندما أردنا ربط هذا class الخاص بنا نظام لتحضير والذي كان شكل interface الخاص به كالتالي: public interface XnewAttendaceSystem { public String getName() ; public void setName(String name); public String getId(); public void setId(String id); public String getMinute(); public void setMinute(String minute); public String getHour(); public void setHour(String hour); } وclass الذي سيقوم بعمل implementation له بهذا الشكل public class XnewAttendaceSystemImpl implements XnewAttendaceSystem{ String name; String id; String minute; String hour; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getMinute() { return minute; } public void setMinute(String minute) { this.minute = minute; } public String getHour() { return hour; } public void setHour(String hour) { this.hour = hour; } } فكما ترى لدينا two classes من نوعين مختلفين, فيلزمنا إنشاء class لنسميه المحول, حيث يمكننا عن طريقه ربط two classes مع بعضهما. وهو يقوم بعمل implementation ل XnewAttendaceSystem وفي constructor يستقبل object من نوع Xattendance ويقوم بالتحويل public class fromOldClassToNewSystem implements XnewAttendaceSystem{ String name; String id; String minute; String hour; final Xattendance xattendance; public fromOldClassToNewSystem(Xattendance xattendance) { this.xattendance = xattendance; adapt(); } @Override public String getName() { return this.name; } @Override public void setName(String name) { this.name = name; } @Override public String getId() { return this.id; } @Override public void setId(String id) { this.id = id; } @Override public String getMinute() { return this.minute; } @Override public void setMinute(String minute) { this.minute = minute; } @Override public String getHour() { return this.hour; } @Override public void setHour(String hour) { this.hour = hour; } public void adapt(){ setName(xattendance.getStuedntName()); setId(xattendance.getStuedntName()); setHour(xattendance.getTime().substring(0, 1)); setMinute(xattendance.getTime().substring(3, 4)); } } فكما ترى تم تحويل object من نوع إلى أخر, فدعنا نلقي نظرة عن طريقة استخدم هذا pattern. سنقوم بإنشاء بيانات نقوم نحن بكتابتها- من الممكن أن تكون البيانات قادمة من API أو من Database, ولكن هنا لتبسيط المقالة ستكون مكتوبة يدوياً- ومن ثم نحولها من النظام الخاص فينا, إلى النظام الذي قمنا بالربط معه. public static void main(String[] args) { Xattendance x = new XattendanceImpl(); x.setStuedntId(10110101); x.setStuedntName("ameen"); x.setTime("12:30"); XnewAttendaceSystem obj = new fromOldClassToNewSystem(x); //test System.out.println(obj.getName()); System.out.println(obj.getHour()); } وبهذا نلاحظ أنه تم إرسال البيانات من نظام إلى أخر باستخدام adapter pattern. ختاما لتعلم أن لدينا نوعان من adapter الأول هو object adapter وهو الذي تعاملنا معه في المثال اّنف الذكر, والذي استخدمنا في بنائه مبدأ composition, والأخر هو adapter class والذي يعتمد في بنائه على multiple inheritance وهو غير مدعوم في java تجنباً لمشكلة DDD (deadly diamond of death), ولكن يمكنك تطبيقها باستخدام لغات تدعم multiple inheritance كلغة C++ ويكون شكل Digram كالتالي: إلى هنا, هذا ما كان في الجعبة ووفق الله الجميع وصلى الله وسلم وبارك المراجع: - sourcemaking.com/design_patterns - java design pattern , rohit joshi - head first design pattern
  11. بسم الله الرحمن الرحيم استكمالا للحديث عن أكثر Design pattern استخداما في بيئة Android, وبعد أن كنا قد بدأنا في الحديث عن أنواع Structural pattern, اليوم سيكون حديثنا عن Facade Pattern, فنبدأ بالسؤال المعتاد ماهو Facade Pattern؟ لنبدأ بتعريفه: إذا فهو Pattern يستخدم لتبسيط interfacees لأجزاء النظام, الصورة التالية ستكون شارحة جدا لوظيفة واّلية عمل هذا Pattern. جميل جدا, أعتقد أن المفهوم بات متضحاً جدا, فلندلف إلى تطبيقه بالمثال التالي. لتعلم أنه Pattern بسيط وواضح جدا, فلو فرضنا أن لدينا interface اسمه line, ولديه three claases قاموا بعمل implementation لهذا interface. وهم straight و curve و snaky. public interface Line { void draw(); } class straight public class Straight implements Line { @Override public void draw() { System.out.println("straight"); } } class curve public class Curve implements Line { @Override public void draw() { System.out.println("curve"); } } snaky class public class Snaky implements Line { @Override public void draw() { System.out.println("snaky"); } } أصبح لدينا مجموعة من classes, لو كان النظام الذي سنقوم ببنائه أكبر من ذلك سيكون من الصعب جدا تذكر هذه classes, لذلك سنقوم ببناء Facade class يحل هذه المشكلة ويبسط التعامل معها. public class LineMaker { private Line straight; private Line curve; private Line snaky; public LineMaker() { straight = new Straight(); curve = new Curve(); snaky = new Snaky(); } public void drawStraight(){ straight.draw(); } public void drawCurve(){ curve.draw(); } public void drawSnaky(){ snaky.draw(); } } أصبح التعامل هذه المجموعة من classes غاية في البساطة ويمكن التعامل معها في main كالتالي public static void main(String[] args) { LineMaker lineMaker = new LineMaker(); lineMaker.drawStraight(); lineMaker.drawCurve(); lineMaker.drawSnaky(); } } بينما لو لم نقم باستخدام facade class سيكون شكل main كالتالي: public static void main(String[] args) { private Line straight = new Straight(); private Line curve = new Curve(); private Line snaky = new Snaky(); straight.drawStraight(); curve.drawCurve(); snaky.drawSnaky(); } } طبعا هذا المثال يبسط المشكلة لكن في حال كان حجم النظام كبير ستواجه مشكلة في التعامل مع هذه الكمية من classes. وصلى الله وسلم وبارك على نبينا محمد وعلى اّله وصحبه أجمعين. المراجع: - sourcemaking.com/design_patterns - java design pattern , rohit joshi - head first design pattern - tutorialspoint.com تم ترقية هذا الطرح المميز الى صفحة المقالات
  12. بسم الله الرحمن الرحيم كما ما بدأناه بالحديث عن أكثر Design pattern استخداما في بيئة Android واليوم سنتحدث عن أحد أنواع Structural pattern , ألا وهو Adapter pattern ,قبل أن نبدأ, وبما أننا في أول pattern في هذه السلسة من نوع Structural فماذا نعني ب Structural pattern ؟ Structural pattern : هو تصميم للكود بطرق تسهل فهم العلاقة بين classes و objects والربط بينها. جميل جدا, إذا ماهو Adapter pattern ؟ من كتاب GoF ولتبسيط الأمور, هذا pattern يستخدم لربط بين two classes لا يمكن الربط بينهما لعدم التوافق. كثيراً ما تكون التعريفات غير مفيدة, لنبدأ بكتابة بعض الأكواد تشرح هذا pattern. بداية دعنا نعرف المشكلة, لو فرضنا أن لديك class قديم قمت ببنائه, ومن ثم اردت الربط بينه وبين class جديد فلن تقوم بإعادة كتابته, وإنما تقوم ببناء interface يقوم بالربط بينهم كما سنقوم بذالك في المثال التالي. لنفترض أن لدينا نظام إدارة فصول إلكتروني, وأردنا ربطه مع نظام حضور وانصراف جديد,فكان شكل interface الخاص بنا كالتالي. public interface Xattendance { public String getStuedntName(); public long getStuedntId() ; public String getTime(); public void setStuedntName(String stuedntName); public void setStuedntId(long stuedntId); public void setTime(String time); } ثم قمنا بعمل implementation له داخل هذا class public class XattendanceImpl implements Xattendance{ String stuedntName; long stuedntId; String time; public String getStuedntName() { return stuedntName; } public void setStuedntName(String stuedntName) { this.stuedntName = stuedntName; } public long getStuedntId() { return stuedntId; } public void setStuedntId(long stuedntId) { this.stuedntId = stuedntId; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } } ولكن وقع الإشكال عندما أردنا ربط هذا class الخاص بنا نظام لتحضير والذي كان شكل interface الخاص به كالتالي: public interface XnewAttendaceSystem { public String getName() ; public void setName(String name); public String getId(); public void setId(String id); public String getMinute(); public void setMinute(String minute); public String getHour(); public void setHour(String hour); } وclass الذي سيقوم بعمل implementation له بهذا الشكل public class XnewAttendaceSystemImpl implements XnewAttendaceSystem{ String name; String id; String minute; String hour; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getMinute() { return minute; } public void setMinute(String minute) { this.minute = minute; } public String getHour() { return hour; } public void setHour(String hour) { this.hour = hour; } } فكما ترى لدينا two classes من نوعين مختلفين, فيلزمنا إنشاء class لنسميه المحول, حيث يمكننا عن طريقه ربط two classes مع بعضهما. وهو يقوم بعمل implementation ل XnewAttendaceSystem وفي constructor يستقبل object من نوع Xattendance ويقوم بالتحويل public class fromOldClassToNewSystem implements XnewAttendaceSystem{ String name; String id; String minute; String hour; final Xattendance xattendance; public fromOldClassToNewSystem(Xattendance xattendance) { this.xattendance = xattendance; adapt(); } @Override public String getName() { return this.name; } @Override public void setName(String name) { this.name = name; } @Override public String getId() { return this.id; } @Override public void setId(String id) { this.id = id; } @Override public String getMinute() { return this.minute; } @Override public void setMinute(String minute) { this.minute = minute; } @Override public String getHour() { return this.hour; } @Override public void setHour(String hour) { this.hour = hour; } public void adapt(){ setName(xattendance.getStuedntName()); setId(xattendance.getStuedntName()); setHour(xattendance.getTime().substring(0, 1)); setMinute(xattendance.getTime().substring(3, 4)); } } فكما ترى تم تحويل object من نوع إلى أخر, فدعنا نلقي نظرة عن طريقة استخدم هذا pattern. سنقوم بإنشاء بيانات نقوم نحن بكتابتها- من الممكن أن تكون البيانات قادمة من API أو من Database, ولكن هنا لتبسيط المقالة ستكون مكتوبة يدوياً- ومن ثم نحولها من النظام الخاص فينا, إلى النظام الذي قمنا بالربط معه. public static void main(String[] args) { Xattendance x = new XattendanceImpl(); x.setStuedntId(10110101); x.setStuedntName("ameen"); x.setTime("12:30"); XnewAttendaceSystem obj = new fromOldClassToNewSystem(x); //test System.out.println(obj.getName()); System.out.println(obj.getHour()); } وبهذا نلاحظ أنه تم إرسال البيانات من نظام إلى أخر باستخدام adapter pattern. ختاما لتعلم أن لدينا نوعان من adapter الأول هو object adapter وهو الذي تعاملنا معه في المثال اّنف الذكر, والذي استخدمنا في بنائه مبدأ composition, والأخر هو adapter class والذي يعتمد في بنائه على multiple inheritance وهو غير مدعوم في java تجنباً لمشكلة DDD (deadly diamond of death), ولكن يمكنك تطبيقها باستخدام لغات تدعم multiple inheritance كلغة C++ ويكون شكل Digram كالتالي: إلى هنا, هذا ما كان في الجعبة ووفق الله الجميع وصلى الله وسلم وبارك المراجع: - sourcemaking.com/design_patterns - java design pattern , rohit joshi - head first design pattern تم ترقية هذا الطرح المميز الى صفحة المقالات
  13. بسم الله الرحمن الرحيم نكمل ما بدأناه بالحديث عن أكثر 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
  14. بسم الله الرحمن الرحيم نكمل ما بدأناه بالحديث عن أكثر Design Batterns استخداما في بيئة Android واليوم نتم الحديث عن Creational patterns بعد تكلمنا عن Singleton و dependency injection اليوم سكون حديثنا عن Builder Design Battern نقتبس التعريف من كتاب 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 تم ترقية هذا الطرح المميز الى صفحة المقالات
  15. بسم الله الرحمن الرحيم نكمل ما بدأنا به في شرح 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

عالم البرمجة

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