diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e10d54..9e379c4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/MainActivity.java b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/MainActivity.java index 8120e60..1e8243c 100644 --- a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/MainActivity.java +++ b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/MainActivity.java @@ -3,7 +3,9 @@ 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; @@ -12,27 +14,36 @@ import io.reactivex.functions.Function; import io.reactivex.functions.Predicate; import io.reactivex.schedulers.Schedulers; +import io.reactivex.subjects.PublishSubject; + + +import android.content.Intent; import android.os.Bundle; import android.util.Log; +import android.widget.ProgressBar; -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; +import java.util.concurrent.TimeUnit; + -public class MainActivity extends AppCompatActivity { +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 @@ -40,18 +51,52 @@ 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(); + } - getPostsObservable() - .subscribeOn(Schedulers.io()) - .flatMap(new Function>() { + 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(Post post) throws Exception { - return getCommentsObservable(post); + 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 >= (3000 / 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()); + } + }); } }) - .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer() { @Override public void onSubscribe(Disposable d) { @@ -60,7 +105,8 @@ public void onSubscribe(Disposable d) { @Override public void onNext(Post post) { - updatePost(post); + Log.d(TAG, "onNext: done."); + navViewPostActivity(post); } @Override @@ -70,57 +116,90 @@ public void onError(Throwable e) { @Override public void onComplete() { + } }); } - private Observable getPostsObservable(){ - return ServiceGenerator.getRequestApi() + private void retrievePosts(){ + 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); + .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() { + + } + }); } - 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()); - + @Override + protected void onResume() { + super.onResume(); + progressBar.setProgress(0); + initSwitchMapDemo(); } private void initRecyclerView(){ - adapter = new RecyclerAdapter(); + 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 onDestroy() { - super.onDestroy(); + 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)); } } + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/RecyclerAdapter.java b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/RecyclerAdapter.java index 839aa6f..1515c27 100644 --- a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/RecyclerAdapter.java +++ b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/RecyclerAdapter.java @@ -19,12 +19,17 @@ public class RecyclerAdapter extends RecyclerView.Adapter 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); + return new MyViewHolder(view, onPostClickListener); } @Override @@ -52,42 +57,35 @@ public List getPosts(){ return posts; } - public class MyViewHolder extends RecyclerView.ViewHolder{ + public class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { - TextView title, numComments; - ProgressBar progressBar; + OnPostClickListener onPostClickListener; + TextView title; - public MyViewHolder(@NonNull View itemView) { + public MyViewHolder(@NonNull View itemView, OnPostClickListener onPostClickListener) { super(itemView); title = itemView.findViewById(R.id.title); - numComments = itemView.findViewById(R.id.num_comments); - progressBar = itemView.findViewById(R.id.progress_bar); + + this.onPostClickListener = onPostClickListener; + + itemView.setOnClickListener(this); } 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); - } + @Override + public void onClick(View v) { + onPostClickListener.onPostClick(getAdapterPosition()); } } -} + public interface OnPostClickListener{ + void onPostClick(int position); + } +} diff --git a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/ViewPostActivity.java b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/ViewPostActivity.java new file mode 100644 index 0000000..d3382da --- /dev/null +++ b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/ViewPostActivity.java @@ -0,0 +1,51 @@ +package com.codingwithmitch.rxjavaflatmapexample; + +import android.os.Bundle; +import android.widget.TextView; + + +import com.codingwithmitch.rxjavaflatmapexample.models.Post; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +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()); + } + } +} + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/models/Post.java b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/models/Post.java index dc0348b..e880d24 100644 --- a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/models/Post.java +++ b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/models/Post.java @@ -1,11 +1,14 @@ package com.codingwithmitch.rxjavaflatmapexample.models; +import android.os.Parcel; +import android.os.Parcelable; + import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import java.util.List; -public class Post { +public class Post implements Parcelable { @SerializedName("userId") @Expose() @@ -33,6 +36,25 @@ public Post(int userId, int id, String title, String body, List comment 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; } @@ -82,4 +104,17 @@ public String toString() { ", 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); + } } diff --git a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/requests/RequestApi.java b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/requests/RequestApi.java index de08754..d81ca00 100644 --- a/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/requests/RequestApi.java +++ b/app/src/main/java/com/codingwithmitch/rxjavaflatmapexample/requests/RequestApi.java @@ -5,9 +5,7 @@ import java.util.List; -import io.reactivex.Flowable; import io.reactivex.Observable; -import okhttp3.ResponseBody; import retrofit2.http.GET; import retrofit2.http.Path; @@ -16,8 +14,8 @@ public interface RequestApi { @GET("posts") Observable> getPosts(); - @GET("posts/{id}/comments") - Observable> getComments( + @GET("posts/{id}") + Observable getPost( @Path("id") int id ); } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0b0e52a..11ad072 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,17 +1,28 @@ - + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_view_post.xml b/app/src/main/res/layout/activity_view_post.xml new file mode 100644 index 0000000..fae9e74 --- /dev/null +++ b/app/src/main/res/layout/activity_view_post.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_post_list_item.xml b/app/src/main/res/layout/layout_post_list_item.xml index 74dc20d..bec30e5 100644 --- a/app/src/main/res/layout/layout_post_list_item.xml +++ b/app/src/main/res/layout/layout_post_list_item.xml @@ -3,52 +3,16 @@ android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" - android:weightSum="100" android:padding="20dp"> - - - - - - - - - - - - - - - - - + android:id="@+id/title" + android:text="this is a title" + android:textColor="#000" + android:textSize="17sp" + /> \ No newline at end of file