Skip to content

Commit

Permalink
L10: RecyclerView with multiple view types
Browse files Browse the repository at this point in the history
  • Loading branch information
AOrobator committed Mar 4, 2019
1 parent e37d4d2 commit a3f7878
Show file tree
Hide file tree
Showing 19 changed files with 517 additions and 53 deletions.
15 changes: 15 additions & 0 deletions lesson10/Lesson10_RecyclerView.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,21 @@ protected void onCreate(Bundle savedInstanceState) {
}
```

## Handling multiple view types

In the `AnswersActivity`, create a RecyclerView where the adapter has 2 view types, a `Question` and
an `Answer`. You'll have to override `getItemViewType` in your adapter. The first item should be the
`Question` and the remaining items should be the `Answer`s. I'd also recommend making an abstract
`ViewHolder` that the 2 view types extend from. That way you can declare your adapter like:

```java
public class AnswersRecyclerAdapter extends RecyclerView.ViewHolder<YourAbstractViewHolderClass> { ... }
```

Your end product should look something like this:

![Answers View][answers_view]

[StackOverflow]: StackOverflow.jpg "StackOverflow"
[StackOverflow API]: https://api.stackexchange.com/docs
[answers_view]: answers_view.jpg "Answers View"
Binary file added lesson10/answers_view.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion lesson10/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".answers.AnswersActivity"/>
<activity android:name=".answers.view.AnswersActivity"/>
<activity android:name=".questions.view.QuestionsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.orobator.helloandroid.lesson10.answers.di;

import com.orobator.helloandroid.common.AppSchedulers;
import com.orobator.helloandroid.lesson10.answers.viewmodel.AnswersViewModelFactory;
import com.orobator.helloandroid.stackoverflow.answers.AnswersApi;
import com.orobator.helloandroid.stackoverflow.answers.AnswersRepository;
import com.orobator.helloandroid.stackoverflow.answers.AnswersRepositoryImpl;
Expand All @@ -18,4 +20,11 @@ public AnswersApi provideAnswersApi(Retrofit retrofit) {
public AnswersRepository provideAnswersRepository(AnswersApi answersApi) {
return new AnswersRepositoryImpl(answersApi);
}

@Provides
public AnswersViewModelFactory provideAnswersViewModelFactory(
AnswersRepository answersRepository,
AppSchedulers schedulers) {
return new AnswersViewModelFactory(answersRepository, schedulers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.orobator.helloandroid.lesson10.answers.view;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.orobator.helloandroid.lesson10.R;
import com.orobator.helloandroid.lesson10.answers.viewmodel.AnswerViewModel;
import com.orobator.helloandroid.lesson10.answers.viewmodel.AnswersViewModel;
import com.orobator.helloandroid.lesson10.answers.viewmodel.AnswersViewModelFactory;
import com.orobator.helloandroid.lesson10.databinding.ActivityAnswersBinding;
import com.orobator.helloandroid.lesson10.questions.viewmodel.QuestionViewModel;
import com.orobator.helloandroid.stackoverflow.questions.Question;
import dagger.android.AndroidInjection;
import java.util.List;
import javax.inject.Inject;

public class AnswersActivity extends AppCompatActivity implements Observer<List<AnswerViewModel>> {
private static final String KEY_QUESTION = "question";

public static Intent getIntent(Context context, Question question) {
Intent intent = new Intent(context, AnswersActivity.class);
intent.putExtra(KEY_QUESTION, question);
return intent;
}

private AnswersRecyclerAdapter adapter;

@Inject AnswersViewModelFactory viewModelFactory;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidInjection.inject(this);

ActivityAnswersBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_answers);

AnswersViewModel viewModel =
ViewModelProviders
.of(this, viewModelFactory)
.get(AnswersViewModel.class);

Question question = getIntent().getParcelableExtra(KEY_QUESTION);
viewModel.setQuestion(question);

viewModel.getAnswerViewModelsLiveData().observe(this, this);

adapter = new AnswersRecyclerAdapter(new QuestionViewModel(question));
binding.answersRecyclerView.setAdapter(adapter);
}

@Override public void onChanged(List<AnswerViewModel> answerViewModels) {
adapter.updateList(answerViewModels);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.orobator.helloandroid.lesson10.answers.view;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.orobator.helloandroid.lesson10.R;
import com.orobator.helloandroid.lesson10.answers.view.AnswersRecyclerAdapter.QuestionDetailViewHolder;
import com.orobator.helloandroid.lesson10.answers.viewmodel.AnswerViewModel;
import com.orobator.helloandroid.lesson10.databinding.ListItemAnswerBinding;
import com.orobator.helloandroid.lesson10.databinding.ListItemQuestionDetailBinding;
import com.orobator.helloandroid.lesson10.questions.viewmodel.QuestionViewModel;
import java.util.Collections;
import java.util.List;

public class AnswersRecyclerAdapter extends RecyclerView.Adapter<QuestionDetailViewHolder> {
private static final int VIEW_TYPE_QUESTION = 0;
private static final int VIEW_TYPE_ANSWER = 1;

private List<AnswerViewModel> answerViewModels = Collections.emptyList();
private final QuestionViewModel questionViewModel;

public AnswersRecyclerAdapter(QuestionViewModel questionViewModel) {
this.questionViewModel = questionViewModel;
}

@Override public int getItemViewType(int position) {
if (position == 0) {
return VIEW_TYPE_QUESTION;
} else {
return VIEW_TYPE_ANSWER;
}
}

@Override public int getItemCount() {
return 1 + answerViewModels.size();
}

@NonNull @Override
public QuestionDetailViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_QUESTION) {
return onCreateQuestionViewHolder(parent);
} else {
return onCreateAnswerViewHolder(parent);
}
}

private QuestionViewHolder onCreateQuestionViewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ListItemQuestionDetailBinding binding =
DataBindingUtil.inflate(inflater, R.layout.list_item_question_detail, parent, false);
return new QuestionViewHolder(binding);
}

private QuestionDetailViewHolder onCreateAnswerViewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ListItemAnswerBinding binding =
DataBindingUtil.inflate(inflater, R.layout.list_item_answer, parent, false);
return new AnswerViewHolder(binding);
}

@Override public void onBindViewHolder(@NonNull QuestionDetailViewHolder holder, int position) {
if (getItemViewType(position) == VIEW_TYPE_QUESTION) {
((QuestionViewHolder) holder).bind(questionViewModel);
} else {
((AnswerViewHolder) holder).bind(answerViewModels.get(position - 1));
}
}

public void updateList(List<AnswerViewModel> answerViewModels) {
this.answerViewModels = answerViewModels;
notifyDataSetChanged();
}

static abstract class QuestionDetailViewHolder extends RecyclerView.ViewHolder {
public QuestionDetailViewHolder(@NonNull View itemView) {
super(itemView);
}
}

static class QuestionViewHolder extends QuestionDetailViewHolder {
private final ListItemQuestionDetailBinding binding;

public QuestionViewHolder(ListItemQuestionDetailBinding binding) {
super(binding.getRoot());

this.binding = binding;
}

public void bind(QuestionViewModel questionViewModel) {
binding.setVm(questionViewModel);
}
}

static class AnswerViewHolder extends QuestionDetailViewHolder {
private final ListItemAnswerBinding binding;

public AnswerViewHolder(ListItemAnswerBinding binding) {
super(binding.getRoot());

this.binding = binding;
}

public void bind(AnswerViewModel answerViewModel) {
binding.setVm(answerViewModel);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.orobator.helloandroid.lesson10.answers.viewmodel;

import android.text.Spanned;
import androidx.core.text.HtmlCompat;
import com.orobator.helloandroid.stackoverflow.answers.Answer;

import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY;

public class AnswerViewModel {
public final Spanned body;
public final String voteCount;
public final String userName;

public AnswerViewModel(Answer answer) {
body = HtmlCompat.fromHtml(answer.body, FROM_HTML_MODE_LEGACY);
voteCount = Integer.toString(answer.score);
userName = answer.owner.getDisplayName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.orobator.helloandroid.lesson10.answers.viewmodel;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.orobator.helloandroid.common.AppSchedulers;
import com.orobator.helloandroid.observableviewmodel.ObservableViewModel;
import com.orobator.helloandroid.stackoverflow.answers.Answer;
import com.orobator.helloandroid.stackoverflow.answers.AnswersRepository;
import com.orobator.helloandroid.stackoverflow.answers.AnswersResponse;
import com.orobator.helloandroid.stackoverflow.questions.Question;
import io.reactivex.disposables.Disposable;
import java.util.ArrayList;
import java.util.List;

public class AnswersViewModel extends ObservableViewModel {
private final AnswersRepository answersRepository;
private final AppSchedulers schedulers;
private Disposable disposable;
private MutableLiveData<List<AnswerViewModel>> answerViewModelsLiveData = new MutableLiveData<>();

public AnswersViewModel(
AnswersRepository answersRepository,
AppSchedulers schedulers) {
this.answersRepository = answersRepository;
this.schedulers = schedulers;
}

public void setQuestion(Question question) {
disposable = answersRepository
.getAnswersForQuestion(question.getQuestionId())
.subscribeOn(schedulers.io)
.observeOn(schedulers.main)
.subscribe(this::onGetAnswersSuccess, this::onGetAnswersError);
}

public LiveData<List<AnswerViewModel>> getAnswerViewModelsLiveData() {
return answerViewModelsLiveData;
}

private void onGetAnswersSuccess(AnswersResponse response) {
List<AnswerViewModel> answerViewModels = new ArrayList<>(response.answers.size());

for (Answer answer : response.answers) {
answerViewModels.add(new AnswerViewModel(answer));
}

answerViewModelsLiveData.setValue(answerViewModels);
}

private void onGetAnswersError(Throwable throwable) {
// Ignored for simplicity. See Lesson 9 for error handling
}

@Override protected void onCleared() {
super.onCleared();

if (disposable != null) {
disposable.dispose();
disposable = null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.orobator.helloandroid.lesson10.answers.viewmodel;

import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.orobator.helloandroid.common.AppSchedulers;
import com.orobator.helloandroid.stackoverflow.answers.AnswersRepository;

public class AnswersViewModelFactory implements ViewModelProvider.Factory {
private final AnswersRepository answersRepository;
private final AppSchedulers schedulers;

public AnswersViewModelFactory(
AnswersRepository answersRepository,
AppSchedulers schedulers) {
this.answersRepository = answersRepository;
this.schedulers = schedulers;
}

@NonNull @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new AnswersViewModel(answersRepository, schedulers);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.orobator.helloandroid.lesson10.di;

import com.orobator.helloandroid.lesson10.answers.AnswersActivity;
import com.orobator.helloandroid.lesson10.answers.di.AnswersActivityModule;
import com.orobator.helloandroid.lesson10.answers.view.AnswersActivity;
import com.orobator.helloandroid.lesson10.questions.di.QuestionsActivityModule;
import com.orobator.helloandroid.lesson10.questions.view.QuestionsActivity;
import dagger.Module;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.orobator.helloandroid.lesson10.R;
import com.orobator.helloandroid.lesson10.answers.AnswersActivity;
import com.orobator.helloandroid.lesson10.answers.view.AnswersActivity;
import com.orobator.helloandroid.lesson10.databinding.ActivityQuestionsBinding;
import com.orobator.helloandroid.lesson10.questions.viewmodel.QuestionsViewModel;
import com.orobator.helloandroid.lesson10.questions.viewmodel.QuestionsViewModelFactory;
Expand Down
Loading

0 comments on commit a3f7878

Please sign in to comment.