
أنا أعمل على تطبيق جديد ، كما يحدث عادةً ، يتواصل مع خدمة الواجهة الخلفية لتلقي البيانات من خلال واجهة برمجة التطبيقات. في هذا المثال ، سوف أقوم بتطوير وظيفة بحث ، ستكون إحدى ميزاتها البحث الفوري أثناء إدخال النص.
بحث فوري
لا شيء معقد ، تعتقد. تحتاج فقط إلى وضع مكون البحث على الصفحة (على الأرجح ، في شريط الأدوات) ، وربط onTextChange
الأحداث onTextChange
وإجراء البحث. إذن هذا ما فعلته:
override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
ولكن هنا تكمن المشكلة. نظرًا لأنني بحاجة إلى تنفيذ عملية بحث مباشرة أثناء الإدخال ، كلما onQueryTextChange()
معالج أحداث onQueryTextChange()
، أتحول إلى واجهة برمجة التطبيقات للحصول على أول مجموعة من النتائج. السجلات هي كما يلي:
D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TEST D/MainActivity: onQueryTextSubmit: TEST
على الرغم من حقيقة أنني أقوم بطباعة طلبي ، هناك خمس مكالمات API ، كل منها يقوم بإجراء بحث. على سبيل المثال ، في السحابة ، تحتاج إلى الدفع مقابل كل مكالمة إلى API. وبالتالي ، عندما أدخل طلبي ، أحتاج إلى القليل من التأخير قبل إرساله ، بحيث يتم إجراء مكالمة API واحدة فقط في النهاية.
الآن ، لنفترض أنني أريد العثور على شيء آخر. أحذف TEST وأدخل أحرف أخرى:
D/MainActivity: onQueryTextChange: TES D/MainActivity: onQueryTextChange: TE D/MainActivity: onQueryTextChange: T D/MainActivity: onQueryTextChange: D/MainActivity: onQueryTextChange: S D/MainActivity: onQueryTextChange: SO D/MainActivity: onQueryTextChange: SOM D/MainActivity: onQueryTextChange: SOME D/MainActivity: onQueryTextChange: SOMET D/MainActivity: onQueryTextChange: SOMETH D/MainActivity: onQueryTextChange: SOMETHI D/MainActivity: onQueryTextChange: SOMETHIN D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING D/MainActivity: onQueryTextChange: SOMETHING E D/MainActivity: onQueryTextChange: SOMETHING EL D/MainActivity: onQueryTextChange: SOMETHING ELS D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextChange: SOMETHING ELSE D/MainActivity: onQueryTextSubmit: SOMETHING ELSE
هناك 20 مكالمات API! وسيؤدي التأخير البسيط إلى تقليل عدد هذه المكالمات. أريد أيضًا التخلص من التكرارات حتى لا يؤدي النص الذي تم اقتصاصه إلى الطلبات المتكررة. ربما أريد أيضًا تصفية بعض العناصر. على سبيل المثال ، هل تحتاج إلى القدرة على البحث بدون الأحرف المدخلة أم البحث باستخدام حرف واحد؟
البرمجة التفاعلية
هناك العديد من الخيارات لما يجب فعله بعد ذلك ، ولكن الآن أريد التركيز على تقنية تُعرف عادةً باسم البرمجة التفاعلية ومكتبة RxJava. عندما جئت لأول مرة عبر البرمجة التفاعلية ، رأيت الوصف التالي:
ReactiveX هي واجهة برمجة تطبيقات تعمل مع الهياكل غير المتزامنة وتعالج تدفقات البيانات أو الأحداث باستخدام مجموعات من أنماط Observer و Iterator ، بالإضافة إلى ميزات البرمجة الوظيفية.
لا يفسر هذا التعريف بشكل كامل طبيعة وقوة ReactiveX. وإذا كان يشرح ، فعندئذ فقط لأولئك الذين هم بالفعل على دراية بمبادئ هذا الإطار. رأيت أيضًا مخططات مثل هذا:

يوضح الرسم التخطيطي دور عامل التشغيل ، ولكنه لا يفهم الجوهر بالكامل. لذا ، دعنا نرى ما إذا كان بإمكاني شرح هذا الرسم البياني بوضوح أكبر بمثال بسيط.
دعونا نعد مشروعنا أولاً. ستحتاج إلى مكتبة جديدة في ملف build.gradle
:
implementation "io.reactivex.rxjava2:rxjava:2.1.14"
تذكر مزامنة تبعيات المشروع لتحميل المكتبة.
الآن دعونا نلقي نظرة على حل جديد. باستخدام الطريقة القديمة ، دخلت إلى API عندما أدخلت كل حرف جديد. باستخدام الطريقة الجديدة ، سأقوم بإنشاء Observable
:
override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val searchView = menu?.findItem(R.id.action_search)?.actionView as SearchView
هذا الكود يفعل نفس الشيء مثل الكود القديم. السجلات هي كما يلي:
D/MainActivity: subscriber: T D/MainActivity: subscriber: TE D/MainActivity: subscriber: TES D/MainActivity: subscriber: TEST D/MainActivity: subscriber: TEST
ومع ذلك ، فإن الفرق الرئيسي بين استخدام التقنية الجديدة هو وجود تيار تفاعلي - Observable
. يرسل معالج النص (أو معالج الطلب في هذه الحالة) العناصر إلى الدفق باستخدام طريقة onNext()
. ولدى Observable
مشتركون Observable
هذه العناصر.
يمكننا إنشاء سلسلة من الأساليب قبل الاشتراك لتقليل قائمة السلاسل للمعالجة. بادئ ذي بدء ، سيكون النص المرسل صغيرًا دائمًا ولن تكون هناك مسافات في بداية السطر ونهايته:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
أقطع طرق لإظهار الجزء الأكثر أهمية. الآن نفس السجلات هي كما يلي:
D/MainActivity: subscriber: t D/MainActivity: subscriber: te D/MainActivity: subscriber: tes D/MainActivity: subscriber: test D/MainActivity: subscriber: test
الآن دعنا نطبق تأخيرًا قدره 250 مللي ثانية ، ونتوقع المزيد من المحتوى:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(250, TimeUnit.MILLISECONDS) .subscribe { text -> Log.d(TAG, "subscriber: $text" }
وأخيرًا ، احذف الدفق المكرر بحيث تتم معالجة الطلب الفريد الأول فقط. سيتم تجاهل الطلبات المتطابقة التالية:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .subscribe { text -> Log.d(TAG, "subscriber: $text" }
ملاحظة perev. في هذه الحالة ، يكون من المنطقي أكثر استخدام عامل التشغيل distinctUntilChanged()
، لأنه في حالة البحث المتكرر على أي سلسلة ، سيتم ببساطة تجاهل الاستعلام. وعند تنفيذ هذا البحث ، من المعقول الانتباه فقط إلى آخر طلب ناجح وتجاهل الطلب الجديد إذا كان مطابقًا للطلب السابق.
في النهاية ، نقوم بتصفية الاستعلامات الفارغة:
Observable.create(ObservableOnSubscribe<String> { ... }) .map { text -> text.toLowerCase().trim() } .debounce(100, TimeUnit.MILLISECONDS) .distinct() .filter { text -> text.isNotBlank() } .subscribe { text -> Log.d(TAG, "subscriber: $text" }
في هذه المرحلة ، ستلاحظ أنه يتم عرض رسالة واحدة فقط (أو ربما رسالتين) في السجلات ، مما يشير إلى عدد أقل من مكالمات API. في هذه الحالة ، سيستمر التطبيق للعمل بشكل كاف. علاوة على ذلك ، ستؤدي الحالات التي تدخل فيها شيئًا ما ، ثم تحذفه وتدخل مرة أخرى ، إلى عدد أقل من المكالمات إلى API.
هناك العديد من عوامل التشغيل المختلفة التي يمكنك إضافتها إلى خط الأنابيب هذا ، اعتمادًا على أهدافك. أعتقد أنها مفيدة جدًا للعمل مع حقول الإدخال التي تتفاعل مع واجهة برمجة التطبيقات. الرمز الكامل كما يلي:
يمكنني الآن استبدال رسالة السجل بمكالمة إلى ViewModel لبدء مكالمة API. ومع ذلك ، هذا موضوع لمقال آخر.
الخلاصة
باستخدام هذه التقنية البسيطة لتغليف العناصر النصية في Observable واستخدام RxJava ، يمكنك تقليل عدد مكالمات API المطلوبة لأداء عمليات الخادم ، بالإضافة إلى تحسين استجابة التطبيق الخاص بك. في هذه المقالة ، قمنا بتغطية جزء صغير فقط من عالم RxJava بأكمله ، لذلك أترك لك روابط لقراءة إضافية حول هذا الموضوع:
- Dan Lew Grokking RxJava (هذا هو الموقع الذي ساعدني على التحرك في الاتجاه الصحيح).
- موقع ReactiveX (غالبًا ما أشير إلى هذا الموقع عند بناء خط أنابيب).