RxJava has so many operators. In order to use them correctly, we must know about them. In our 2nd part, we will discuss the use of Map and FlatMap operators. If you missed my 1st tutorial on RxJava, then you can click here to read it first.
Map
Map transforms the items emitted by an observable by applying a function or condition to each of them.
FlatMap
FlatMap transforms the item emitted by an observable into observables. This means that the FlatMap mapper returns an observable itself, so it is used to map over the asynchronous operations.
This example will explain further, how we can use FlatMap and Map. We have a list of posts and each post has comments. After loading the post data, we will load comments on the post by using an asynchronous operation.
1- Open the Android Studio and create a new project
2- Make sure the below dependencies are added or add them in your build.gradle.xml project file and sync Gradle.
// RxJava Call Adapter
implementation "com.squareup.retrofit2:adapter-rxjava2:$rx_retrofit_adapter"
// RxAndroid
implementation "io.reactivex.rxjava2:rxandroid:$rx_android_version"
// Retrofit
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
// RxJava
implementation "io.reactivex.rxjava2:rxjava:$rxjava_version"
3– Enable the data binding and sync the Gradle again. If you have no knowledge of data binding then you can read about data binding from here.
buildFeatures {
dataBinding true
}
4- Navigate to res->values and open the colors.xml file and add the below lines.
<!-- App colors -->
<color name="color_bg">#121212</color>
<color name="color_tv">#00FFFF</color>
<color name="color_dark_black">#1D1D1D</color>
5- In the Java folder, right-click on the package and create a Java class called ResComment.java and add the below code.
package com.example.androidrxjavaexamplepart_2.network.response;
public class ResComment {
private int postId;
private int id;
private String name;
private String email;
private String 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;
}
}
6- Create a new Java class called ResPost.java and add the below code
package com.example.androidrxjavaexamplepart_2.network.response;
import java.util.List;
public class ResPost {
private int userId;
private int id;
private String title;
private String body;
private List<ResComment> commentList;
private boolean isCommentLoaded;
public boolean isCommentLoaded() {
return isCommentLoaded;
}
public void setCommentLoaded(boolean commentLoaded) {
isCommentLoaded = commentLoaded;
}
public List<ResComment> getCommentList() {
return commentList;
}
public void setCommentList(List<ResComment> commentList) {
this.commentList = commentList;
isCommentLoaded = true;
}
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;
}
}
7- Go to res->layout and create a new layout resource file called adapter_post.xml. This will represent how the row will look. Add the below code.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="post"
type="com.example.androidrxjavaexamplepart_2.network.response.ResPost" />
</data>
<androidx.cardview.widget.CardView
android:id="@+id/cardListing"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="5dp"
android:minHeight="140dp"
app:cardBackgroundColor="@color/color_dark_black"
app:cardCornerRadius="5dp"
app:cardMaxElevation="5dp"
app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:id="@+id/tvUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@{post.title}"
android:textColor="@color/color_tv"
android:layout_marginEnd="5dp"
app:layout_constraintEnd_toStartOf="@+id/commentsLoadingBar"
android:textSize="18sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvUserAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{post.body}"
android:textColor="@color/white"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvUsername" />
<ProgressBar
android:id="@+id/commentsLoadingBar"
android:layout_width="25dp"
android:layout_height="25dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
8- Create a new Java class called AdapterPost.java and add the below code.
package com.example.androidrxjavaexamplepart_2;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.example.androidrxjavaexamplepart_2.databinding.AdapterPostsBinding;
import com.example.androidrxjavaexamplepart_2.network.response.ResPost;
import java.text.DecimalFormat;
import java.util.List;
public class AdapterPosts extends RecyclerView.Adapter<AdapterPosts.PostViewHolder> {
private List<ResPost> resPostList;
private Context _context;
public AdapterPosts(List<ResPost> resPostList) {
this.resPostList = resPostList;
}
@NonNull
@Override
public PostViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new PostViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.adapter_posts, parent, false));
}
@Override
public void onBindViewHolder(@NonNull PostViewHolder holder, int position) {
ResPost resPost = resPostList.get(position);
holder.adapterPostsBinding.setPost(resPost);
holder.adapterPostsBinding.commentsLoadingBar.setVisibility(resPost.isCommentLoaded() ? View.GONE : View.VISIBLE);
}
@Override
public int getItemCount() {
return resPostList.size();
}
protected class PostViewHolder extends RecyclerView.ViewHolder {
private AdapterPostsBinding adapterPostsBinding;
public PostViewHolder(AdapterPostsBinding adapterPostsBinding) {
super(adapterPostsBinding.getRoot());
this.adapterPostsBinding = adapterPostsBinding;
}
}
public void setAllPosts(List<ResPost> list) {
this.resPostList = list;
notifyDataSetChanged();
}
public void updatePost(ResPost resPost) {
resPostList.set(resPostList.indexOf(resPost), resPost);
notifyItemChanged(resPostList.indexOf(resPost));
}
}
9- Create an Interface Java file called ApiService.java and add the below code.
package com.example.androidrxjavaexamplepart_2;
import com.example.androidrxjavaexamplepart_2.network.response.ResComment;
import com.example.androidrxjavaexamplepart_2.network.response.ResPost;
import java.util.List;
import io.reactivex.Observable;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface ApiService {
@GET("/posts")
Observable<List<ResPost>> getAllPosts();
@GET("/posts/{postId}/comments")
Observable<List<ResComment>> getAllCommentsByPostId(@Path("postId") int postId);
}
10- Go to res->layout and open the activity_main.xml file and add the below code
<?xml version="1.0" encoding="utf-8"?>
<layout>
<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"
android:background="@color/color_bg"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvHeading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/app_name"
android:textColor="@color/white"
android:textSize="20sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcvListing"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvHeading" />
<ProgressBar
android:id="@+id/loadingBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
11- Open the MainActivity.java file and add the below code. I added comments on each method. If you get confused then you can ask by posting comments in the comments section.
package com.example.androidrxjavaexamplepart_2;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.example.androidrxjavaexamplepart_2.databinding.ActivityMainBinding;
import com.example.androidrxjavaexamplepart_2.network.response.ResPost;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.ObservableSource;
import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.disposables.CompositeDisposable;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;
public class MainActivity extends AppCompatActivity {
private ApiService apiService;
private AdapterPosts adapterPosts;
private List<ResPost> resPostList;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private ActivityMainBinding activityMainBinding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
initAPiService();
initComponents();
}
/**
* init components
*/
private void initComponents() {
resPostList = new ArrayList<>();
adapterPosts = new AdapterPosts(resPostList);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
activityMainBinding.rcvListing.setLayoutManager(linearLayoutManager);
activityMainBinding.rcvListing.setAdapter(adapterPosts);
Observable<ResPost> postObservable = loadingAllPost().
subscribeOn(Schedulers.io()).
observeOn(AndroidSchedulers.mainThread()).
flatMap((Function<ResPost, ObservableSource<ResPost>>) this::getCommentsByPostId);
postObservable.subscribe(new Observer<ResPost>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
compositeDisposable.add(d);
}
@Override
public void onNext(@NonNull ResPost resPost) {
runOnUiThread(() -> adapterPosts.updatePost(resPost));
}
@Override
public void onError(@NonNull Throwable e) {
}
@Override
public void onComplete() {
}
});
}
/**
* load all posts
* @return return the observable
*/
private Observable<ResPost> loadingAllPost() {
return apiService.getAllPosts().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap((Function<List<ResPost>, ObservableSource<ResPost>>) resPosts -> {
runOnUiThread(() -> {
activityMainBinding.loadingBar.setVisibility(View.GONE);
adapterPosts.setAllPosts(resPosts);
});
return Observable.fromIterable(resPosts)
.subscribeOn(Schedulers.io());
});
}
/**
*
* @param post get the post object response and load the comment of this post via post id
* @return the observable
*/
private Observable<ResPost> getCommentsByPostId(ResPost post) {
return apiService.getAllCommentsByPostId(post.getId()).subscribeOn(Schedulers.io())
.map(resComments -> {
int delay = ((new Random()).nextInt(10) + 1) * 1000; // put the delay for loading comments
Thread.sleep(delay);
post.setCommentList(resComments);
return post;
}).subscribeOn(Schedulers.io());
}
/**
* init the api service object
*/
private void initAPiService() {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build();
apiService = retrofit.create(ApiService.class);
}
@Override
protected void onDestroy() {
super.onDestroy();
compositeDisposable.clear();
}
}
If anything is missed then you can get it by downloading the source code from the below link.
Download Source Code
Thanks for reading the tutorial. Subscribe to my YouTube channel. Like and Share my Facebook page with your friends.