انشاء نمط الـ Loader
بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته
في الدرس السابق تعرفنا على نمط الـ AsyncTask. في هذا الدرس سنتعرف على نمط الـ Loader وطريقة استخدامه.
تنبيه قبل استخدام هذا النمط!
- تم الاستغناء عنه في الاصدار API 28 لذلك ينصح باستخدام المكتبة Support Library ويكون بهذا الشكل مدعوماً كحل مؤقت, كما سنستخدمه في هذا الدرس (اذا لم تقم بإستخدام الـ Support Library فتطبيقاتك لن تعمل في الاصدارات الحديثه من نظام التشغيل اندرويد).
- من الافضل عدم استخدامه والاتجاه الى استخدام الـ ViewModel والـ LiveData ومكوناتها كما جاء في شروحات لها بدورة سابقه قمت بكتابتها بعنوان Android Architecture Components.
لقد ترددت كثيراً في شرحه ام لا في هذه الدورة! ولكن مادفعني لوضع درس له هو: ان الـ Loader الى الان يستخدم بكثرة بين المبرمجين وربما تقوم Google بتعديله وتحسينه في المستقبل كما قامت بتحسين نمط الـ AsyncTask ولم تتخلى عنه. فشرحي هنا قائم على استخدام مكتبة الـ Support Library حتى لايواجه احد اي مشاكل عند استخدامه مستقبلاً.
استخدام الـ Loader افضل بكثير من استخدام الـ AsyncTask لعدة اسباب منها:
- ان الـ Loader لايعمل الا مره احده (بعكس الـ AsyncTask تعمل اكثر من مره عندما يقوم المستخدم بقلب الشاشه او بغلق وفتح التطبيق اثناء عملها وهذا يسبب مشاكل كثيرة وثقل في التطبيق).
- يستطيع التعامل مع عملية تدوير الشاشة بشكل تلقائي.
- يستطيع التعامل مع التغييرات في التطبيق الـ Life Cycle بشكل تلقائي.
- يعمل Cache للبيانات.
- في حالة استخدامه لتحميل الاشياء من الانترنت فهو يتأكد من تحميل الشئ فقط مرة واحدة.
في ماذا نستخدم الـ Loader
اكثر استخدامته هي في التعامل مع تحميل البيانات من الشبكة العنكبوتيه كالـ API وايضاً التعامل مع قواعد البيانات وكذلك بعض العمليات التي تتطلب جهد ووقت كالتعامل مع الـ Bitmap (حالياً فضل استخدام نمط الـ Thread Pools او نمط الـ AsyncTask للمهام البسيطه كما جاء في الدروس السابقة).
مكونات نمط الـ Loader
كائن LoaderManager
نحتاج هذا العنصر لإدارة والتخاطب مع الـ Loaders في الـ Activity او اي مكون اخر.
دالة onCreateLoader()
في هذه الدالة نقوم بانشاء الـ Loader الخاص بنا (نحتاج اولاً الى انشاء كلاس خاصه به). هذه الداله مناسبة لتحديد نوع البيانات التي نريدها مثلاً في حالة استخدام الـ Loader لجلب البيانات من API نستطيع تحديد الـ Queue و Parameter هنا. وكمثال اخر عند استخدام الـ Loader من نوع Cursor لإستخراج بيانات من قاعدة البيانات الـ SQL Lite نقوم بتحديد الـ Queue و projection وماشبه ذلك (من الافضل استخدام قاعدة البيانات الـ Room دورة كاملة عنها في الرابط: Android Architecture Components.
دالة onLoadFinished()
عندما ينهي الـ Loader عمله يقوم بجلب النتائج الى هذه الدالة, مناسبه لعملية ملئ البيانات في Adapter او List.
دالة onLoaderReset()
تعمل هذه الدالة عند تشغيل الـ Loader مره اخرى, اي ان التشغيل القديم تم الانتهاء منه, مناسبه لعملية التنظيف وما شابه ذلك.
تجهيز بعض الكلاسات قبل انشاء نمط الـ Loader
انشاء كلاس Model للعناصر
في هذا المثال نريد استقبال حقائق مضحكة عن الممثل الشهير Chuck Norris من خلال API chucknorris.
والتي تكون صيغة الرد على شكل JSON كالتالي:
{
"icon_url" : "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
"id" : "YIJeclypScWCOV8qS96DKA",
"url" : "https://api.chucknorris.io/jokes/YIJeclypScWCOV8qS96DKA"
"value" : "If Chuck Norris wanted you dead, he'd kick you so hard that the force causes your body to accelerate past light speed, tear open the fabric of space time, and your corpse go back in time and kill your past self, thus erasing your existence from the entire universe."
}
اذا ستكون الكلاس الخاصة بالـ Model كالتالي:
public class Facts {
String mIconUrl;
String mId;
String mURL;
String mValue;
public Facts(String iconUrl, String id, String URL, String value) {
mIconUrl = iconUrl;
mId = id;
mURL = URL;
mValue = value;
}
public String getIconUrl() {
return mIconUrl;
}
public void setIconUrl(String iconUrl) {
mIconUrl = iconUrl;
}
public String getId() {
return mId;
}
public void setId(String id) {
mId = id;
}
public String getURL() {
return mURL;
}
public void setURL(String URL) {
mURL = URL;
}
public String getValue() {
return mValue;
}
public void setValue(String value) {
mValue = value;
}
}
بما ان الـ API يحتاج الاتصال الى الشبكة العنكبوتيه لابد من وضع تصاريح الولوج في ملف الـ AndroidManifest كالتالي:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
خطوات انشاء الـ Loader (إنشاء كلاس الـ AsyncTaskLoader)
الان سنقوم بإنشاء نمط الـ Loader.
يجب الانتباه الى الـ import كالتالي:
import android.support.v4.content.AsyncTaskLoader;
حتى يكون مدعوماً بإستخدام الـ Support Library.
انشاء كلاس خاصة من نوع AsyncTaskLoader
نقوم بأنشاء كلاس جديده ثم نعمل extends لكلاس AsyncTaskLoader (مبنيه على AsyncTask ولمعرفة الفرق تفضل بقرائة هذه المقالة AsyncTask and AsyncTaskLoader) ونجعل النوع هو Facts من نوع كلاس الموديل السابقة التي أنشئناها على النحو التالي:
public class CustomAsyncTaskLoader extends AsyncTaskLoader<Facts> {}
نقوم بعمل Override للدوال الضروريه:
@Override
protected void onStartLoading() {
forceLoad();
}
@Nullable
@Override
public Facts loadInBackground() {
}
الدالة loadInBackground هي التي ستقوم بتشغيل الشفرة البرمجية في خيط اخر بعيداً عن الخيط الحاسوبي الرئيسي.
والان نقوم بكتابة الكلاس كاملة مع الدوال التي ستتعامل مع الاتصال بالانترنت وتحميل البيانات من الـ API لتصبح الكلاس بالشكل التالي:
public class CustomAsyncTaskLoader extends AsyncTaskLoader<Facts> {
private static final String TAG = "CustomAsyncTaskLoader"; // logt
String mUrl;
public CustomAsyncTaskLoader(@NonNull Context context, String url) {
super(context);
mUrl = url;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Nullable
@Override
public Facts loadInBackground() {
if (mUrl == null) {
return null;
}
URL url = createUrl(mUrl);
String jsonResponse = makeHttpRequest(url);
Facts fact = extractFromJson(jsonResponse);
return fact;
}
private URL createUrl(String stringUrl) {
URL url = null;
try {
url = new URL(stringUrl);
} catch (MalformedURLException exception) {
Log.e(TAG, "Problem building the URL ", exception);
return null;
}
return url;
}
private String makeHttpRequest(URL url) {
String jsonResponse = "";
HttpURLConnection urlConnection = null;
InputStream inputStream = null;
try {
// setting the connection, then connect
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setReadTimeout(10000 /* milliseconds */);
urlConnection.setConnectTimeout(15000 /* milliseconds */);
urlConnection.connect();
// checking and getting the data
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
inputStream = urlConnection.getInputStream();
jsonResponse = readFromStream(inputStream);
} else {
Log.d(TAG, "makeHttpRequest Error response code: " + urlConnection.getResponseCode());
}
} catch (IOException e) {
Log.d(TAG, "makeHttpRequest: " + e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return jsonResponse;
}
public static String readFromStream(InputStream inputStream) throws IOException {
StringBuilder output = new StringBuilder();
if (inputStream != null) {
// InputStreamReader convert all data to 0 and 1
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
// BufferReader convert all data to what we want (text)
BufferedReader reader = new BufferedReader(inputStreamReader);
String line = reader.readLine();
while (line != null) {
output.append(line);
line = reader.readLine();
}
}
return output.toString();
}
private Facts extractFromJson(String jsonResponse) {
Facts fact = null;
String iconUrl;
String id;
String url;
String value;
/*
https://api.chucknorris.io/jokes/random
{
"category": null,
"icon_url": "https:\/\/assets.chucknorris.host\/img\/avatar\/chuck-norris.png",
"id": "Qjo6JWN2Q0KV2HuSTxiFzg",
"url": "https:\/\/api.chucknorris.io\/jokes\/Qjo6JWN2Q0KV2HuSTxiFzg",
"value": "FACT: You are having a birthday because Chuck Norris decided to let you live another year!"
}
*/
try {
JSONObject baseJsonResponse = new JSONObject(jsonResponse);
iconUrl = baseJsonResponse.getString("icon_url");
id = baseJsonResponse.getString("id");
url = baseJsonResponse.getString("url");
value = baseJsonResponse.getString("value");
fact = new Facts(iconUrl, id, url, value);
} catch (JSONException e) {
e.printStackTrace();
}
return fact;
}
}
الـ Constructor:
- فيه نحدد البيانات التي نريد استقبالها من الـ Activity الى كلاس الـ Loader هذه, حتى نقوم بإستخدامهم هنا.
الدوال الاساسية:
- onStartLoading.
- loadInBackground: هنا نضع الشفرة البرمجية المراد تشغيلها في خيط حاسوبي مختلف عن الخيط الرئيسي.
الدوال المساعدة:
- createUrl: دالة نستخدمها لإنشاء رابط للإتصال الى الـ API.
- makeHttpRequest: دالة نستخدمها لإجراء الاتصال وتحميل البيانات من الـ API من خلال الرابط التي ستقوم بعمله الدالة السابقه.
- readFromStream: دالة نستخدمها لتحويل البيانات بعد تحميلها من الدالة السابقة الى بيانات نصية.
- extractFromJson: دالة نقوم بإستخدامها لتحويل البيانات النصية من الدالة السابقة الى عناصر لكلاس الموديل Fact.
الا يوجد طريقة اسهل للتعامل مع الـ API بدلاً من كتابة هذا الكم من الشفرة البرمجية
نعم يوجد مكاتب تختصر هذه الاشياء لك في عدة اسطر قليله جداً من اشهرها مكتبة Retrofit وOkHttp وابسطها في رايي هي Volley.
طريقة استخدام نمط الـ Loader في الـ Activity
يجب الانتباه الى الـ import كالتالي:
import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader;
حتى يكون مدعوماً بإستخدام الـ Support Library.
عمل imlements للواجهه LoaderManager.LoaderCallbacks:
public class MainChuckNorris extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Facts> {
٫٫٫
لاحظ قمنا بتحديد نوعة بإستخدام الـ <> والنوع هو الـ Model لدينا.
عمل حقل للـ Loader:
private static final int CHUCK_NORRIS_API_LOADER_ID = 22;
تستطيع تسميته وقيمته بإي شئ. اذا كنت تريد استخدام اثنين من الـ Loader فعليك بإنشاء حقل لكل واحد منهما.
عمل Override للدوال onCreateLoader و onLoadFinished و onLoaderReset:
@NonNull
@Override
public Loader onCreateLoader(int i, @Nullable Bundle bundle) {
return new CustomAsyncTaskLoader(this, bundle.getString("API_URL"));
}
@Override
public void onLoadFinished(@NonNull Loader<Facts> loader, Facts facts) {
mTextView.setText(facts.getValue());
}
@Override
public void onLoaderReset(@NonNull Loader loader) {
}
الدوال:
- onCreateLoader: تعمل هذه الدالة عندما نقوم بتشغيل الـ Loader, نقوم فيها بإنشاء الـ Loader الخاص بنا من الكلاس التي قمنا بعملها سابقاً (الـ CustomAsyncTaskLoader), وتمرير له البيانات. (مناسبة لتحديد نوع البيانات التي نريدها مثلاً في حالة استخدام الـ Loader لجلب البيانات من API نستطيع تحديد الـ Queue و Parameter هنا وذلك بإستخدام الـ Uri Builder يوجد بعض الامثله في اجابات السؤال التالي: Use URI builder in Android or create URL with variables).
- onLoadFinished: عندما ينهي الـ Loader عمله يقوم بجلب النتائج الى هذه الدالة, مناسبه لعملية ملئ البيانات في Adapter او List.
- onLoaderReset: تعمل هذه الدالة عند تشغيل الـ Loader مره اخرى,اي ان التشغيل القديم تم الاستغناء عنه, مناسبه لعملية التنظيف وما شابه ذلك حتى لايصبح لديك بيانات مكرره.
طريقة تشغيل الـ Loader في الـ Activity
من الافضل انشاء دالة بإسم initLoader ووضع الشفرة البرمجية الخاصة لتشغيل الـ Loader ومناداتها من اي مكان سواء زر او طريقة السحب للتحديث او في الـ onCreate وماشبهه ذلك.
الدالة هي:
private void initLoader(){
Bundle bundle = new Bundle();
bundle.putString("API_URL", "https://api.chucknorris.io/jokes/random");
android.support.v4.app.LoaderManager.getInstance(this).initLoader(
CHUCK_NORRIS_API_LOADER_ID,
bundle,
this).forceLoad();
}
- قمنا هنا بإستخدام bundle ووضعنا به البيانات المراد ارسالها.
- قما بإستخدام الـ Loader Manager بطريقه يكون مدعوما من قبل الـ Support Library (تستطيع قرائة اجابتي في هذا السؤال: What is the appropriate replacer of deprecated getSupportLoaderManager?, للإسف لم اجد غير هذه الطريقة الغريبه).
- استخدمنا دالة الـ initLoader لتشغيل الـ Loader وذلك من خلال اعطائها الـ ID الخاص بالـ Loader المراد تشغيلة, والـ Bundle الذي يحتوي على البيانات المراد ارسالها للـ Loader واخيراً قمنا بإستخدام forceLoad حتى نجبره على العمل.
في هذا الدرس رأينا طريقة استخدام نمط الـ Loader وبرغم انه تم التخلي عنه ولكننا نستطيع استخدامه بواسطة الـ Support Library.
المصادر والمراجع
للمزيد راجع درس المقدمة.
نهاية الدرس
لاتنسى تتبع الدرس والدورة كذلك لإشعارك عندما يتم التعديل على المتحوى او اضافة المزيد من المعلومات. ايضاً لاتنسى الاعجاب بالدرس ومشاركته مع الاخرين.
محتوى الدورة
الكلمات الدليلية
عن الدرس
0 إعجاب |
1 متابع |
0 مشاركة |
3153 مشاهدات |
منذ 5 سنوات |
التعليقات (0)
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !