در این مثال، ما به شما نشان میدهیم که چگونه میتوانید با استفاده از Retrofit و MVVM با Future Observable، یک درخواست شبکه را به REST API انجام دهید.
وابستگی (Dependencies)
ما برای این مثال به وابستگی Retrofit احتیاج داریم زیرا دارای درخواست شبکه (network request) هستیم. همچنین وابستگی به LiveData و ViewModels داریم زیرا این مثال از معماری MVVM استفاده میکند.
همچنین وابستگی به RxJava Call Adapter داریم. به طور پیش فرض، شما نمیتوانید یک Observable یا یک Flowable را از یک درخواست Retrofit بازگردانید. این وابستگی به شما امکان میدهد تا Retrofit Call objects را به Flowables / Observables تبدیل کنید.
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
def retrofitVersion = "2.5.0"
def rxjava_version = '2.2.7'
def rxandroid_version = '2.1.1'
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
// RxJava Call Adapter
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
RequestApi :
Request Api در اینجا متدی است که Retrofit برای ایجاد query به REST API استفاده میکند. توجه کنید که یک Observable برمیگردد.
این به دلیل وابستگی RxJava Call Adapter میباشد که در بخش وابستگیها به آن اشاره کردیم، که میتواند یک Observable را بازگرداند.
public interface RequestApi {
@GET("todos/1")
Observable makeObservableQuery();
}
ServiceGenerator :
این کلاس مسئول ایجاد نمونه Retrofit، مراجعه و گرفتن کلاس RequestApi است که ما در بالا تعریف کردیم.
نکته ای که در اینجا باید به آن توجه کنیم، فراخوانی متد addCallAdapterFactory است. بدون آن، ما نمیتوانیم
Retrofit Call objects را به Flowables / Observables تبدیل کنیم.
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
public class ServiceGenerator {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com";
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = retrofitBuilder.build();
private static RequestApi requestApi = retrofit.create(RequestApi.class);
public static RequestApi getRequestApi(){
return requestApi;
}
}
Repository :
در اینجا مثالی از "bread and butter" آورده شده است. اینجاست که ما از Executor برای فراخوانی شبکه استفاده میکنیم و سپس Future Observable را به ViewModel باز میگردانیم.
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if(instance == null){
instance = new Repository();
}
return instance;
}
public Future> makeFutureQuery(){
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Callable> myNetworkCallable = new Callable>() {
@Override
public Observable call() throws Exception {
return ServiceGenerator.getRequestApi().makeObservableQuery();
}
};
final Future> futureObservable = new Future>(){
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if(mayInterruptIfRunning){
executor.shutdown();
}
return false;
}
@Override
public boolean isCancelled() {
return executor.isShutdown();
}
@Override
public boolean isDone() {
return executor.isTerminated();
}
@Override
public Observable get() throws ExecutionException, InterruptedException {
return executor.submit(myNetworkCallable).get();
}
@Override
public Observable get(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
return executor.submit(myNetworkCallable).get(timeout, unit);
}
};
return futureObservable;
}
}
View Model :
از آنجا که ما از معماری MVVM استفاده میکنیم، باید یک ViewModel داشته باشیم. فقط یک متد واحد وجود دارد که به Repository دسترسی پیدا میکند و Future Observable را برمی گرداند.
public class MainViewModel extends ViewModel {
private Repository repository;
public MainViewModel() {
repository = Repository.getInstance();
}
public Future> makeFutureQuery(){
return repository.makeFutureQuery();
}
}
MainActivity:
و سرانجام در MainActivity ،query با Subscribe کردن observable شروع میشود و پاسخ از سرور در Log چاپ میشود.
MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
try {
viewModel.makeFutureQuery().get()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: called.");
}
@Override
public void onNext(ResponseBody responseBody) {
Log.d(TAG, "onNext: got the response from server!");
try {
Log.d(TAG, "onNext: " + responseBody.string());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: called.");
}
});
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: got the response from server!
MainActivity: onNext: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
ساخت عملگر (fromPublisher)
- مقدمه
- ()fromPublisher
- مثال
مقدمه
در این بخش در مورد اپراتور ()fromPublisher صحبت خواهیم کرد.
ما توضیح مختصری خواهیم داد و سپس به مثالی بسیار مفصل میپردازیم.
نکته: ما انتظار داریم شما بدانید که چگونه از معماری MVVM و Retrofit استفاده کنید.
()fromPublisher
()fromPublisher برای تبدیل اشیاء LiveData به reactive streams یا از reactive streams به اشیاء LiveData استفاده میشود.
احتمالا هرگز نمیخواهید LiveData را به یک reactive Observable تبدیل کنید، غیر از موارد نادری که ممکن است بخواهید از اپراتور استفاده کنید.
از طرف دیگر، تبدیل یک Observable به LiveData در واقع بسیار کاربردی است. موارد زیادی برای این کار وجود دارد. مثال زیر یک درخواست شبکه با استفاده از Retrofit ایجاد میکند و پاسخی را به صورت یک شیء Flowable بازیابی میکند.
Publisher interface ،Flowable را پیاده سازی میکند. اشیاء Publisher دقیقاً مانند subscribed ،Observable میشوند.
یا
مثالی از FromPublisher:
مجوز اینترنت (Internet Permission):
Internet Permission را به Manifest اضافه کنید زیرا میخواهیم با استفاده از Retrofit درخواستی انجام دهیم.
وابستگی (Dependencies):
ما به وابستگیهای زیادی احتیاج داریم. انتظار داریم که شما در حال حاضر بدانید که چگونه از Retrofit و معماری MVVM استفاده کنید. بنابراین تنها دو وابستگی زیر را میخواهیم توضیح دهیم:
(RxJava Call Adapter (Call object to Observable:
به طور پیش فرض، شما نمیتوانید یک Observable یا یک flowable را از یک Retrofit request بازگردانید.
implementation "com.squareup.retrofit2:adapter-rxjava2:2.9.0"
جدیدترین نسخه این کتابخانه را میتوانید دریافت کنید.
تبدیل LiveData به Observable:
به ما این امکان را میدهد تا Observables (یا Flowables) را به اشیاء LiveData تبدیل کنیم.
implementation "android.arch.lifecycle:reactivestreams:1.1.1"
جدیدترین نسخه این کتابخانه را میتوانید دریافت کنید.
def retrofitVersion = "2.5.0"
def rxjava_version = '2.2.7'
def rxandroid_version = '2.1.1'
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
// RxJava Call Adapter (Call object to Observable)
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
// Convert Observable to LiveData
implementation "android.arch.lifecycle:reactivestreams:1.1.1"
RequestApi :
این کلاس جایی است که متدهای Retrofit request در آن هستند. در این حالت تنها یک مورد وجود دارد.
توجه کنید که یک شیء Flowable را برمی گرداند.
public interface RequestApi {
@GET("todos/1")
Flowable makeQuery();
}
ServiceGenerator:
این کلاس مسئول ایجاد نمونه Retrofit مراجعه و گرفتن کلاس RequestApi است که ما در بالا تعریف کردیم.
نکته: موردی که در اینجا باید به آن توجه کنیم ، فراخوانی متد addCallAdapterFactory است. بدون آن، ما نمیتوانیم
Flowables / Observables را به Retrofit Call objects تبدیل کنیم.
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
public class ServiceGenerator {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com";
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = retrofitBuilder.build();
private static RequestApi requestApi = retrofit.create(RequestApi.class);
public static RequestApi getRequestApi(){
return requestApi;
}
}
Repository:
همان طور که چندین بار بیان کردیم، ما از معماری MVVM استفاده میکنیم. و معماری مناسب MVVM همیشه شامل یک repository (حداقل در هر زمان انجام معاملات بانک اطلاعاتی) است.
این جایی است که جادو اتفاق میافتد. متد ()makeReactiveQuery از متد ()fromPublisher برای تبدیل یک Flowable به LiveData استفاده میکند.
توجه کنید که ما در یک background thread مشترک (Subscribe) میشویم! این خیلی مهم است. کلیه عملیات شبکه باید بر روی یک background thread انجام شود.
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if(instance == null){
instance = new Repository();
}
return instance;
}
public LiveData makeReactiveQuery(){
return LiveDataReactiveStreams.fromPublisher(ServiceGenerator.getRequestApi()
.makeQuery()
.subscribeOn(Schedulers.io()));
}
}
ViewModel:
ما یک بار دیگر که از معماری MVVM استفاده میکنیم باید ViewModel داشته باشیم. فقط یک متد واحد وجود دارد که به مخزن دسترسی پیدا میکند و LiveData را برمی گرداند.
public class MainViewModel extends ViewModel {
private Repository repository;
public MainViewModel() {
repository = Repository.getInstance();
}
public LiveData makeQuery(){
return repository.makeReactiveQuery();
}
}
MainActivity:
قسمت آخر Subscirbe کردن LiveData در MainActivity است.
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onChanged: this is a live data response!MainActivity: onChanged: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
عملگرهای Filter : Filter
وقتی لیستی از custom objects را در اختیار دارید و میخواهید بر اساس یک فیلد خاص آن را فیلتر کنید، چند بار در این وضعیت قرار گرفتهاید؟ اگر لیست بسیار بزرگ است پس فیلتر کردن باید روی یک background thread انجام شود.
اگر چنین اتفاقی برای شما بیفتد، عملگر ()filter بهترین دوست شما خواهد بود.
()filter یک اپراتور عالی است. ما حدس میزنیم که شما از این اپراتور زیاد استفاده خواهید کرد.
مثال ۱: فیلتر کردن یک فیلد String در یک لیست
در این مثال میخواهم به شما نشان دهیم چگونه لیستی از Task POJO's) custom java objects) را در یک فیلد String خاص فیلتر کنید. بنابراین فقط Task Objects را که شامل یک پارامتر String خاص هستند، منتشر میکند.
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- Tasks را برای توضیحات خاص فیلتر میکنیم.
- سپس Subscribe کردن و منتشر کردن کارهایی (Task) که از Test عبور میکنند.
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.filter(new Predicate() {
@Override
public boolean test(Task task) throws Exception {
if(task.getDescription().equals("Walk the dog")){
return true;
}
return false;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: This task matches the description: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
خروجی کد بالا به صورت زیر میباشد.
onNext: This task matches the description: Walk the dog
مثال ۲ : فیلتر کردن یک فیلد Boolean در یک لیست
ما به شما نشان میدهیم که چگونه لیستی از custom objects را در یک فیلد خاص Boolean فیلتر کنید. اگر فیلد Boolean
صحیح (True) باشد، Task Object در Observer منتشر میشود.
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- Tasksرا در قسمت ()isComplete فیلتر میکنیم.
- سپس Subscribe کردن و منتشر کردن کارهایی (Task) که از Test عبور میکنند.
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.filter(new Predicate() {
@Override
public boolean test(Task task) throws Exception {
return task.isComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: This is a completed task: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
خروجی کد بالا به صورت زیر میباشد.
onNext: : Take out the trash
onNext: : Make my bed
onNext: : Make dinner
عملگرهای Distinct : Filter
- مقدمه
- اسفاده نادرست از ()Distinct
- استفاده درست از ()Distinct
مقدمه
اپراتور ()Distinct نوع دیگری است که شما زیاد از آن استفاده خواهید کرد. میتوانید اشیاء سفارشی (Custom Objects) را بر اساس فیلدهای مشخص فیلتر کنید. فکر میکنیم بدون مثال درک این موضوع کمی سخت باشد. بنابراین اگر دچار سردرگمی شدید، به مثالهای زیر توجه کنید.
اپراتور Distinct فقط با دسترسی به آیتم هایی که از قبل منتشر نشده اند، Observable را فیلتر میکند.
اسفاده نادرست از ()Distinct
ما فکر میکنیم اپراتور ()Distinct در نگاه اول خیلی قابل درک نیست. بنابراین میخواهیم روش نادرست استفاده از آن را به شما نشان دهیم (که ممکن است روشی باشد که شما سعی کرده اید از آن استفاده کنید).
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- اپراتور ()Distinct را اعمال کرده و یک custom function را پاس میدهیم.
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
tasks.add(new Task("Make dinner", true, 5)); // duplicate for testing the distinct operator
return tasks;
}
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.distinct(new Function() { // <--- WRONG
@Override
public Task apply(Task task) throws Exception {
return task;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: Take out the trash
MainActivity: onNext: Walk the dog
MainActivity: onNext: Make my bed
MainActivity: onNext: Unload the dishwasher
MainActivity: onNext: Make dinner
MainActivity: onNext: Make dinner
توجه کنید که خروجی یک نسخهی تکراری را نشان میدهد. این صحیح نیست. اپراتور ()Distinct برای حذف نسخههای تکراری است. پس کجا اشتباه کردیم؟
نوع دادهی دوم در عملکرد (Funtcion) نادرست است. به مثال دوم زیر نگاهی بیندازید تا ببینید چگونه قرار است از آن استفاده شود.
استفاده درست از ()Distinct
همانطور که در مثال قبلی دیدید، خروجی مقادیر متمایز را بر اساس فیلد توضیحات نشان نمیداد.این به دلیل استفاده نادرست از عملکردهای منتقل شده به عنوان ورودی به اپراتور ()Distinct بود.
در اینجا نحوهی درست استفاده از آن آمده است.
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- اعمال اپراتور ()Distinct و پاس دادن یک
- اپراتور ()Distinct را اعمال کرده و یک custom function را پاس میدهیم.
- تفاوت در نوع بازگشت عملکرد است. توجه کنید که ما یک رشته را بر میگردانیم و در آن رشته چک کردن را انجام میدهیم.
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
tasks.add(new Task("Make dinner", true, 5)); // duplicate for testing the distinct operator
return tasks;
}
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.distinct(new Function() { // <--- CORRECT
@Override
public String apply(Task task) throws Exception {
return task.getDescription();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: Take out the trash
MainActivity: onNext: Walk the dog
MainActivity: onNext: Make my bed
MainActivity: onNext: Unload the dishwasher
MainActivity: onNext: Make dinner
اکنون خروجی صحیح است.
عملگرهای Take : Filter و TakeWhile
مقدمه
()take و ()takeWhile را در دسته فیلترها قرار میدهیم. آنها شبیه به عملگر ()Filter هستند زیرا لیستهای اشیاء را فیلتر میکنند.
تفاوت اصلی بین عملگرهای ()take و عملگر ()Filter در این است که عملگر ()Filter تمامی اشیاء موجود در لیست را بررسی میکند. بنابراین میتوان گفت عملگر ()Filter شامل تمامی آن هاست.
در حالی که اپراتورهای ()take منحصر به فرد در نظر گرفته میشوند پس لازم نیست همهی موارد موجود در لیست را بررسی کنند. آنها اشیاء را منتشر میکنند تا زمانی که شرط عملکرد آنها برآورده شود.
()take
اپراتور ()take تنها "n" آیتم را که توسط یک Observable قابل انتشار است را منتشر میکند، در حالی که از موارد باقیمانده کاملا غافل میشود.
این مثال را در نظر بگیرید:
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- اپراتور take را اعمال کرده و ۳ مقدار را پاس میدهیم.
public class DataSource {
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
}
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.take(3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: Take out the trash
MainActivity: onNext: Walk the dog
MainActivity: onNext: Make my bed
حتی اگر ۵ شیء به لیست اضافه شده باشد، فقط ۳ مورد منتشر میشوند.
()TakeWhile
()TakeWhile منبع Observable را منعکس میکند تا زمانی که شرایطی که شما تعیین میکنید نادرست شود.
این مثال را در نظر بگیرید:
- ما یک Observable از لیستی از Task Objects را ایجاد میکنیم.
- اپراتور ()TakeWhile را اعمال کرده و از custom function استفاده میکنیم. این تابع در حال بررسی یک کار انجام شده است.
- پس از یافتن یک کار کامل شده ، Observableبه اتمام میرسد.
public class DataSource {
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
}
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.takeWhile(new Predicate() {
@Override
public boolean test(Task task) throws Exception {
return task.isComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: Take out the trash
عملگرهای تبدیل کننده (Transformation Operators)
- Introduction
- ()Map
- ()Buffer
- ()Debounce
- ()ThrottleFirst
- ()FlatMap
- ()ConcatMap
- ()SwitchMap
کار یک اپراتور به شرح زیر است :
اپراتورها مواردی را که توسط یک Observable منتشر میشود را تبدیل میکنند.
در زیر برخی از متداولترین اپراتورهای دگرگونی وجود دارد که فرض میکنیم شما از آنها استفاده میکنید.
Map
یک تابع را برای هر مورد منتشر شده اعمال میکند. با استفاده از تابعی از آن، هر مورد منتشر شده را تغییر میدهد.
(نظم ارسال حفظ میشود.)
نمودار Map :

Buffer
بهطور دوره ای items را از یک Observable به صورت bundles جمع میکند و به جای اینکه یکبار items را منتشر کند، bundles را منتشر میکند. (نظم ارسال حفظ میشود)
نمودار Buffer:

Debounce
اگر یک زمان خاص گذشته باشد، یک Item را از یک Observable منتشر میکند بدون آنکه یک Item دیگر را منتشر کند. این عملگر برای کلیک دکمه (button clicks) بسیار عالی است. اگر کاربر بارها و بارها یک دکمه را کلیک کند، دیگر نیازی به اجرای چندین بار کار نیست. میتوانیم از اپراتور ()Debounce استفاده کنیم تا با معرفی یک بازهی زمانی مجاز بین کلیک، کلیکهای خود را کنترل کنیم. اگر مدت زمانی سپری نشده باشد، میتوانیم از هر دو متد جلوگیری کنیم. (نظم ارسال حفظ میشود.)
نمودار Debounce:

ThrottleFirst
موارد منتشر شده توسط منبع Observable را که در یک بازهی زمانی قرار دارند، فیلتر میکند.(نظم ارسال حفظ میشود.)
نمودار ThrottleFirst:

FlatMap
موارد منتشر شده توسط یک Observable را به Observables تبدیل میکند، و سپس انتشار از آن را به یک Observable Single قسمت میکند. اگر با LiveData آشنا باشید، MediatorLiveData میتواند کاری بسیار مشابه انجام دهد. در مورد ()FlatMap در ادامه بیشتر صحبت میکنیم. (نظم ارسال حفظ نمیشود.)
نمودار FlatMap:

ConcatMap
موارد منتشر شده توسط یک Observable را به Observables تبدیل میکند. این در اصل همان مورد ()FlatMap است، اما نظم ارسال حفظ میشود. اما از آنجا که ()ConcatMap باید منتظر بماند تا هر یک از Observable کار خود را انجام دهند پس از نظر فنی غیر همزمان نیست. (نظم ارسال حفظ میشود.)
نمودار ConcatMap:

SwitchMap
()SwitchMap آیتمهای منتشر شده توسط یک Observable را به یک Observable تبدیل میکند درست مثل ()ConcatMap و ()FlatMap. تفاوت این است که به محض مشترک شدن یک Observer جدید، Observer قبلی را لغو میکند. ()SwitchMap محدودیتی را حل میکند که ()ConcatMap و ()FlatMap هم دارند. (نظم ارسال حفظ میشود)
نمودار SwitchMap:

عملگرهای تبدیل کننده : Map
یک تابع را برای هر مورد منتشر شده اعمال میکند. با استفاده از تابعی از آن، هر مورد منتشر شده را تغییر میدهد.
(نظم ارسال حفظ میشود.)
نمودار Map :

برای دو مثال زیر به این دو کلاس مراجعه میکنیم:
Task.java (کلاس Task)
public class Task {
private String description;
private boolean isComplete;
private int priority;
public Task(String description, boolean isComplete, int priority) {
this.description = description;
this.isComplete = isComplete;
this.priority = priority;
}
// getter and setters ....
}
DataSource.java (کلاس DataSource)
public class DataSource {
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
}
مثال : (Mapping (Task -> String
در این مثال ما یک custom map function ایجاد میکنیم که فیلد توضیحات را از Task Objects استخراج میکند و فقط آن پارامتر را منتشر میکند.
تابع:
Function extractDescriptionFunction = new Function() {
@Override
public String apply(Task task) throws Exception {
Log.d(TAG, "apply: doing work on thread: " + Thread.currentThread().getName());
return task.getDescription();
}
};
ایجاد Observer و Observable :
Observable extractDescriptionObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(extractDescriptionFunction)
.observeOn(AndroidSchedulers.mainThread());
extractDescriptionObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(String s) {
Log.d(TAG, "onNext: extracted description: " + s);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: apply: doing work on thread: RxCachedThreadScheduler-1
... Repeat x5
onNext: extracted description: Take out the trash
onNext: extracted description: Walk the dog
onNext: extracted description: Make my bed
onNext: extracted description: Unload the dishwasher
onNext: extracted description: Make dinner
مثال : (Mapping (Task -> Updated Task
در این مثال ما یک custom map function ایجاد میکنیم که یک Task object را بروزرسانی میکند و سپس آن کار بروزرسانی شده را منتشر میکند.
تابع:
Function completeTaskFunction = new Function() {
@Override
public Task apply(Task task) throws Exception {
Log.d(TAG, "apply: doing work on thread: " + Thread.currentThread().getName());
task.setComplete(true);
return task;
}
};
ایجاد Observer و Observable:
Observable completeTaskObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(completeTaskFunction)
.observeOn(AndroidSchedulers.mainThread());
completeTaskObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: is this task complete? " + task.isComplete());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: apply: doing work on thread: RxCachedThreadScheduler-1
... Repeat x5
MainActivity: onNext: is this task complete? true
... Repeat x5
مثال : Order of Emitted Objects
همانطور که در ابتدا گفتیم، ترتیب اشیاء منتشر شده از اپراتور ()Map حفظ میشود. این بدان معناست که همه اشیاء منتشر شده از Observable به همان ترتیب هستند که به Observable اضافه شده اند.
توجه کنید که اشیاء به همان ترتیب که در کلاس DataSource.java هستند، منتشر میشوند. شاید در حال حاضر این موضوع برای شما جالب به نظر نرسد، اما صبر کنید تا ما به برخی Mapهای دیگر (مانند ()FlapMap ) نگاهی بیندازیم، که نظم را حفظ نمیکند.
// Create an Observable using the fromIterable operator
Observable mappedObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(new Function() {
@Override
public Task apply(Task task) throws Exception {
task.setComplete(true);
return task;
}
})
.observeOn(AndroidSchedulers.mainThread());
// subscribe to the Observable and view the emitted results
mappedObservable.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: mapped: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
MainActivity: onNext: mapped: Take out the trash
MainActivity: onNext: mapped: Walk the dog
MainActivity: onNext: mapped: Make my bed
MainActivity: onNext: mapped: Unload the dishwasher
MainActivity: onNext: mapped: Make dinner
عملگرهای تبدیل کننده: Buffer
بهطور دوره ای items را از یک Observable به صورت bundles جمع میکند و به جای اینکه یکبار items را منتشر کند، bundles را منتشر میکند. (نظم ارسال حفظ میشود.)
نمودار Buffer:

اولین و بارزترین کاربرد اپراتور ()Buffer بسته بندی (Bundle) اشیای منتشر شده به گروهها است. شاید شما فقط میخواهید به طور همزمان ۲ اشیاء منتشر کنید و یک تأخیر زمانی بین آنها اضافه کنید. این همان چیزی است که در اولین مثال زیر مشاهده خواهید کرد.
برنامه بسیار مفید دیگر tracking UI interactions است. این همان چیزی است که در مثال دوم در زیر مشاهده خواهید کرد. همچنین به شما نشان خواهیم داد که چگونه از یک کتابخانه بسیار مفید ساخته شده توسط Jake Wharton، کتابخانه RxBinding، استفاده کنید. میتوانیم از کتابخانه RxBinding استفاده کنیم تا رویدادهای کلیک قابل مشاهده باشد.
توجه داشته باشید که برای مثالهای زیر به این دو کلاس مراجعه میکنیم:
Task.java (کلاس Task):
public class Task {
private String description;
private boolean isComplete;
private int priority;
public Task(String description, boolean isComplete, int priority) {
this.description = description;
this.isComplete = isComplete;
this.priority = priority;
}
// getter and setters ....
}
DataSource.java (کلاس DataSource):
public class DataSource {
public static List createTasksList(){
List tasks = new ArrayList<>();
tasks.add(new Task("Take out the trash", true, 3));
tasks.add(new Task("Walk the dog", false, 2));
tasks.add(new Task("Make my bed", true, 1));
tasks.add(new Task("Unload the dishwasher", false, 0));
tasks.add(new Task("Make dinner", true, 5));
return tasks;
}
}
یک مثال ساده:
در این مثال:
- Observable را ایجاد میکنیم.
- اپراتور ()Buffer را به کار میبریم.
- Subscribe کردن و مشاهده کردن نتیجه منتشر شده در log.
// Create an Observable
Observable taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io());
taskObservable
.buffer(2) // Apply the Buffer() operator
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() { // Subscribe and view the emitted results
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(List tasks) {
Log.d(TAG, "onNext: bundle results: -------------------");
for(Task task: tasks){
Log.d(TAG, "onNext: " + task.getDescription());
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
onNext: bundle results: -------------------
onNext: Take out the trash
onNext: Walk the dog
onNext: bundle results: -------------------
onNext: Make my bed
onNext: Unload the dishwasher
onNext: bundle results: -------------------
onNext: Make dinner
مثال : Tracking UI Interactions
در این مثال ، کلیک روی دکمه در UI را Observe میکنیم. برای تعیین فاصله زمانی از بافر استفاده میکنیم. هر کلیک که در بازه زمانی مشخص ثبت خواهد شد. سپس در پایان بازه کلیکها اضافه میشوند بنابراین میدانیم که تعداد آنها چقدر بوده است.
Dependencies:
این وابستگی به کتابخانه RxBinding است. می توانید آخرین نسخه آن را دریافت کنید.
def rxbinding_version = "4.0.0"
// Rx Binding Library
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbinding_version"
Activity Code:
در اینجا تمام کدی که میخواهید به Activity خود اضافه کنید وجود دارد. البته باید یک دکمه به UI نیز اضافه کنید. ما کد UI را درج نکردیم چون بسیار ساده است.
نکته: به متد ()RxView.clicks توجه کنید. این بخشی از کتابخانه RxBinding است.
فراموش نکنید که Disposables را به CompositeDisposables اضافه کنید. سپس آنها را با متد onDestroy پاک کنید.
// global disposables object
CompositeDisposable disposables = new CompositeDisposable();
// detect clicks to a button
RxView.clicks(findViewById(R.id.button))
.map(new Function() { // convert the detected clicks to an integer
@Override
public Integer apply(Unit unit) throws Exception {
return 1;
}
})
.buffer(4, TimeUnit.SECONDS) // capture all the clicks during a 4 second interval
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d); // add to disposables to you can clear in onDestroy
}
@Override
public void onNext(List integers) {
Log.d(TAG, "onNext: You clicked " + integers.size() + " times in 4 seconds!");
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
// make sure to clear disposables when the activity is destroyed
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear();
}
خروجی کد بالا به صورت زیر میباشد.
onNext: You clicked 19 times in 4 seconds!
عملگرهای تبدیل کننده : Debounce
اپراتور Debounce مواردی را که توسط منبع Observable منتشر میشود را فیلتر میکند که به سرعت یک مورد دیگر به دنبال آن منتشر میشود. (نظم ارسال حفظ میشود.)
نمودار Debounce:

فرض کنید SearchView را در برنامه خود دارید. از آنجا که کاربر کاراکترهایی را در SearchView وارد میکند، میخواهید Query مربوط به سرور را انجام دهید. اگر گرفتن کاراکترها را برای یک دوره زمانی محدود نکنید، هر بار که یک کاراکتر جدید را وارد SearchView میکنید، درخواست جدیدی ایجاد میشود. به طور معمول این کار غیر ضروری است و نتایج نامطلوب به همراه خواهد داشت. اجرای یک جستجوی جدید در هر ۰.۵ ثانیه، خوب است.
مثال : محدود کردن درخواستهای سرور
ما Layout و کل Activity را برای این مثال گنجانده ایم زیرا چند قسمت مختلف و متغیرهای اضافی وجود دارد.
این وابستگی به کتابخانه RxBinding است. می توانید آخرین نسخه آن را دریافت کنید.
def rxbinding_version = "4.0.0"
// Rx Binding Library
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbinding_version"
کدهای زیر برای activity_main.xml است:
MainActivity.java:
توجه: متغیر "timeSinceLastRequest" فقط برای خروجیهای Log است. این بخش منطقی (Logic) نیست.
این ۷ مرحله در زیر انجام شده شده است.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//ui
private SearchView searchView;
// vars
private CompositeDisposable disposables = new CompositeDisposable();
private long timeSinceLastRequest; // for log printouts only. Not part of logic.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
searchView = findViewById(R.id.search_view);
timeSinceLastRequest = System.currentTimeMillis();
// create the Observable
Observable observableQueryText = Observable
.create(new ObservableOnSubscribe() {
@Override
public void subscribe(final ObservableEmitter emitter) throws Exception {
// Listen for text input into the SearchView
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(final String newText) {
if(!emitter.isDisposed()){
emitter.onNext(newText); // Pass the query to the emitter
}
return false;
}
});
}
})
.debounce(500, TimeUnit.MILLISECONDS) // Apply Debounce() operator to limit requests
.subscribeOn(Schedulers.io());
// Subscribe an Observer
observableQueryText.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(String s) {
Log.d(TAG, "onNext: time since last request: " + (System.currentTimeMillis() - timeSinceLastRequest));
Log.d(TAG, "onNext: search query: " + s);
timeSinceLastRequest = System.currentTimeMillis();
// method for sending a request to the server
sendRequestToServer(s);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
// Fake method for sending a request to the server
private void sendRequestToServer(String query){
// do nothing
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear(); // clear disposables
}
خروجی کد بالا به صورت زیر میباشد.
onNext: time since last request: 3290
onNext: search query: john
onNext: time since last request: 1171
onNext: search query: blake
onNext: time since last request: 2360
onNext: search query: mitch
عملگرهای تبدیل کننده : ThrottleFirst
موارد منتشر شده توسط منبع Observable را که در یک بازه زمانی قرار دارند، فیلتر میکند. (نظم ارسال حفظ میشود.
نمودار ThrottleFirst:

اپراتور ()ThrottleFirst در توسعه اندروید بسیار مفید است. به عنوان مثال: اگر یک کاربر یک دکمه (Button) را اسپم (spamming) میکند و شما نمیخواهید هر کلیک را ثبت کنید. شما میتوانید از اپراتور ()ThrottleFirst استفاده کنید تا فقط در هر بازه زمانی رویدادهای جدید کلیک را ثبت کنید.
مثال : اسپم شدن Button را محدود کنید
این وابستگی به کتابخانه RxBinding است. می توانید آخرین نسخه آن را دریافت کنید.
def rxbinding_version = "4.0.0"
// Rx Binding Library
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbinding_version"
کدهای زیر برای activity_main.xml است:
توجه: متغیر "timeSinceLastRequest" فقط برای خروجیهای Log است. این بخش منطقی (Logic) نیست.
این چهار مرحله در کدهای زیر انجام شده است.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//ui
private Button button;
// vars
private CompositeDisposable disposables = new CompositeDisposable();
private long timeSinceLastRequest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button);
timeSinceLastRequest = System.currentTimeMillis();
// Set a click listener to the button with RxBinding Library
RxView.clicks(button)
.throttleFirst(500, TimeUnit.MILLISECONDS) // Throttle the clicks so 500 ms must pass before registering a new click
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(Unit unit) {
Log.d(TAG, "onNext: time since last clicked: " + (System.currentTimeMillis() - timeSinceLastRequest));
someMethod(); // Execute some method when a click is registered
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
private void someMethod(){
timeSinceLastRequest = System.currentTimeMillis();
// do something
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear(); // Dispose observable
}
}
خروجی کد بالا به صورت زیر میباشد.
onNext: time since last clicked: 649
onNext: time since last clicked: 830
onNext: time since last clicked: 564
عملگرهای تبدیل کننده : FlatMap
موارد منتشر شده توسط یک Observable را به Observables تبدیل میکند، و سپس انتشار از آن را به یک Observable Single قسمت میکند. اگر با LiveData آشنا باشید، MediatorLiveData میتواند کاری بسیار مشابه انجام دهد. در مورد ()FlatMap در ادامه بیشتر صحبت میکنیم. (نظم ارسال حفظ نمیشود.)
نمودار ()FlatMap:

اپراتور ()FlatMap در توسعه اندروید بسیار مفید است و کارکرد اصلی دارد:
- ساخت Observables از اشیاء منتشر شده توسط Observables دیگر.
- ترکیب کردن منبع چندین Observables به Single Obsevables (این چیزی است که به عنوان "flattening" شناخته میشود).
از آنجایی که یک Single Observable از منابع بالقوه زیادی تولید میشود، آخرین Observable به صورت تصادفی منتشر میشود.
به عبارت دیگر، نظم حفظ نمیشود. بسته به وضعیت شما، این ممکن است مهم باشد یا مهم نباشد. در مثال زیر نظم مهم نیست.
مثال: ()FlatMap
فرض کنید میخواهید با استفاده از REST API، برخی از پستهای وبلاگ را از یک وب سایت Query کنید. اما این همه ی کار نیست.
هر پست وبلاگ حاوی نظرات است. و نظرات از یک end-point url دیگر دریافت میشود. بنابراین برای بازیابی تمام دادهها باید دو Query ایجاد کنیم. وب سایت jsonplaceholder.typicode.com دارای یک REST API است که میتوانم برای نشان دادن این مورد استفاده کنیم.
ما برای تعامل با REST API از Retrofit استفاده میکنیم.
چرا از FlatMap استفاده کنیم؟
زیرا باید اطلاعات را از بیش از یک منبع تهیه کنیم و سپس آن را در یک انتشار واحد ترکیب کنیم، یک Flatmap برای این وضعیت ایده آل است.
به شماره ۱ در url توجه کنید. این به این معنی است که نظرات مربوط به پست وبلاگ را با id = 1 بازیابی میکند.
build.gradle:
یکی از وابستگی هایی که شاید با آن آشنایی نداشته باشید، Retrofit Call Adapter است. برای تبدیل Retrofit Call objects به Observables لازم است. در کلاس ServiceGenerator.java خواهید دید که ما یک ()RxJava2CallAdapterFactory.create را به عنوان ورودی در متد addCallAdapterFactory از شی Retrofit Builder میگذاریم. بدون این وابستگی در دسترس نخواهد بود.
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.codingwithmitch.rxjavaflatmapexample"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def retrofitVersion = "2.5.0"
def rxjava_version = '2.2.7'
def rxandroid_version = '2.1.1'
def recyclerview_version = "1.0.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
// RxJava Call Adapter
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
// Recyclerview
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
}
AndroidManifest.xml
دسترسی به اینترنت (internet permissions) به فایل Manifest.xml اضافه کنید.
activity_main.xml
در کد زیر فقط یک RecyclerView ایجاد شده است.
layout_post_list_item.xml
این یک Layout برای آیتمهای RecyclerView میباشد.
//This is the layout for the RecyclerView list-items
Post.java
ما باید برای درخواستهای پست، Data را مدل سازی کنیم که در کد زیر میبینید.
public class Post {
@SerializedName("userId")
@Expose()
private int userId;
@SerializedName("id")
@Expose()
private int id;
@SerializedName("title")
@Expose()
private String title;
@SerializedName("body")
@Expose()
private String body;
private List comments;
public Post(int userId, int id, String title, String body, List comments) {
this.userId = userId;
this.id = id;
this.title = title;
this.body = body;
this.comments = comments;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public List getComments() {
return comments;
}
public void setComments(List comments) {
this.comments = comments;
}
@Override
public String toString() {
return "Post{" +
"userId=" + userId +
", id=" + id +
", title='" + title + ''' +
", body='" + body + ''' +
'}';
}
}
Comment.java
ما باید برای درخواستهای نظرات، Data را مدل سازی کنیم که در کد زیر میبینید.
public class Comment {
@Expose
@SerializedName("postId")
private int postId;
@Expose
@SerializedName("id")
private int id;
@Expose
@SerializedName("name")
private String name;
@Expose
@SerializedName("email")
private String email;
@Expose
@SerializedName("body")
private String body;
public Comment(int postId, int id, String name, String email, String body) {
this.postId = postId;
this.id = id;
this.name = name;
this.email = email;
this.body = body;
}
public int getPostId() {
return postId;
}
public void setPostId(int postId) {
this.postId = postId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
RequestApi.java
در اینجا interface methods برای اجرای درخواستهای شبکه با استفاده از Retrofit ارائه شده است.
- ()getPosts لیست پستها را بازیابی میکند.
- ()getComments لیستی از نظرات را برای یک پست خاص بازیابی میکند.
public interface RequestApi {
@GET("posts")
Observable> getPosts();
@GET("posts/{id}/comments")
Observable> getComments(
@Path("id") int id
);
}
ServiceGenerator.java
برای استفاده از Retrofit باید از آن نمونه بگیریم که کلاس ServiceGenerator مسئول ایجاد نمونه Retrofit است.
public class ServiceGenerator {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com";
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = retrofitBuilder.build();
private static RequestApi requestApi = retrofit.create(RequestApi.class);
public static RequestApi getRequestApi(){
return requestApi;
}
}
RecyclerAdapter.java
در این بخش ما یک RecyclerView ایجاد میکنیم.
public class RecyclerAdapter extends RecyclerView.Adapter {
private static final String TAG = "RecyclerAdapter";
private List posts = new ArrayList<>();
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_post_list_item, null, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.bind(posts.get(position));
}
@Override
public int getItemCount() {
return posts.size();
}
public void setPosts(List posts){
this.posts = posts;
notifyDataSetChanged();
}
public void updatePost(Post post){
posts.set(posts.indexOf(post), post);
notifyItemChanged(posts.indexOf(post));
}
public List getPosts(){
return posts;
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView title, numComments;
ProgressBar progressBar;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.title);
numComments = itemView.findViewById(R.id.num_comments);
progressBar = itemView.findViewById(R.id.progress_bar);
}
public void bind(Post post){
title.setText(post.getTitle());
if(post.getComments() == null){
showProgressBar(true);
numComments.setText("");
}
else{
showProgressBar(false);
numComments.setText(String.valueOf(post.getComments().size()));
}
}
private void showProgressBar(boolean showProgressBar){
if(showProgressBar) {
progressBar.setVisibility(View.VISIBLE);
}
else{
progressBar.setVisibility(View.GONE);
}
}
}
}
MainActivity.java
در زیر درون ManiActivity کدهای مربوطه را اضافه میکنیم.
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//ui
private RecyclerView recyclerView;
// vars
private CompositeDisposable disposables = new CompositeDisposable();
private RecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
initRecyclerView();
getPostsObservable()
.subscribeOn(Schedulers.io())
.flatMap(new Function>() {
@Override
public ObservableSource apply(Post post) throws Exception {
return getCommentsObservable(post);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(Post post) {
updatePost(post);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
}
});
}
private Observable getPostsObservable(){
return ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function, ObservableSource>() {
@Override
public ObservableSource apply(final List posts) throws Exception {
adapter.setPosts(posts);
return Observable.fromIterable(posts)
.subscribeOn(Schedulers.io());
}
});
}
private void updatePost(final Post p){
Observable
.fromIterable(adapter.getPosts())
.filter(new Predicate() {
@Override
public boolean test(Post post) throws Exception {
return post.getId() == p.getId();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(Post post) {
Log.d(TAG, "onNext: updating post: " + post.getId() + ", thread: " + Thread.currentThread().getName());
adapter.updatePost(post);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
}
});
}
private Observable getCommentsObservable(final Post post){
return ServiceGenerator.getRequestApi()
.getComments(post.getId())
.map(new Function, Post>() {
@Override
public Post apply(List comments) throws Exception {
int delay = ((new Random()).nextInt(5) + 1) * 1000; // sleep thread for x ms
Thread.sleep(delay);
Log.d(TAG, "apply: sleeping thread " + Thread.currentThread().getName() + " for " + String.valueOf(delay)+ "ms");
post.setComments(comments);
return post;
}
})
.subscribeOn(Schedulers.io());
}
private void initRecyclerView(){
adapter = new RecyclerAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear();
}
}
توجه: همه کدهای این بخش را میتوانید ببینید.
عملگرهای تبدیل کننده : ConcatMap
موارد منتشر شده توسط یک Observable را به Observables تبدیل میکند. این در اصل همان مورد ()FlatMap است، اما نظم ارسال حفظ میشود. اما از آنجا که ()ConcatMap باید منتظر بماند تا هر یک از Observable کار خود را انجام دهند پس از نظر فنی ()ConcatMap غیر همزمان نیست. (نظم ارسال حفظ میشود.)

برای نشان دادن این موضوع ، میخواهیم دقیقاً همان مثالی که در بخش Flatmap انجام دادیم را انجام بدهیم، اما به جای آن از اپراتور ()Concatmap استفاده میکنیم. اگر قبلاً از آن مثال استفاده کرده باشید، نتایج آن واقعاً جالب است.
مثالی از ConcatMap:
تنها کاری که شما باید انجام دهید این است که کدهای موجود در OnCreate of MainActivity را در کد زیر جایگزین کنید.
اگر میخواهید این مثال را درک کنید، باید بخش اپراتور Flatmap را بخوانید.
MainActivity.java
در زیر درون ManiActivity کدهای مربوطه را اضافه میکنیم.
package com.codingwithmitch.rxjavaflatmapexample;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
import android.os.Bundle;
import android.util.Log;
import com.codingwithmitch.rxjavaflatmapexample.models.Comment;
import com.codingwithmitch.rxjavaflatmapexample.models.Post;
import com.codingwithmitch.rxjavaflatmapexample.requests.ServiceGenerator;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//ui
private RecyclerView recyclerView;
// vars
private CompositeDisposable disposables = new CompositeDisposable();
private RecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
initRecyclerView();
getPostsObservable()
.subscribeOn(Schedulers.io())
.concatMap(new Function>() {
@Override
public ObservableSource apply(Post post) throws Exception {
return getCommentsObservable(post);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(Post post) {
updatePost(post);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
}
});
}
private Observable getPostsObservable(){
return ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function, ObservableSource>() {
@Override
public ObservableSource apply(final List posts) throws Exception {
adapter.setPosts(posts);
return Observable.fromIterable(posts)
.subscribeOn(Schedulers.io());
}
});
}
private void updatePost(Post post){
adapter.updatePost(post);
}
private Observable getCommentsObservable(final Post post){
return ServiceGenerator.getRequestApi()
.getComments(post.getId())
.map(new Function, Post>() {
@Override
public Post apply(List comments) throws Exception {
int delay = ((new Random()).nextInt(5) + 1) * 1000; // sleep thread for x ms
Thread.sleep(delay);
Log.d(TAG, "apply: sleeping thread " + Thread.currentThread().getName() + " for " + String.valueOf(delay)+ "ms");
post.setComments(comments);
return post;
}
})
.subscribeOn(Schedulers.io());
}
private void initRecyclerView(){
adapter = new RecyclerAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
@Override
protected void onDestroy() {
super.onDestroy();
disposables.clear();
}
}
حال به جای اینکه کامنتها همه با هم بازیابی شوند و به صورت تصادفی منتشر شوند، به صورت متوالی بازیابی میشوند. بنابراین لیست بعدی نظرات تا منتشر نشدن فهرست قبلی قابل بازیابی نیست.
ستون اول: App launch
ستون دوم: پس از گذشت ۳۰۰۰ میلی ثانیه
ستون سوم: پس از گذشت ۲۰.۰۰۰ میلی ثانیه

این خروجی مربوط به کدهای MainActivity بالا است.
apply: sleeping thread RxCachedThreadScheduler-2 for 4000ms
apply: sleeping thread RxCachedThreadScheduler-2 for 2000ms
apply: sleeping thread RxCachedThreadScheduler-2 for 5000ms
apply: sleeping thread RxCachedThreadScheduler-2 for 4000ms
apply: sleeping thread RxCachedThreadScheduler-2 for 5000ms
بنابراین به طور خلاصه، اپراتور ()Concatmap مشکلی را که اپراتور ()Flatmap دارد، حل میکند. اپراتور ()Flatmap به ترتیب منتشر کردن اشیاء اهمیت نمیدهد. فقط وقتی اشیاء آماده میشوند، آنها را منتشر میکند.
با این حال، اپراتور ()Concatmap باید منتظر باشد تا شیء قبلی منتشر شده سپس حرکت به قسمت بعدی را انجام دهد. بسته به وضعیت شما، این میتواند خوب باشد یا میتواند بد باشد.
عملگرهای تبدیل کننده : SwitchMap
()SwitchMap آیتمهای منتشر شده توسط یک Observable را به یک Observable تبدیل میکند درست مثل ()ConcatMap و ()FlatMap. تفاوت این است که به محض مشترک شدن (Subscribe) یک Observer جدید، Observer قبلی را لغو میکند. ()SwitchMap محدودیتی را حل میکند که ()ConcatMap و ()FlatMap هم دارند.(نظم ارسال حفظ میشود)
نمودار SwitchMap:

همانطور که در تعریف فوق بیان کردیم، ()SwitchMap مشکلی را حل میکند که ()ConcatMap و ()FlatMap هر دو دارای آن هستند. این اطمینان را میدهد که فقط یک Observer میتواند در هر زمان معین Subscribe شود.
حالت ۱:
فرض کنید کاربر بر روی دکمه UI که درخواست شبکه را اجرا میکند، کلیک میکند. اما کاربر به سرعت یک دکمه متفاوت را فشار میدهد که درخواست دوم به شبکه را قبل از اتمام اولین نسخه انجام میدهد. شما میخواهید روند اول را خاتمه دهید و observers را UnSubscribe کنید، سپس مرحله دوم را شروع کنید.
اما این فقط در مورد درخواستهای شبکه صدق نمیکند. هر فرآیند در Background برای اتمام، زمان نیاز دارد، قابل لغو است.
مثال دوم را در نظر بگیرید:
حالت ۲:
مثال دیگر انتخاب یک تصویر یا فیلم است. اگر کاربر روی آن کلیک کند، ممکن است تصویر یا فیلم به صورت تمام صفحه شود و با کیفیت بالاتر ارائه شود. اگر قبل از اینکه مورد اول به طور کامل ارائه شود، یک مورد رسانه مختلف را انتخاب کنید، میخواهید این روند را لغو کنید و یک مورد جدید را شروع کنید.
SwitchMap برای موقعیت هایی مانند این مورد ایده آل است.
مثالی از ()SwitchMap:
نسخهی نمایشی اپلیکیشن به شکل زیر میباشد.
- مرحلهی ۱:
لیستی از پستهای وبلاگ را از آدرس اینترنتی (URL) بازیابی کنید:
/jsonplaceholder.typicode.com/posts
- مرحلهی ۲:
هنگامی که یک مورد لیست کلیک میشود، درخواست دیگری به json placeholder api ارسال میشود، اما این بار برای یک پست خاص. بنابراین آدرس اینترنتی چیزی شبیه به این خواهد بود: /jsonplaceholder.typicode.com/posts/5 با این کار پست با id = 5 بازیابی میشود.
- مرحلهی ۳:
قبل از اینکه درخواست انجام شود، ما شبیه سازی میکنیم که شبکه ای با سرعت عملکرد پایین یا کند چگونه باشد. ما درخواست را مجبور میکنیم قبل از اجرا ۳۰۰۰ ms منتظر بماند. progress bar بروزرسانی میشود که زمان سپری شده از کلیک یک آیتم لیست را نشان میدهد. سرعت آهستهی شبکه با استفاده از اپراتور ()Interval، اپراتور ()TakeWhile و ()Filter شبیه سازی میشود.
- مرحله ۴:
اگر کاربر قبل از اجرای درخواست شبکه، مورد جدیدی از لیست را کلیک کند، کل فرایند دوباره شروع میشود. اپراتور ()SwitchMap از این امر مراقبت میکند.
- مرحله ۵:
در صورت تکمیل فرآیند (پس از گذشت ۳۰۰۰ میلی ثانیه بدون کلیک بر روی گزینه ای جدید)، درخواست انجام میشود و کاربر به یک Activity جدید که عنوان پست را نشان میدهد، هدایت میشوید.

build.gradle
چک کنید که فایل Build.Gradle شما شبیه کدهای زیر باشد.
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.codingwithmitch.rxjavaflatmapexample"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
def retrofitVersion = "2.5.0"
def rxjava_version = '2.2.7'
def rxandroid_version = '2.1.1'
def recyclerview_version = "1.0.0"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
// RxJava Call Adapter
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rxandroid_version"
// Recyclerview
implementation "androidx.recyclerview:recyclerview:$recyclerview_version"
}
AndroidManifest.xml
دسترسی به اینترنت و تعریف دو Activity را درون فایل AndroidManifest.xml اضافه کنید.
activity_main.xml
در این بخش در Activity_main.xml یک RecyclerView و horizontal Progressbar ایجاد میکنیم.
layout_post_list_item.xml
//This is the layout for the RecyclerView list-items.
activity_view_post.xml
کدهای Layout برای Activity دوم درون activity_view_post.xml است.
Post.java
ما باید برای درخواستهای پست، Data را مدل سازی کنیم که در کد زیر میبینید.
public class Post implements Parcelable {
@SerializedName("userId")
@Expose()
private int userId;
@SerializedName("id")
@Expose()
private int id;
@SerializedName("title")
@Expose()
private String title;
@SerializedName("body")
@Expose()
private String body;
private List comments;
public Post(int userId, int id, String title, String body, List comments) {
this.userId = userId;
this.id = id;
this.title = title;
this.body = body;
this.comments = comments;
}
protected Post(Parcel in) {
userId = in.readInt();
id = in.readInt();
title = in.readString();
body = in.readString();
}
public static final Creator CREATOR = new Creator() {
@Override
public Post createFromParcel(Parcel in) {
return new Post(in);
}
@Override
public Post[] newArray(int size) {
return new Post[size];
}
};
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public List getComments() {
return comments;
}
public void setComments(List comments) {
this.comments = comments;
}
@Override
public String toString() {
return "Post{" +
"userId=" + userId +
", id=" + id +
", title='" + title + ''' +
", body='" + body + ''' +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeInt(id);
dest.writeString(title);
dest.writeString(body);
}
}
RequestApi.java
در اینجا interface methods برای اجرای درخواستهای شبکه با استفاده از Retrofit ارائه شده است.
- ()getPosts لیست پستها را بازیابی میکند.
- ()getComments لیستی از نظرات را برای یک پست خاص بازیابی میکند.
public interface RequestApi {
@GET("posts")
Observable> getPosts();
@GET("posts/{id}")
Observable getPost(
@Path("id") int id
);
}
ServiceGenerator.java
کلاس ServiceGenerator مسئول ایجاد نمونهی Retrofit است.
public class ServiceGenerator {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com";
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());
private static Retrofit retrofit = retrofitBuilder.build();
private static RequestApi requestApi = retrofit.create(RequestApi.class);
public static RequestApi getRequestApi(){
return requestApi;
}
}
RecyclerAdapter.java
یک کلاس RecyclerViewAdapter با یک OnClick interface سفارشی برای تشخیص کلیک به موارد لیست ایجاد میکنیم.
public class RecyclerAdapter extends RecyclerView.Adapter {
private static final String TAG = "RecyclerAdapter";
private List posts = new ArrayList<>();
private OnPostClickListener onPostClickListener;
public RecyclerAdapter(OnPostClickListener onPostClickListener) {
this.onPostClickListener = onPostClickListener;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_post_list_item, null, false);
return new MyViewHolder(view, onPostClickListener);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.bind(posts.get(position));
}
@Override
public int getItemCount() {
return posts.size();
}
public void setPosts(List posts){
this.posts = posts;
notifyDataSetChanged();
}
public void updatePost(Post post){
posts.set(posts.indexOf(post), post);
notifyItemChanged(posts.indexOf(post));
}
public List getPosts(){
return posts;
}
public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
OnPostClickListener onPostClickListener;
TextView title;
public MyViewHolder(@NonNull View itemView, OnPostClickListener onPostClickListener) {
super(itemView);
title = itemView.findViewById(R.id.title);
this.onPostClickListener = onPostClickListener;
itemView.setOnClickListener(this);
}
public void bind(Post post){
title.setText(post.getTitle());
}
@Override
public void onClick(View v) {
onPostClickListener.onPostClick(getAdapterPosition());
}
}
public interface OnPostClickListener{
void onPostClick(int position);
}
}
ViewPostActivity.java
در ViewPostActivity کدهای مربوطه را اضافه کنید.
public class ViewPostActivity extends AppCompatActivity {
private static final String TAG = "ViewPostActivity";
private TextView text;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_post);
text = findViewById(R.id.text);
getIncomingIntent();
}
private void getIncomingIntent(){
if(getIntent().hasExtra("post")){
Post post = getIntent().getParcelableExtra("post");
text.setText(post.getTitle());
}
}
}
MainActivity.java
- ما از یک شیء PublishSubject استفاده میکنیم تا تشخیص دهیم که یک Post Object از لیست انتخاب شده است. وقتی تنظیم شود، فرایند ایجاد Observable را شروع میکند. میتوانید متد Override شده onPostClick را مشاهده کنید.
- سپس ما از عملگر SwitchMap استفاده میکنیم و یک
- ()takeWhile() ،Interval و ()Filter برای شبیه سازی یک اتصال ضعیف به شبکه هستند. آنها progress bar را تنظیم میکنند و زمان سپری شده را ردیابی میکنند.
- اگر زمان سپری شود، اپراتور ()FlapMap کار را با تبدیل یک Long Object (حاصل عملگر Interval) به یک Observable Object پایان میدهد.
- و در آخر، Object Post به Observer منتقل میشود.
- بنابراین هر بار که یک پست جدید از لیست انتخاب میشود، فرآیند به دلیل وجود عملگر ()SwitchMap دوباره شروع میشود.
public class MainActivity extends AppCompatActivity implements RecyclerAdapter.OnPostClickListener {
private static final String TAG = "MainActivity";
//ui
private RecyclerView recyclerView;
private ProgressBar progressBar;
// vars
private CompositeDisposable disposables = new CompositeDisposable();
private RecyclerAdapter adapter;
private PublishSubject publishSubject = PublishSubject.create(); // for selecting a post
private static final int PERIOD = 100;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recycler_view);
progressBar = findViewById(R.id.progress_bar);
initRecyclerView();
retrievePosts();
}
private void initSwitchMapDemo(){
publishSubject
// apply switchmap operator so only one Observable can be used at a time.
// it clears the previous one
.switchMap(new Function>() {
@Override
public ObservableSource apply(final Post post) throws Exception {
return Observable
// simulate slow network speed with interval + takeWhile + filter operators
.interval(PERIOD, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.takeWhile(new Predicate() { // stop the process if more than 5 seconds passes
@Override
public boolean test(Long aLong) throws Exception {
Log.d(TAG, "test: " + Thread.currentThread().getName() + ", " + aLong);
progressBar.setMax(3000 - PERIOD);
progressBar.setProgress(Integer.parseInt(String.valueOf((aLong * PERIOD) + PERIOD)));
return aLong <= (3000 / PERIOD);
}
})
.filter(new Predicate() {
@Override
public boolean test(Long aLong) throws Exception {
return aLong >= (۳۰۰۰ / PERIOD);
}
})
// flatmap to convert Long from the interval operator into a Observable
.subscribeOn(Schedulers.io())
.flatMap(new Function>() {
@Override
public ObservableSource apply(Long aLong) throws Exception {
return ServiceGenerator.getRequestApi()
.getPost(post.getId());
}
});
}
})
.subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(Post post) {
Log.d(TAG, "onNext: done.");
navViewPostActivity(post);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
}
});
}
private void retrievePosts(){
ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer>() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(List posts) {
adapter.setPosts(posts);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
}
});
}
@Override
protected void onResume() {
super.onResume();
progressBar.setProgress(0);
initSwitchMapDemo();
}
private void initRecyclerView(){
adapter = new RecyclerAdapter(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
private void navViewPostActivity(Post post){
Intent intent = new Intent(this, ViewPostActivity.class);
intent.putExtra("post", post);
startActivity(intent);
}
@Override
protected void onPause() {
Log.d(TAG, "onPause: called.");
disposables.clear();
super.onPause();
}
@Override
public void onPostClick(final int position) {
Log.d(TAG, "onPostClick: clicked.");
// submit the selected post object to be queried
publishSubject.onNext(adapter.getPosts().get(position));
}
}
توجه: همه کدهای این بخش را میتوانید ببینید.
جمع بندی:
در مقالهی آموزش کامل RxJava در اندروید ، به طور کلی با RxJava و RxAndroid آشنا شدیم. ما با ویژگیها و کاراییهای RxJava و RxAndroid در مثال هایی آشنا شدیم .شما با این آموزش و مثالهای آن از یک توسعه دهنده تازه کار به توسعه دهنده متوسط در RxJava تبدیل میشوید. اگر در این زمینه تجربهای یا سوالی دارید خوشحال میشویم که با ما و کاربران وب سایت سون لرن به اشتراک بگذارید.
بیشتر بدانید:
یاد گیری برنامه نویسی اندروید و پیش نیازهای آن
روش کسب درآمد از برنامه نویسی اندروید
برنامه نویسی اندروید چیست؟
منابع این مقاله:
codingwithmitch.com
github.com
Reactivx.io
اگر به یادگیری بیشتر در زمینهی برنامه نویسی اندروید علاقه داری، با شرکت در دورهی آموزشی متخصص اندروید در کمتر از یکسال به یک توسعهدهنده اندروید همه فن حریف تبدیل میشوی که آمادهی استخدام، دریافت پروژه و حتی پیادهسازی اپلیکیشن خودت هستی.
چه امتیازی به این مقاله می دید؟