آموزش RxJava با مثال های کاربردی ، ما در کدنویسی پلتفرم اندروید کتابخانه های بسیاری داریم که استفاده از آنها از واجبات است ! یکی از پرکاربردترین آنها کتابخانه RxJava ست که در این پست تجاری اپ بیشتر به آن میپردازیم پس با ما همراه باشید 😉
در دنیای Rxjava ما به تعداد ستاره های آسمان 😀 اپراتور بدردبخور داریم که به معنای واقعی جادو میکنند !!
البته ما یک آموزش پروژه محور کامل الگوی طراحی MVVM با Rxjava در تجاری اپ داریم که میتونید مطالعه کنید 🙂
در این پست ما تعدادی مثال کاملا کاربردی از استفاده هایی که میشه از کتابخانه RxJava در اندروی کرد آوردیم که امید است برای شما نیز بکار آید 🙂
نکته قابل توجه این ست که ما از زبان کاتلین kotlin استفاده میکنیم
مشخصا همه میدونیم که زمانی از TextWatcher استفاده میکنیم که میخوایم اطلاعات ورودی یک EditText رو در ۳ حالت به دست بیاریم !
در حالت عادی ما بدین صورت TextWatcher خواهیم داشت
et1.addTextChangedListener(new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// TODO Auto-generated method stub
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// TODO Auto-generated method stub
}
@Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
اما با استفاده از کتابخانه RxJava میتونیم کد رو بصورت زیر بنویسیم و کدهای Boilerplate رو حذف کنیم
RxTextView.afterTextChangeEvents(your_edittext_name)
.filter { t -> t.toString().length > 3 //some filter }
.subscribe { //do sth if filter has pass }
RxTextView.beforeTextChangeEvents(edt_new_pass)
.filter { t -> t.toString().length > 3 }
.subscribe { checkLayoutAccept() }
در بعضی مواقع ما نیاز داریم تا یک سری عملیات را بروی تعدادی view اعمال کنیم و بر این اساس یک سری داده ایجاد کنیم.
برای مثال فرض کنید یک سری CheckBox داریم که نمایانگر روزهای هفته هستند و براساس اینکه فعال هستند یا غیر فعال یک ارایه از اعداد بسته به روز انتخابی ایجاد کنیم.
در حالت عادی ما یک سری if else… تودرتو خواهیم داشت درحالی که در کتابخانه RxJava ما یک بلاک بصورت زیر خواهیم داشت
var days = ""
Observable.just(activity.check_sat_day, activity.check_sun_day, activity.check_mon_day, activity.check_tue_day, activity.check_wed_day, activity.check_thu_day, activity.check_fri_day)
.filter({
it.isChecked
}).flatMap {
if (it == activity.check_sat_day)
days += "0"
if (it == activity.check_sun_day)
days += "1"
if (it == activity.check_mon_day)
days += "2"
if (it == activity.check_tue_day)
days += "3"
if (it == activity.check_wed_day)
days += "4"
if (it == activity.check_thu_day)
days += "5"
if (it == activity.check_fri_day)
days += "6"
return@flatMap Observable.just(days)
}.subscribe()
این مثال دقیقا برعکس مثال قبل است بدین صورت که در اینجا ما یک آرایه از اعداد داریم و براساس آنها چک باکس هایی که مشخص کننده روز های هفته است را فعال یا غیر فعال میکنیم
بصورت زیر
val ary: List<String> = days.split("".toRegex())//"12345" -> "1" , "2" , "3"
Observable.fromIterable(ary)
.flatMap {
if (it == "0")
check_sat_day.isChecked = true
if (it == "1")
check_sun_day.isChecked = true
if (it == "2")
check_mon_day.isChecked = true
if (it == "3")
check_tue_day.isChecked = true
if (it == "4")
check_wed_day.isChecked = true
if (it == "5")
check_thu_day.isChecked = true
if (it == "6")
check_fri_day.isChecked = true
return@flatMap Observable.just(it)
}.subscribe()
در بعضی مواقع ما لازم داریم که برروی مقادیر ورودی یک ادیت تکست بصورت آنی با هر تغییر عملیاتی انجام دهیم اما نمیخواهیم عملیات با وارد شدن هر دونه کاراکتر اعمال شود.
بلکه برروی تکست ورودی نهایی که مورد نظر کاربر است !
با مثالی واضح تر این مسئله را بیان میکنیم . شما ادیت تکستی که کار search را میخواهد انجام دهد درنظر بگیرید
ما میخواهیم با وارد کردن داده در این ادیت تکست مقادیر خروجی سرچ تغییر کند بصورت آنی
خب طبیعتا باید یک listener برای حالت تغییر ورودی ادیت تکست قرار دهیم برای مثال میتونیم از رویداد onTextChange مربوط به TextWatcher استفاده کنیم اما مشکلی که هست در این روش این ست که با وارد کردن هر کاراکتر عملیات ما انجام خواهد شد حال فرض کنید که این عملیات درخواست یک api سرور است.
خب طبیعتا با وارد شدن هرکاراکتر اگر این api صدا زده شود برنامه ما خیلی رم مصرف میکنه و برای سیستم موبایل بشدت مضر خواهد بوداما با debounce ما مشخص میکنیم که روی حالت TextChange باش اما پس از گذشت مدت زمان کوتاهی (مثلا چند میلی ثانیه) عملیات را نجام بده
این روش خیلی بهینه تر خواهد بود چرا که ما درهنگام تایپ کاربر و با ورود هرکاراکتر سرور را مشغول نمیکنیم بلکه دقیقا پس از تایپ کاربر کل تکست ورودی را میگیریم و api را صدا میزنیم
Observable<String> obs;
obs = RxTextView.textChanges(activity.getBinding().edtUsername).
filter(charSequence ->
{
Log.e("filter run ", charSequence.toString());
return charSequence.length() > 3 && !baseUsername.equals(charSequence);
})
.debounce(2000, TimeUnit.MILLISECONDS)
.map(charSequence -> {
Log.e("map run ", charSequence.toString());
return charSequence.toString();
});
obs.subscribe(s -> {
Log.e("obs.subscribe run ", s);
if (!s.equals(baseUsername))
checkUsername(s);
});
حلقه ()for یکی از پرکاربرد ترین دستور دربرنامه نویسی ست که با کتابخانه RxJava در اندروید میتونیم اون رو بدین صورت تغییر بدیم و کنترل بیشتری روی اون داشته باشیم بدون کدهای اضافه
این کدی ست که در حالت عادی میزنیم
for (i in 0 until offList.size) {
if (offList[i].foodId == 2)
doSomeFunc(offList[i])
}
و این حالت کتابخانه RxJava آن است که اپراتور firstElement مشخص میکنه که با گرفتن اولین آیتم موفق از حلقه خارج شو که میتونیم اون رو هم حذف کنیم و حلقه رو برروی تمامی آیتم های لیست اعمال کنیم
Observable.fromIterable(offList)
.filter{
it.foodId == 2
}.firstElement()
.subscribe{
doSomeFunc(it)
}
همچنین بدین صورت میتونیم حلقه های تودتو بنویسیم
Observable.fromIterable(mList)
.filter{it ->
baseFoods.contains(it)
}
.map{it ->
Observable.fromIterable(baseFoods)
.filter{bf->
it.foodId == bf.foodId
}
.firstElement()
.subscribe{bf ->
doSomeFunc(bf)
}
}.subscribe()
خب شرایطی که برای حالت ورود برنامه میذاریم معمولا این دوشرط رو دارن که شماره موبایل معتبر باشه و رمزعبور هم بیش از ۴ کاراکتر باشه.
در حالت عادی ما بلاک های if تودرتو رو داریم اما در حالت کتابخانه RxJava ما بلاک زیر رو خواهیم داشت که پله پله مشخص کردیم که باید چیکار کنی
Observable.just(edt_mobile.text.toString())
.filter {
if (checkMobile(it))
return@filter true
else {
edt_mobile.error = getString(R.string.login01_error)
return@filter false
}
}.map {
edt_password.text.toString()
}.filter {
if (it.isNotBlank() && it.length >= 4)
return@filter true
else {
edt_password.error = getString(R.string.login02_error)
return@filter false
}
}.subscribe {
goLogin(edt_mobile.text.toString(), edt_password.text.toString())
}