I am trying to learn RxJava and was implementing a simple app where I am loading a list of posts from JSONPlaceholder and for each post I am loading the comments by calling another api and updating the posts adapter. The problem is onNext() method of the post observable is getting called only once and only one posts comments is getting loaded.
Here is my activity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private RecyclerView rv;
private PostsAdapter adapter;
private CompositeDisposable disposables = new CompositeDisposable();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rv = findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this,
RecyclerView.VERTICAL, false));
adapter = new PostsAdapter(this);
rv.setAdapter(adapter);
getPostObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap(new Function<Post, ObservableSource<Post>>() {
#Override
public ObservableSource<Post> apply(Post post) throws Exception {
return getCommentsObservable(post);
}
}).subscribe(new Observer<Post>() {
#Override
public void onSubscribe(Disposable d) {
disposables.add(d);
}
#Override
public void onNext(Post post) {
Log.d(TAG, "onNext: called");
updatePost(post);
}
#Override
public void onError(Throwable e) {
Log.d(TAG, "onError: ", e);
}
#Override
public void onComplete() {
Log.d(TAG, "onComplete: called");
}
});
}
private void updatePost(Post post) {
adapter.updatePost(post);
}
private Observable<Post> getPostObservable() {
return ApiClient.getApi()
.getPosts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.flatMap((Function<List<Post>, ObservableSource<Post>>) posts -> {
adapter.setPosts(posts);
return Observable.fromIterable(posts)
.subscribeOn(Schedulers.io());
});
}
private Observable<Post> getCommentsObservable(final Post post) {
return ApiClient.getApi()
.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;
Thread.sleep(delay);
post.setComments(comments);
return post;
}
})
.subscribeOn(Schedulers.io());
}
#Override
protected void onDestroy() {
super.onDestroy();
disposables.clear();
}
}
Here is the Adapter
public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.ViewHolder> {
private final String TAG = "nexa_" + this.getClass().getSimpleName();
private Context context;
private List<Post> dataList = new ArrayList<>();
public PostsAdapter(Context context) {
this.context = context;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
return new ViewHolder(
LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_posts, parent, false)
);
}
public void setPosts(List<Post> posts) {
this.dataList = posts;
notifyDataSetChanged();
}
public void updatePost(Post post) {
dataList.set(dataList.indexOf(post), post);
notifyItemChanged(dataList.indexOf(post));
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
if (dataList.isEmpty())
return;
holder.bind(dataList.get(position));
}
#Override
public int getItemCount() {
return dataList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tvNumber, tvDesc;
ProgressBar progressBar;
public ViewHolder(#NonNull View itemView) {
super(itemView);
tvNumber = itemView.findViewById(R.id.tv_number);
tvDesc = itemView.findViewById(R.id.tv_desc);
progressBar = itemView.findViewById(R.id.progressbar);
}
public void bind(Post post) {
tvDesc.setText(post.getBody());
if (post.getComments() == null) {
toggleProgressbar(true);
tvNumber.setText("");
} else {
toggleProgressbar(false);
tvNumber.setText(String.valueOf(post.getComments().size()));
}
}
void toggleProgressbar(boolean show) {
if (show) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
}
}
}
ApiClient class
public class ApiClient {
public static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
private static Retrofit retrofit = null;
public static ApiInterface getApi() {
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
.build();
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addInterceptor(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.header("Authorization", "");
Request request = requestBuilder.build();
return chain.proceed(request);
}
})
.build();
if (retrofit == null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
}
return retrofit.create(ApiInterface.class);
}
}
ApiInterface class
public interface ApiInterface {
#GET("posts")
Observable<List<Post>> getPosts();
#GET("posts/{id}/comments")
Observable<List<Comment>> getComments(
#Path("id") int id
);
}
The problem was i was calling getCommentsObservable() which is a network call from background thread. I added observeOn(AndroidSchedulers.mainThread()) and it is working now.
private Observable<Post> getCommentsObservable(final Post post) {
return ApiClient.getApi()
.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;
Thread.sleep(delay);
post.setComments(comments);
return post;
}
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()); // ADDED THIS LINE
}
Related
I am removing an item in PagedListAdapter. I am not using room, simply I store a cached list inMemoryElements of all the elements I have loaded. When I remove one item, I just delete that item from the list, and then call DataSource.invalidate(), which calls again LoadInitialCallback.onResult() where I pass my cached list.
Everything works fine the first time I delete one item, but the second time I delete other item, I get:
java.lang.IndexOutOfBoundsException: Index: 18, Size: 18 at java.util.ArrayList.get(ArrayList.java:437) at androidx.paging.PagedStorage.get(PagedStorage.java:174) at androidx.paging.PagedStorageDiffHelper$1.areItemsTheSame(PagedStorageDiffHelper.java:77)
Theese are my components:
In my Fragment, where I have the paginated RecyclerView, here I call the ViewModel method makeCallGetArticleListPaging for loading the elements into LiveData wrap. I then call function getPagedList for subscribing to LiveData<PagedList<ArticleDto>> values.
In DataSourceFactory: I have a reference of current datasource for calling .invalidate()
Reposiroty: where I create the pagedList.
Finally when item is deleted, I call ViewModel method invalidateDatasource()
DataSource:
public class ArticleDataSource extends PageKeyedDataSource<Integer, ArticleDto> {
ApiService apiService;
private List<ArticleDto> inMemoryElements;
public ArticleDataSource(ApiService apiService, String apiServiceMethod, List<ArticleDto> inMemoryElements, int currentPage) {
this.apiService = apiService;
this.inMemoryElements = inMemoryElements;
this.currentPage = currentPage;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, ArticleDto> callback) {
queryArticleDto.setPageIndex(currentPage);
HashMap<String,String> headers = new HashMap<String, String>();
headers.put("Content-Type", HttpConstants.HTTP_CONTENT_TYPE);
if(inMemoryElements.isEmpty()){
apiService.search(headers,queryArticleDto).enqueue(new Callback<PageResultDto<ArticleDto>>() {
#Override
public void onResponse(Call<PageResultDto<ArticleDto>> call, Response<PageResultDto<ArticleDto>> response) {
inMemoryElements.addAll(response.body().getElements());
callback.onResult(response.body().getElements(), null, 1);
}
#Override
public void onFailure(Call<PageResultDto<ArticleDto>> call, Throwable t) {
callback.onResult(new ArrayList<>(), null, 0);
}
});
}else{
callback.onResult(inMemoryElements,null,currentPage + 1);
}
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull final LoadCallback<Integer, ArticleDto> callback) {
int currentPage = params.key;
queryArticleDto.setPageIndex(currentPage);
apiService.search(headers,queryArticleDto).enqueue(new Callback<PageResultDto<ArticleDto>>() {
#Override
public void onResponse(Call<PageResultDto<ArticleDto>> call, Response<PageResultDto<ArticleDto>> response) {
if (response.isSuccessful()) {
inMemoryElements.addAll(response.body().getElements());
callback.onResult(response.body().getElements(), currentPage + 1);
}
}
#Override
public void onFailure(Call<PageResultDto<ArticleDto>> call, Throwable t) {
callback.onResult(new ArrayList<>(), currentPage);
}
});
}
}
DataSourceFactory:
public class ArticleDataSourceFactory extends DataSource.Factory<Integer, ArticleDto> {
//To perform network calls
private ApiService apiService;
private UserSessionLiveData userSessionLiveData;
private ArticleDto queryArticleDto;
private ArticleDataSource articleDataSource;
#Inject
public ArticleDataSourceFactory(ApiService apiService, UserSessionLiveData userSessionLiveData) {
this.apiService = apiService;
this.userSessionLiveData = userSessionLiveData;
}
public void setQueryArticleDto(ArticleDto queryArticleDto) {
this.queryArticleDto = queryArticleDto;
if(articleDataSource != null)
articleDataSource.setQueryArticleDto(queryArticleDto);
}
public void setApiServiceMethod(String apiServiceMethod) {
this.apiServiceMethod = apiServiceMethod;
}
//Factory method pattern implemented below
//Where a create method does the job of initializing the objects for client
#NonNull
#Override
public DataSource<Integer, ArticleDto> create() {
articleDataSource = new ArticleDataSource(apiService,
apiServiceMethod,
userSessionLiveData,
articleDataSource == null ? new ArrayList<>() : articleDataSource.getInMemoryElements(),
articleDataSource == null ? 0 : articleDataSource.getCurrentPage()
);
articleDataSource.setQueryArticleDto(queryArticleDto);
return articleDataSource;
}
public ArticleDataSource getArticleDataSource() {
return articleDataSource;
}}
PagedList:
public class ArticleRepository {
public LiveData<PagedList<ArticleDto>> getPagedList(ArticleDto articleDto, String apiServiceMethod) {
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPageSize(5)
.setPrefetchDistance(1)
.setMaxSize(10)
.build();
return new LivePagedListBuilder<>(articleDataSourceFactory, config)
.setFetchExecutor(Executors.newFixedThreadPool(5)) // Use five threads to do the fetching operations
.build();
}}
ViewModel:
public class ArticleViewModel extends ViewModel {
private final MutableLiveData<PagedList<ArticleDto>> _pagedList = new MutableLiveData<>();
private LiveData<PagedList<ArticleDto>> pagedList = (LiveData<PagedList<ArticleDto>>) _pagedList;
public void makeCallGetArticleListPaging(ArticleDto articleDto, String apiServiceMethod){
pagedList = articleRepository.getPagedList(articleDto,apiServiceMethod);
}
public LiveData<PagedList<ArticleDto>> getPagedList() {
return pagedList;
}}
Fragment:
public class ArticleListFragment extends Fragment {
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
articleViewModel.makeCallGetArticleListPaging(articleDto, ArticleDataSource.API_SERVICE_METHOD_SEARCH);
observeData();
}
private void observeData() {
articleViewModel.getPagedList().observe(getViewLifecycleOwner(), new Observer<PagedList<ArticleDto>>() {
#Override
public void onChanged(PagedList<ArticleDto> posts) {
postsPagedListAdapter.submitList(posts);
}
});
}
public void invalidateDatasource(int rowPosition){
articleDataSourceFactory.getArticleDataSource().getInMemoryElements().remove(rowPosition);
articleDataSourceFactory.getArticleDataSource().invalidate();
}}
DiffUtil.ItemCallback
new DiffUtil.ItemCallback<ArticleDto>() {
#Override
public boolean areItemsTheSame(#NonNull ArticleDto oldItem, #NonNull ArticleDto newItem) {
return oldItem.id == newItem.id;
}
#Override
public boolean areContentsTheSame(#NonNull ArticleDto oldItem, #NonNull ArticleDto newItem) {
return oldItem.equals(newItem);
}
};
I am trying to fetch data from server and showing it in recycler view.I am using retrofit library and RxJava2 but its unable to fetch data from server.
Its showing following line in LogCat:
E/RecyclerView: No adapter attached; skipping layout
Response form the server:
[
{
"term_id": "4",
"name": "Entertainment"
},
{
"term_id": "5",
"name": "Tech & Gadgets"
},
{
"term_id": "6",
"name": "Sports"
},
{
"term_id": "7",
"name": "Health and Fitness Tips"
}
]
Below is my code:
RetrofitClient.java
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getInstance(){
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(22,TimeUnit.SECONDS)
.readTimeout(22, TimeUnit.SECONDS)
.writeTimeout(22, TimeUnit.SECONDS)
.build();
if(retrofit == null)
retrofit = new Retrofit.Builder()
.baseUrl("https://www.flypped.com/api/")
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
return retrofit;
}
}
ApiService.class
public interface ApiService {
#GET("Categoery_api")
Observable<List<Model>> getData();
}
Model.java
public class Model {
#SerializedName("catId")
#Expose
String catId;
#SerializedName("catName")
#Expose
String catName;
public Model(){
}
public Model(String catId, String catName) {
this.catId = catId;
this.catName = catName;
}
public String getCatId() {
return catId;
}
public void setCatId(String catId) {
this.catId = catId;
}
public String getCatName() {
return catName;
}
public void setCatName(String catName) {
this.catName = catName;
}
}
MainActivity.java
private void fetchData(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService myApi = retrofit.create(ApiService.class);
myApi.getData().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<List<Model>>() {
#Override
public void onSubscribe(Disposable d) {
d.dispose();
}
#Override
public void onNext(List<Model> models) {
if(models.size() > 0){
progress.setVisibility(View.INVISIBLE);
adapter = new PostAdapter(getApplicationContext(),list);
recycler.setAdapter(adapter);
}
}
#Override
public void onError(Throwable e) {
Toast.makeText(getApplicationContext(),e.getMessage(),Toast.LENGTH_SHORT).show();
}
#Override
public void onComplete() {
}
});
}
PostAdapter.java
public class PostAdapter extends RecyclerView.Adapter<PostAdapter.ViewHolder> {
List<Model> list = new ArrayList<>();
Context context;
public PostAdapter(Context context,List<Model> list) {
this.context = context;
this.list = list;
}
#NonNull
#Override
public PostAdapter.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_row,parent,false);
ViewHolder view = new ViewHolder(v);
return view;
}
#Override
public void onBindViewHolder(#NonNull PostAdapter.ViewHolder holder, int position) {
Model model = list.get(position);
holder.catName.setText(model.getCatName());
holder.catId.setText(model.getCatId());
}
#Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView catId,catName,comp,titl;
public ViewHolder(#NonNull View itemView) {
super(itemView);
catId = itemView.findViewById(R.id.catId);
catName = itemView.findViewById(R.id.catName);
}
}
}
Someone please let me know what I am doing wrong any help would be appreciated.
THANKS
You are calling d.dispose(); in onSubscribe which will dispose the resource and result so remove dispose call as
public void onSubscribe(Disposable d) {
//d.dispose(); remove it
}
you can move dispose in onDestroy to free up resources(JIC request is still running) when activity(or fragment) is going to be removed from memory and make sure you have layout manager set on recycler view.
I am trying to make a call to this api and am having difficulty as the response.body() is returning null.
http://demo.museum.vebrary.vn/api/stuff/getall
I want to get stuff name of list and show to my recyclerview.
My model:
public class SOAnswersResponse {
#SerializedName("StuffModels")
#Expose
private List<StuffModel> stuffModels = null;
public List<StuffModel> getStuffModels() {
return stuffModels;
}
public void setStuffModels(List<StuffModel> stuffModels) {
this.stuffModels = stuffModels;
}
and
public class StuffModel {
#SerializedName("STUFFID")
#Expose
private Integer sTUFFID;
#SerializedName("STUFFCODE")
#Expose
private String sTUFFCODE;
#SerializedName("STUFFNAME")
#Expose
private String sTUFFNAME;
#SerializedName("STUFFNOTE")
#Expose
private String sTUFFNOTE;
#SerializedName("STUFFORDER")
#Expose
private Integer sTUFFORDER;
#SerializedName("CUSTOMERID")
#Expose
private String cUSTOMERID;
#SerializedName("EXHIBITS")
#Expose
private List<Object> eXHIBITS = null;
Json response
{
"StuffModels":[
{
"STUFFID":2,
"STUFFCODE":"Gi",
"STUFFNAME":"Giấy",
"STUFFNOTE":"",
"STUFFORDER":2,
"CUSTOMERID":"CAMAU",
"EXHIBITS":[
]
},
ApiUtils Class
public class ApiUtils {
private ApiUtils() {
}
public static final String BASE_URL = "http://demo.museum.vebrary.vn/api/";
public static SOService getSOService() {
return RetrofitClient.getClient(BASE_URL).create(SOService.class);
}
}
Service interface
public interface SOService {
#GET("/stuff/getall")
Call<SOAnswersResponse> getAnswers();
}
RetrofitClient Class
public class RetrofitClient {
private static Retrofit retrofit = null;
public static Retrofit getClient(String baseUrl) {
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
My RecyclerView adapter
public class CategogyNameRecyclerViewAdapter extends RecyclerView.Adapter<CategogyNameRecyclerViewAdapter.ViewHolder> {
private List<StuffModel> mItems;
private Context mContext;
private PostItemListener mItemListener;
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView titleTv;
PostItemListener mItemListener;
public ViewHolder(View itemView, PostItemListener postItemListener) {
super(itemView);
titleTv = itemView.findViewById(R.id.tvListMenuCategogy);
this.mItemListener = postItemListener;
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
StuffModel item = getItem(getAdapterPosition());
this.mItemListener.onPostClick(item.getSTUFFID());
notifyDataSetChanged();
}
}
public CategogyNameRecyclerViewAdapter(Context context, List<StuffModel> posts, PostItemListener itemListener) {
mItems = posts;
mContext = context;
mItemListener = itemListener;
}
#Override
public CategogyNameRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
LayoutInflater inflater = LayoutInflater.from(context);
View postView = inflater.inflate(R.layout.item_list_text, parent, false);
ViewHolder viewHolder = new ViewHolder(postView, this.mItemListener);
return viewHolder;
}
#Override
public void onBindViewHolder(CategogyNameRecyclerViewAdapter.ViewHolder holder, int position) {
StuffModel item = mItems.get(position);
TextView textView = holder.titleTv;
textView.setText(item.getSTUFFNAME());
}
#Override
public int getItemCount() {
return mItems.size();
}
public void updateAnswers(List<StuffModel> items) {
mItems = items;
notifyDataSetChanged();
}
private StuffModel getItem(int adapterPosition) {
return mItems.get(adapterPosition);
}
public interface PostItemListener {
void onPostClick(long id);
}
}
And my main activity
public class Testttt extends AppCompatActivity {
private CategogyNameRecyclerViewAdapter mAdapter;
private RecyclerView mRecyclerView;
private SOService mService;
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
setContentView(R.layout.test );
mService = ApiUtils.getSOService();
mRecyclerView = (RecyclerView) findViewById(R.id.rcvCategogyNameMenuTest);
mAdapter = new CategogyNameRecyclerViewAdapter(this, new ArrayList<StuffModel>(0), new CategogyNameRecyclerViewAdapter.PostItemListener() {
#Override
public void onPostClick(long id) {
Toast.makeText(Testttt.this, "Post id is" + id, Toast.LENGTH_SHORT).show();
}
});
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
RecyclerView.ItemDecoration itemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST);
mRecyclerView.addItemDecoration(itemDecoration);
loadAnswers();
}
public void loadAnswers() {
mService.getAnswers().enqueue(new Callback<SOAnswersResponse>() {
#Override
public void onResponse(Call<SOAnswersResponse> call, Response<SOAnswersResponse> response) {
Toast.makeText(Testttt.this, "333333333333333333"+response.body(), Toast.LENGTH_SHORT).show();
if(response.isSuccessful()) {
mAdapter.updateAnswers(response.body().getStuffModels());
Log.d("AnswersPresenter", "posts loaded from API");
}else {
int statusCode = response.code();
}
}
#Override
public void onFailure(Call<SOAnswersResponse> call, Throwable t) {
showErrorMessage();
Log.d("AnswersPresenter", "error loading from API");
}
});
}
public void showErrorMessage() {
Toast.makeText(this, "Error loading posts", Toast.LENGTH_SHORT).show();
}
}
The first thing that came in my mind:
Your
public static final String BASE_URL = "http://demo.museum.vebrary.vn/api/";
has a "/" at the the end and your
#GET("/stuff/getall")
Call<SOAnswersResponse> getAnswers();
starts with a "/". So there is a double backslash in the url that might leads to the 404 code. Does this solve the problem?
When i call your URL i receive XML. Maybe the API is not configured correctly?
Change your Service interface
public interface SOService {
#GET("stuff/getall")
Call<SOAnswersResponse> getAnswers();
}
it occurred because you have use start with backslash it already added in your base url
Good day, so my problem as follows.
When I initialize the retrofit call it has following flow.
1.In the Presenterclass, it goes to getData method.
2.Then it goes to Interactorclass and creates a instance of itself.
3.Then it starts to fetch data from getMovieList method.
4.When it gets to call.enqueue it says OnDataCallback is null.
5.Goes back to Presenter and returns a null List.
6.It resumes call.enqueue does the api call as required.
7.Returns to Presenter and returns the List of objects.
I have tried so many things but non seem to work, maybe I'm understanding something incorrectly ,any help would be appreciated thanks.
Interactor Class.
public class MovieListInteractor implements IMovieContarctor.MovieListInteractor {
private InteractorListener mListener;
private List<MovieListModel> mList;
public MovieListInteractor(InteractorListener mListener) {
this.mListener = mListener;
}
public int getMovieSize() {
return mList.size();
}
#Override
public String getMoviePosterPath(int pos) {
return mList.get(pos).getPosterPath();
}
#Override
public MovieListModel getMovie(int pos) {
return mList.get(pos);
}
#Override
public List<MovieListModel> getMovieList() {
if (mList == null)
mList = new ArrayList<>();
ServerMovieCall mCall = new ServerMovieCall();
mCall.getMovieList(new IOnDataCallback<List<MovieListModel>>() {
#Override
public void onSuccess(List<MovieListModel> data) {
mList.addAll(data);
}
#Override
public void onFailure(String message) {
Log.d(TAG, "onFailure: " + message);
}
});
return mList;
}
}
Presenter class
public class MovieListPresenter implements IMovieContarctor.MovieListPresenter, InteractorListener {
private MovieListFragment mView;
private MovieListInteractor mInteractor;
private List<MovieListModel> mList;
public MovieListPresenter(MovieListFragment mView) {
this.mView = mView;
mInteractor = new MovieListInteractor(this);
getData();
}
//TODO: Add functionality so that the view could call mView.refreshAdapterList
//TODO: And from the view - adapter.notifiyDataSetChange;
#Override
public void onSuccess(List<MovieListModel> data, String msg) {
mView.onDataChange(data, msg);
}
#Override
public void onFailure(String msg) {
mView.onDataFailure(msg);
}
#Override
public void bindAtPosition(int position, MovieListViewHolder holder) {
holder.setValue(mInteractor.getMovie(position));
}
#Override
public void getData() {
mList = mInteractor.getMovieList();
}
#Override
public String getMoviePosterPath(int pos) {
return mInteractor.getMoviePosterPath(pos);
}
#Override
public int getSize() {
return mInteractor.getMovieSize();
}
}
Adapter
public class MovieListAdapter extends RecyclerView.Adapter<MovieListViewHolder> {
private MovieListPresenter mPresenter;
private Context ctx;
public MovieListAdapter(MovieListPresenter mPresenter, Context context) {
this.mPresenter = mPresenter;
this.ctx = context;
}
#Override
public MovieListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new MovieListViewHolder(LayoutInflater.from(ctx).inflate(R.layout.main_view_movie_list, parent, false));
}
#Override
public void onBindViewHolder(MovieListViewHolder holder, int position) {
mPresenter.bindAtPosition(position, holder);
String image_url = IMAGE_URL_BASE_PATH + mPresenter.getMoviePosterPath(position);
Picasso.with(ctx).load(image_url).placeholder(android.R.drawable.sym_def_app_icon)
.error(android.R.drawable.sym_def_app_icon).into(holder.mImage);
}
#Override
public int getItemCount() {
return mPresenter.getSize();
}
}
MovieCall class
public class ServerMovieCall {
private Retrofit retrofit;
private static ServerMovieCall mInstance;
private Context ctx;
public static ServerMovieCall getInstance() {
if (mInstance == null) {
mInstance = new ServerMovieCall();
}
return mInstance;
}
public ServerMovieCall() {
configureClient();
}
private void configureClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging).addInterceptor(new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
HttpUrl url = request.url().newBuilder().addQueryParameter("api_key", Constants.API_KEY).build();
request = request.newBuilder().url(url).build();
return chain.proceed(request);
}
}).build();
retrofit = new Retrofit.Builder().baseUrl(Constants.API_BASE_URL).client(client)
.addConverterFactory(GsonConverterFactory.create()).build();
}
public void getMovieList(final IOnDataCallback<List<MovieListModel>> onDataCallback) {
IServerMovieApiHelper iMovieReviewApiService = retrofit.create(IServerMovieApiHelper.class);
Call<MovieListResponse> call = iMovieReviewApiService.getMovieList();
call.enqueue(new Callback<MovieListResponse>() {
#Override
public void onResponse(Call<MovieListResponse> call, Response<MovieListResponse> response) {
if (response.isSuccessful()) {
onDataCallback.onSuccess(response.body().getMovies());
} else {
onDataCallback.onFailure(response.message());
}
}
#Override
public void onFailure(Call<MovieListResponse> call, Throwable t) {
onDataCallback.onFailure(t.getLocalizedMessage());
}
});
}
Contract interfaces
public interface IMovieContarctor {
public interface MovieListInteractor {
List<MovieListModel> getMovieList();
MovieListModel getMovie(int pos);
String getMoviePosterPath(int pos);
}
public interface MovieListPresenter {
void bindAtPosition(int position, MovieListViewHolder holder);
String getMoviePosterPath(int pos);
int getSize();
void getData();
}
public interface MovieListView {
void onDataChange(List<MovieListModel> data, String message);
void onDataFailure(String message);
}
}
DataCallback Listener.
public interface IOnDataCallback<T> {
void onSuccess(T data);
void onFailure(String message);
}
In Interactor class:
#Override
public List<MovieListModel> getMovieList() {
if (mList == null)
mList = new ArrayList<>();
ServerMovieCall mCall = new ServerMovieCall();
mCall.getMovieList(new IOnDataCallback<List<MovieListModel>>() {
#Override
public void onSuccess(List<MovieListModel> data) {
mList.addAll(data);
mListener.onSuccess(mList, "Your message");
}
#Override
public void onFailure(String message) {
Log.d(TAG, "onFailure: " + message);
}
});
return mList;
}
In Presenter class:
#Override
public void onSuccess(List<MovieListModel> data, String msg) {
mList = data;
mView.onDataChange(data, msg);
}
I can normally get and show data on Toast message, but I cannot add them to RecyclerView. How can I do this? Please help me. Thanks. I am sharing my code below.
NewsCatalog.java
public class NewsCatalog {
public List<MainNodes> nodes;
}
MainNodes.java
public class MainNodes {
public SubNodes node;
}
SubNodes.java
public class SubNodes {
public String body;
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
NewsInterface
public interface NewsService {
String BASE_URL = "http://test.muhabirce.de/app/term/";
#GET("saglik")
Call<NewsCatalog> newsCatalog();
}
Adapter.java
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
private ArrayList<SubNodes> subNodes;
private Context context;
public NewsAdapter(ArrayList<SubNodes> subNodes, Context context) {
this.subNodes = subNodes;
this.context = context;
}
#Override
public NewsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context)
.inflate(R.layout.card_view_items, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(NewsAdapter.ViewHolder holder, int position) {
holder.tvText.setText(subNodes.get(position).getBody());
}
#Override
public int getItemCount() {
return subNodes.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView tvText;
public ViewHolder(View itemView) {
super(itemView);
tvText = (TextView) itemView.findViewById(R.id.tvText);
}
}
}
MainActivity.java
private void bindDatas() {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(NewsService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
NewsService newsService = retrofit.create(NewsService.class);
Call<NewsCatalog> requestCatalog = newsService.newsCatalog();
requestCatalog.enqueue(new Callback<NewsCatalog>() {
#Override
public void onResponse(Call<NewsCatalog> call, Response<NewsCatalog> response) {
SubNodes subNodess = new SubNodes();
if (!response.isSuccessful()) {
Log.i("TAG", "Error: " + response.code());
} else {
NewsCatalog catalog = response.body();
try {
for (MainNodes m : catalog.nodes
) {
subNodess.setBody(m.node.body);
}
} catch (Exception e) {
Log.i(TAG, "" + e.getMessage());
}
}
subNodes.add(subNodess);
adapter.notifyDataSetChanged();
}
#Override
public void onFailure(Call<NewsCatalog> call, Throwable t) {
Log.e(TAG, "Error: " + t.getMessage());
}
});
}
}
You should not create a new Subnodes object in response callback. Adapter does not have reference to this object and so the changes in data is not reflected. Update the subnodes object in the recyclerview adapter. Then only notifyDataSetChanged() will reflect the change in data.