// RxJava
implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
// RxAndroid
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
Observable<String> animalsObservable = Observable.just("Eagle", "Bee", "Lion", "Dog", "Wolf");
Observer -2 ایجاد کنید که به Observable گوش دهد. Observer متدهای Interface زیر را برای شناخت وضعیت Observable ارائه میدهد.
Observer<String> animalsObserver = getAnimalsObserver();
private Observer<String> getAnimalsObserver() {
return new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String s) {
Log.d(TAG, "Name: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "All items are emitted!");
}
};
}
Observer -3 را در عضویت Observable قرار دهید تا بتواند شروع به دریافت داده کند. در اینجا، شما میتوانید دو روش دیگر، ()ObserveOn و ()subscribeOn را مشاهده کنید.
animalsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(animalsObserver);
اگر برنامه را اجرا کنید، میتوانید خروجی زیر را در LogCat خود مشاهده کنید.
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import info.androidhive.rxandroidexamples.R;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class Example1Activity extends AppCompatActivity {
/**
* Basic Observable, Observer, Subscriber example
* Observable emits list of animal names
*/
private static final String TAG = Example1Activity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example1);
// observable
Observable<String> animalsObservable = getAnimalsObservable();
// observer
Observer<String> animalsObserver = getAnimalsObserver();
// observer subscribing to observable
animalsObservable
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(animalsObserver);
}
private Observer<String> getAnimalsObserver() {
return new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
}
@Override
public void onNext(String s) {
Log.d(TAG, "Name: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "All items are emitted!");
}
};
}
private Observable<String> getAnimalsObservable() {
return Observable.just("Eagle", "Bee", "Lion", "Dog", "Wolf");
}
}
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import info.androidhive.rxandroidexamples.R;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
public class Example2Activity extends AppCompatActivity {
/**
* Basic Observable, Observer, Subscriber example
* Observable emits list of animal names
* You can see Disposable introduced in this example
*/
private static final String TAG = Example2Activity.class.getSimpleName();
private Disposable disposable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example2);
// observable
Observable<String> animalsObservable = getAnimalsObservable();
// observer
Observer<String> animalsObserver = getAnimalsObserver();
// observer subscribing to observable
animalsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(animalsObserver);
}
private Observer<String> getAnimalsObserver() {
return new Observer<String>() {
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe");
disposable = d;
}
@Override
public void onNext(String s) {
Log.d(TAG, "Name: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "All items are emitted!");
}
};
}
private Observable<String> getAnimalsObservable() {
return Observable.just("Eagle", "Bee", "Lion", "Dog", "Wolf");
}
@Override
protected void onDestroy() {
super.onDestroy();
// don't send events once the activity is destroyed
disposable.dispose();
}
}
این کد همان خروجی مشابه نمونه قبلی را تولید میکند.
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import info.androidhive.rxandroidexamples.R;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.observers.DisposableObserver;
import io.reactivex.schedulers.Schedulers;
public class Example4Activity extends AppCompatActivity {
/**
* Basic Observable, Observer, Subscriber example
* Observable emits list of animal names
* You can see filter() operator is used to filter out the
* animal names that starts with letter `b`
*/
private static final String TAG = Example4Activity.class.getSimpleName();
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_example4);
Observable<String> animalsObservable = getAnimalsObservable();
DisposableObserver<String> animalsObserver = getAnimalsObserver();
DisposableObserver<String> animalsObserverAllCaps = getAnimalsAllCapsObserver();
/**
* filter() is used to filter out the animal names starting with `b`
* */
compositeDisposable.add(
animalsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<String>() {
@Override
public boolean test(String s) throws Exception {
return s.toLowerCase().startsWith("b");
}
})
.subscribeWith(animalsObserver));
/**
* filter() is used to filter out the animal names starting with 'c'
* map() is used to transform all the characters to UPPER case
* */
compositeDisposable.add(
animalsObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<String>() {
@Override
public boolean test(String s) throws Exception {
return s.toLowerCase().startsWith("c");
}
})
.map(new Function<String, String>() {
@Override
public String apply(String s) throws Exception {
return s.toUpperCase();
}
})
.subscribeWith(animalsObserverAllCaps));
}
private DisposableObserver<String> getAnimalsObserver() {
return new DisposableObserver<String>() {
@Override
public void onNext(String s) {
Log.d(TAG, "Name: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "All items are emitted!");
}
};
}
private DisposableObserver<String> getAnimalsAllCapsObserver() {
return new DisposableObserver<String>() {
@Override
public void onNext(String s) {
Log.d(TAG, "Name: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: " + e.getMessage());
}
@Override
public void onComplete() {
Log.d(TAG, "All items are emitted!");
}
};
}
private Observable<String> getAnimalsObservable() {
return Observable.fromArray(
"Ant", "Ape",
"Bat", "Bee", "Bear", "Butterfly",
"Cat", "Crab", "Cod",
"Dog", "Dove",
"Fox", "Frog");
}
@Override
protected void onDestroy() {
super.onDestroy();
// don't send events once the activity is destroyed
compositeDisposable.clear();
}
}
اگر این مثال را اجرا کنید، میتوانید خروجی زیر را مشاهده کنید. در اینجا نام حیواناتی که با "B" شروع میشوند توسط animalsObserver ،Observed میشوند و همه نامهای شروع شده با حرف "c" و همه animalsObserverAllCaps با Observed میشوند.
Flowable.range(0, 1000000)
.onBackpressureBuffer()
.observeOn(Schedulers.computation())
.subscribe(new FlowableSubscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable t) {
Log.e(TAG, "onError: ", t);
}
@Override
public void onComplete() {
}
});
Observable<Integer> observable = Observable
.just(1, 2, 3, 4, 5);
Flowable<Integer> flowable = observable.toFlowable(BackpressureStrategy.BUFFER);
Observable<Integer> observable = Observable
.just(1, 2, 3, 4, 5);
Flowable<Integer> flowable = observable.toFlowable(BackpressureStrategy.BUFFER);
Observable<Integer> backToObservable = flowable.toObservable(
// Instantiate the object to become an Observable
final Task task = new Task("Walk the dog", false, 4);
// Create the Observable
Observable<Task> singleTaskObservable = Observable
.create(new ObservableOnSubscribe<Task>() {
@Override
public void subscribe(ObservableEmitter<Task> emitter) throws Exception {
if(!emitter.isDisposed()){
emitter.onNext(task);
emitter.onComplete();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// Subscribe to the Observable and get the emitted object
singleTaskObservable.subscribe(new Observer<Task>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: single task: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
// Create the Observable
Observable<Task> taskListObservable = Observable
.create(new ObservableOnSubscribe<Task>() {
@Override
public void subscribe(ObservableEmitter<Task> emitter) throws Exception {
// Inside the subscribe method iterate through the list of tasks and call onNext(task)
for(Task task: DataSource.createTasksList()){
if(!emitter.isDisposed()){
emitter.onNext(task);
}
}
// Once the loop is complete, call the onComplete() method
if(!emitter.isDisposed()){
emitter.onComplete();
}
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// Subscribe to the Observable and get the emitted objects
taskListObservable.subscribe(new Observer<Task>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Task task) {
Log.d(TAG, "onNext: task list: " + task.getDescription());
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
Observable.just("first", "second", "third", "fourth", "fifth", "sixth",
"seventh", "eighth", "ninth", "tenth")
.subscribeOn(Schedulers.io()) // What thread to do the work on
.observeOn(AndroidSchedulers.mainThread()) // What thread to observe the results on
.subscribe(new Observer<String>() { // view the results by creating a new observer
@Override
public void onSubscribe(Disposable d) {
Log.d(TAG, "onSubscribe: called");
}
@Override
public void onNext(String s) {
Log.d(TAG, "onNext: " + s);
}
@Override
public void onError(Throwable e) {
Log.e(TAG, "onError: ", e);
}
@Override
public void onComplete() {
Log.d(TAG, "onComplete: done...");
}
});
خروجی کد بالا به صورت زیر میباشد.
Observable.range(0,11)
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
Observable.range(0,3)
.repeat(2)
.observeOn(Schedulers.io())
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Integer>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Integer integer) {
Log.d(TAG, "onNext: " + integer);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
final Handler handler = new Handler();
final Runnable runnable = new Runnable() {
int elapsedTime = 0;
@Override
public void run() {
if(elapsedTime >= 5){ // if greater than 5 seconds
handler.removeCallbacks(this);
}
else{
elapsedTime = elapsedTime + 1;
handler.postDelayed(this, 1000);
Log.d(TAG, "run: " + elapsedTime);
}
}
};
handler.postDelayed(runnable, 1000);
خروجی کد بالا به صورت زیر میباشد.
// emit an observable every time interval
Observable<Long> intervalObservable = Observable
.interval(1, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.takeWhile(new Predicate<Long>() { // stop the process if more than 5 seconds passes
@Override
public boolean test(Long aLong) throws Exception {
return aLong <= 5;
}
})
.observeOn(AndroidSchedulers.mainThread());
Subscribe to the Observable :
در این بخش Observable را Subscribe میکنیم.
intervalObservable.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: interval: " + aLong);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
// emit single observable after a given delay
Observable<Long> timeObservable = Observable
.timer(3, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
Subscribe to the Observable :
در این بخش Observable را Subscribe میکنیم.
timeObservable.subscribe(new Observer<Long>() {
long time = 0; // variable for demonstating how much time has passed
@Override
public void onSubscribe(Disposable d) {
time = System.currentTimeMillis() / 1000;
}
@Override
public void onNext(Long aLong) {
Log.d(TAG, "onNext: " + ((System.currentTimeMillis() / 1000) - time) + " seconds have elapsed." );
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
خروجی کد بالا به صورت زیر میباشد.
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 ....
}
Task[] list = new Task[5];
list[0] = (new Task("Take out the trash", true, 3));
list[1] = (new Task("Walk the dog", false, 2));
list[2] = (new Task("Make my bed", true, 1));
list[3] = (new Task("Unload the dishwasher", false, 0));
list[4] = (new Task("Make dinner", true, 5));
Observable<Task> taskObservable = Observable
.fromArray(list)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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() {
}
});)
List<Task> taskList = new ArrayList<>();
taskList.add(new Task("Take out the trash", true, 3));
taskList.add(new Task("Walk the dog", false, 2));
taskList.add(new Task("Make my bed", true, 1));
taskList.add(new Task("Unload the dishwasher", false, 0));
taskList.add(new Task("Make dinner", true, 5));
Observable<Task> taskObservable = Observable
.fromIterable(taskList)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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() {
}
});
Output: T
مثال ()fromCallable: شما باید یک Task Object را از local SQLite database cache برگردانید. کلیهی عملیات پایگاه داده باید بر روی background thread انجام شود. سپس نتیجه به Main Thread بازگردانده میشود.// create Observable (method will not execute yet)
Observable<Task> callable = Observable
.fromCallable(new Callable<Task>() {
@Override
public Task call() throws Exception {
return MyDatabase.getTask();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
// method will be executed since now something has subscribed
callable.subscribe(new Observer<Task>() {
@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() {
}
});
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"
public interface RequestApi {
@GET("todos/1")
Observable<ResponseBody> makeObservableQuery();
}
.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;
}
}
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if(instance == null){
instance = new Repository();
}
return instance;
}
public Future<Observable<ResponseBody>> makeFutureQuery(){
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Callable<Observable<ResponseBody>> myNetworkCallable = new Callable<Observable<ResponseBody>>() {
@Override
public Observable<ResponseBody> call() throws Exception {
return ServiceGenerator.getRequestApi().makeObservableQuery();
}
};
final Future<Observable<ResponseBody>> futureObservable = new Future<Observable<ResponseBody>>(){
@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<ResponseBody> get() throws ExecutionException, InterruptedException {
return executor.submit(myNetworkCallable).get();
}
@Override
public Observable<ResponseBody> get(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
return executor.submit(myNetworkCallable).get(timeout, unit);
}
};
return futureObservable;
}
}
public class MainViewModel extends ViewModel {
private Repository repository;
public MainViewModel() {
repository = Repository.getInstance();
}
public Future<Observable<ResponseBody>> makeFutureQuery(){
return repository.makeFutureQuery();
}
}
MainViewModel viewModel = ViewModelProviders.of(this).get(MainViewModel.class);
try {
viewModel.makeFutureQuery().get()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<ResponseBody>() {
@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();
}
خروجی کد بالا به صورت زیر میباشد.
<uses-permission android:name="android.permission.INTERNET"/>
وابستگی (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"
public interface RequestApi {
@GET("todos/1")
Flowable<ResponseBody> makeQuery();
}
.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;
}
}
public class Repository {
private static Repository instance;
public static Repository getInstance(){
if(instance == null){
instance = new Repository();
}
return instance;
}
public LiveData<ResponseBody> makeReactiveQuery(){
return LiveDataReactiveStreams.fromPublisher(ServiceGenerator.getRequestApi()
.makeQuery()
.subscribeOn(Schedulers.io()));
}
}
public class MainViewModel extends ViewModel {
private Repository repository;
public MainViewModel() {
repository = Repository.getInstance();
}
public LiveData<ResponseBody> makeQuery(){
return repository.makeReactiveQuery();
}
}
Observable<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.filter(new Predicate<Task>() {
@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<Task>() {
@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<Task> createTasksList(){
List<Task> 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<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.filter(new Predicate<Task>() {
@Override
public boolean test(Task task) throws Exception {
return task.isComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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<Task> createTasksList(){
List<Task> 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;
}
خروجی کد بالا به صورت زیر میباشد.
public static List<Task> createTasksList(){
List<Task> 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<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.distinct(new Function<Task, Task>() { // <--- WRONG
@Override
public Task apply(Task task) throws Exception {
return task;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
public static List<Task> createTasksList(){
List<Task> 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<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.distinct(new Function<Task, String>() { // <--- CORRECT
@Override
public String apply(Task task) throws Exception {
return task.getDescription();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
public class DataSource {
public static List<Task> createTasksList(){
List<Task> 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<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.take(3)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
public class DataSource {
public static List<Task> createTasksList(){
List<Task> 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<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.takeWhile(new Predicate<Task>() {
@Override
public boolean test(Task task) throws Exception {
return task.isComplete();
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
taskObservable.subscribe(new Observer<Task>() {
@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
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<Task> createTasksList(){
List<Task> 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;
}
}
Function<Task, String> extractDescriptionFunction = new Function<Task, String>() {
@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<String> extractDescriptionObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(extractDescriptionFunction)
.observeOn(AndroidSchedulers.mainThread());
extractDescriptionObservable.subscribe(new Observer<String>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
Function<Task, Task> completeTaskFunction = new Function<Task, Task>() {
@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<Task> completeTaskObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(completeTaskFunction)
.observeOn(AndroidSchedulers.mainThread());
completeTaskObservable.subscribe(new Observer<Task>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
// Create an Observable using the fromIterable operator
Observable<Task> mappedObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io())
.map(new Function<Task, Task>() {
@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<Task>() {
@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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
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<Task> createTasksList(){
List<Task> 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;
}
}
// Create an Observable
Observable<Task> taskObservable = Observable
.fromIterable(DataSource.createTasksList())
.subscribeOn(Schedulers.io());
taskObservable
.buffer(2) // Apply the Buffer() operator
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Task>>() { // Subscribe and view the emitted results
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(List<Task> 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() {
}
});
خروجی کد بالا به صورت زیر میباشد.
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<Unit, Integer>() { // 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<List<Integer>>() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d); // add to disposables to you can clear in onDestroy
}
@Override
public void onNext(List<Integer> 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();
}
خروجی کد بالا به صورت زیر میباشد.
def rxbinding_version = "4.0.0"
// Rx Binding Library
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbinding_version"
کدهای زیر برای activity_main.xml است:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.appcompat.widget.SearchView
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/search_view">
</androidx.appcompat.widget.SearchView>
</androidx.constraintlayout.widget.ConstraintLayout>
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<String> observableQueryText = Observable
.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(final ObservableEmitter<String> 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<String>() {
@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
}
خروجی کد بالا به صورت زیر میباشد.
def rxbinding_version = "4.0.0"
// Rx Binding Library
implementation "com.jakewharton.rxbinding4:rxbinding:$rxbinding_version"
کدهای زیر برای activity_main.xml است:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/button"
android:text="button"/>
</androidx.constraintlayout.widget.ConstraintLayout>
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<Unit>() {
@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
}
}
خروجی کد بالا به صورت زیر میباشد.
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"
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codingwithmitch.rxjavaflatmapexample">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/recycler_view"
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
//This is the layout for the RecyclerView list-items
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="100"
android:padding="20dp">
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="80"
android:layout_gravity="center_vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/title"
android:text="this is a title"
android:textColor="#000"
android:textSize="17sp"
/>
</RelativeLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="20"
android:layout_gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/num_comments"
android:textSize="14sp"
android:layout_centerInParent="true"/>
<ProgressBar
android:layout_width="20dp"
android:layout_height="20dp"
android:id="@+id/progress_bar"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_centerInParent="true"
android:visibility="gone"/>
</RelativeLayout>
</LinearLayout>
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<Comment> comments;
public Post(int userId, int id, String title, String body, List<Comment> 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<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> comments) {
this.comments = comments;
}
@Override
public String toString() {
return "Post{" +
"userId=" + userId +
", id=" + id +
", title='" + title + '\'' +
", body='" + body + '\'' +
'}';
}
}
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;
}
}
public interface RequestApi {
@GET("posts")
Observable<List<Post>> getPosts();
@GET("posts/{id}/comments")
Observable<List<Comment>> getComments(
@Path("id") int id
);
}
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;
}
}
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
private static final String TAG = "RecyclerAdapter";
private List<Post> 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<Post> posts){
this.posts = posts;
notifyDataSetChanged();
}
public void updatePost(Post post){
posts.set(posts.indexOf(post), post);
notifyItemChanged(posts.indexOf(post));
}
public List<Post> 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);
}
}
}
}
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<Post, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> apply(Post post) throws Exception {
return getCommentsObservable(post);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Post>() {
@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<Post> getPostsObservable(){
return ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<List<Post>, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> apply(final List<Post> 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<Post>() {
@Override
public boolean test(Post post) throws Exception {
return post.getId() == p.getId();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Post>() {
@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<Post> getCommentsObservable(final Post post){
return ServiceGenerator.getRequestApi()
.getComments(post.getId())
.map(new Function<List<Comment>, Post>() {
@Override
public Post apply(List<Comment> 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();
}
}
توجه: همه کدهای این بخش را میتوانید ببینید.
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<Post, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> apply(Post post) throws Exception {
return getCommentsObservable(post);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Post>() {
@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<Post> getPostsObservable(){
return ServiceGenerator.getRequestApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<List<Post>, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> apply(final List<Post> posts) throws Exception {
adapter.setPosts(posts);
return Observable.fromIterable(posts)
.subscribeOn(Schedulers.io());
}
});
}
private void updatePost(Post post){
adapter.updatePost(post);
}
private Observable<Post> getCommentsObservable(final Post post){
return ServiceGenerator.getRequestApi()
.getComments(post.getId())
.map(new Function<List<Comment>, Post>() {
@Override
public Post apply(List<Comment> 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();
}
}
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"
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codingwithmitch.rxjavaflatmapexample">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ViewPostActivity" />
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ProgressBar
android:id="@+id/progress_bar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:padding="10dp"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
//This is the layout for the RecyclerView list-items.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/title"
android:text="this is a title"
android:textColor="#000"
android:textSize="17sp"
/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:padding="15dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
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<Comment> comments;
public Post(int userId, int id, String title, String body, List<Comment> 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<Post> CREATOR = new Creator<Post>() {
@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<Comment> getComments() {
return comments;
}
public void setComments(List<Comment> 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);
}
}
public interface RequestApi {
@GET("posts")
Observable<List<Post>> getPosts();
@GET("posts/{id}")
Observable<Post> getPost(
@Path("id") int id
);
}
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;
}
}
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.MyViewHolder> {
private static final String TAG = "RecyclerAdapter";
private List<Post> 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<Post> posts){
this.posts = posts;
notifyDataSetChanged();
}
public void updatePost(Post post){
posts.set(posts.indexOf(post), post);
notifyItemChanged(posts.indexOf(post));
}
public List<Post> 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);
}
}
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());
}
}
}
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<Post> 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<Post, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> 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<Long>() { // 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<Long>() {
@Override
public boolean test(Long aLong) throws Exception {
return aLong >= (3000 / PERIOD);
}
})
// flatmap to convert Long from the interval operator into a Observable<Post>
.subscribeOn(Schedulers.io())
.flatMap(new Function<Long, ObservableSource<Post>>() {
@Override
public ObservableSource<Post> apply(Long aLong) throws Exception {
return ServiceGenerator.getRequestApi()
.getPost(post.getId());
}
});
}
})
.subscribe(new Observer<Post>() {
@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<List<Post>>() {
@Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
@Override
public void onNext(List<Post> 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 تبدیل میشوید. اگر در این زمینه تجربهای یا سوالی دارید خوشحال میشویم که با ما و کاربران وب سایت سون لرن به اشتراک بگذارید.
بیشتر بدانید:
پیش نیازهای یادگیری برنامه نویسی اندروید
روش کسب درآمد از برنامه نویسی اندروید
برنامه نویسی اندروید چیست؟
اگر به یادگیری بیشتر در زمینهی برنامه نویسی اندروید علاقه داری، با شرکت در دورهی برنامه نویسی اندروید در کمتر از یکسال به یک توسعهدهنده اندروید همه فن حریف تبدیل میشوی که آمادهی استخدام، دریافت پروژه و حتی پیادهسازی اپلیکیشن خودت هستی.
ممنون که با ما همراه هستید.
ممنون که با ما همراه هستید :)
من کلا با آموزشی که مثال زیاد داشته باشه خیلی راحت یاد میگیرم و این مقاله از این نظر خیلی عالی بود و واقعا مفید بود واسم مرسی
درود خوشحالیم که مقاله براتون مفید بوده دوست عزیز :)
الان به نظرتون مثال هاتون به درد کسی هم میخوره ؟ چرا flatmap انقدر ناقص توضیح داده شده ؟ توی انتخاب تولید کنندههای محتوا تون تجدید نظر کنید
درود سعی میکنیم از نظرات تون در تولید محتواهای بعدی استفاده کنیم. ممنون که با ما همراه هستید.
با تشکر از مقاله ارزشمندتون که معلومه هم برای خودتون و هم برای مخاطب ارزش قائل هستید.
درود خوشحالیم مقاله براتون مفید بوده. ممنون که با ما همراه هستید.
عالی بود خلاصه یک کتاب ۵۰۰ صفحه ای رو یکجا گذاشتید از این مدل مقالات جامع بیشتر بزارید
سلام، حتما دوست عزیز
خیلی عالی بود با اینکه دیگه جاوا کار نمیکنم و به دارت سوییچ کردم مباحت مروبط با ری اکت عالی بود و مفاهیمش خیلی به دردم خورد مچکر
سلام دوست عزیز، ممنون از نظرتون موفق باشید.
سلام. با وجود تجربه زیادم توی کد نویسی، همیشه فکر میکردم RX جاوا یه چیز غوله و بسیار سخت و همش ازش فرار میکردم. ولی امروز وقتی این سایت رو پیدا کردم که دیدم رایگان گذاشته و انقدر روان، خیلی خوب یادگرفتم. حداقل مفاهیمش رو. و این باعث شد که اسم سایت شما همیشه توی ذهنم باشه تا هر وقت منبع آموزش فارسی در مورد چیزی خواستم، اول به اینجا سر بزنم. همین روند رو ادامه بدید چون واقعا سورسهای فارسی و سایت هایی که سرشون به تنشون ارزش داشته باشه(!) خیلی کمه. به یکه تازی تون ادامه بدید. SEO تون رو هم قوی و قویتر کنید تا مثل برخی سایتهای فارسی، به ورود به سایت تون عادت کنیم و کنن از بس اولید. موفق باشید
سلام . از این که از مقاله لذت بردین و واستون مفید بوده واقعا خوشحالیم، انشالله در ادامه راه موفق باشی
سلام منبع آموزش درست ذکر نشده است منبع صحیح سایت codingwithmitch.com میباشد با تشکر از سایت خوبتون
سلام مهدیار جان ممنون بابت یاد آوری که کردی فراموش شده بود تصحیح شد.
سلامت باشی وحید جان
ممنون از نظرتون از اینکه واستون مفید بوده خوشحالیم
به بدترین شکل ممکن ترجمه شده کل محتوا انگلیسیه فقط کلماتش فارسی هستن تا حدودی متوجه شدم چون از قبل چیزایی بلد بودم کسی که هیچی ازش ندونه حتما متوجه هیچیش نمیشه !
ممنون از نظرتون دوست عزیر این مقاله خیلی از کلمات انگلیسیش مخصوص هستش و معنای درستی در صورت ترجمه به جمله نمیده سعی میکنیم در بروز رسانیهای بعدی اگه مشکلی بود برطرف کنیم در ضمن کسانی بودند که هیچی نمیدونستند و با همین مقاله یاد گرفتند
خواهش میکنم نظر لطفتون هست
ممنون از نظرتون