I've tried a few different ways of fixing this, but it just doesn't seem to want to work. I have attached the fragment involved. The recyclerview works when I use the search function; However, when I first load the page, I get the error that
E/RecyclerView: No adapter attached; skipping layout
I think it might be an issue with the onCreate vs onCreateView, but I'm not exactly sure what to put where.
NewsFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
v = inflater.inflate(R.layout.fragment_news, container, false);
swipeRefreshLayout = v.findViewById(R.id.swipeRefresh);
recyclerView = v.findViewById(R.id.news_recyclerView);
etQuery = v.findViewById(R.id.etQuery);
btnSearch = v.findViewById(R.id.btnSearch);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
final String country = getCountry();
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson("", country, API_KEY);
}
});
retrieveJson("", country, API_KEY);
btnSearch.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!etQuery.getText().toString().equals("")) {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson(etQuery.getText().toString(), country, API_KEY);
}
});
retrieveJson(etQuery.getText().toString(), country, API_KEY);
} else {
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
retrieveJson("", country, API_KEY);
}
});
retrieveJson("", country, API_KEY);
}
}
});
return v;
}
public void retrieveJson(String query ,String country, String apiKey){
swipeRefreshLayout.setRefreshing(true);
Call<Headlines> call;
if (!etQuery.getText().toString().equals("")){
call=NewsApiClient.getInstance().getApi().getSpecificData(query,apiKey);
}else{
call=NewsApiClient.getInstance().getApi().getHeadlines(country,apiKey);
}
call.enqueue(new Callback<Headlines>() {
#Override
public void onResponse(Call<Headlines> call, Response<Headlines> response) {
if (response.isSuccessful() && response.body().getArticles() != null ){
swipeRefreshLayout.setRefreshing(false);
articles.clear();
articles = response.body().getArticles();
newsAdapter = new NewsAdapter(getContext(), articles);
recyclerView.setAdapter(newsAdapter);
}
}
#Override
public void onFailure(Call<Headlines> call, Throwable t) {
swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public String getCountry(){
Locale locale = Locale.getDefault();
String country = locale.getCountry();
return country.toLowerCase();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
On the onCreateView you are in fact not setting any adapter. You instead set it when retrieving the json data inside the call.enqueue callbacks, this callbacks are called asynchronously and thus the first time the onCreateView is called no adapter is set to the RecyclerView. To avoid that, set an adapter with empty data empty adapter right at the start of the onCreateView function like this (make newsAdapter a variable of the current fragment)
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
newsAdapter = new NewsAdapter(getContext(), articles);
recyclerView.setAdapter(newsAdapter);
make articles be a variable of the current fragment
and insted of setting and adapter on the callbacks of you retriveJson function simply update the articles array with the data and call the notifydatasetchanged function of the RecycleView like this
public void onResponse(Call<Headlines> call, Response<Headlines> response) {
if (response.isSuccessful() && response.body().getArticles() != null ){
swipeRefreshLayout.setRefreshing(false);
articles.clear();
articles = response.body().getArticles();
newsAdapter.setData(articles);
newsAdapter.notifyDataSetChanged()
}
}
setData is a function that you have to create in your NewsAdapter class.
Hope it helped!
And take a look a this brief guide on how to use RecycleViews
Related
I am trying to make a layout with recyclerview something like the video. I made a recyclerview which update list after certain interval but the problem is after data update it scroll to top position automatically. I want to make something like the video. https://youtu.be/omcS-6LeKoo
I have tried with link from SO
RecyclerView scrolls to top position when change the adapter data RecyclerView notifyDataSetChanged scrolls to top position but unable to solve. below is my attempt
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
list.add(mModelClass_adapter);
}
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new
StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<ModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
These lines of code are causing the problem for you. You're setting a new adapter reference and linear layout manager reference every time of your API calling.
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
To Do your desired task you need to do following steps -
Just set your LayoutManager and adapter for the first time.
Make a setDataList method in your adapter class. And set your updated list to adapter list.
And then every time of calling API set that list to setDataList and call adapter.notifyDataSetChanged() method of your adapter class.
The above steps will solve your problem. Just give it a try.
The problem is probably because of you are setting new adapter reference in network callback method onResponse(). Try setting adapter in onCreate and then update dataset in callback.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
recyclerView.setAdapter(yourAdapter);
}
In network callback,
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
adapter.setDataSet(newDataList) //not change adapter reference,only update data set
}
}
Implement setDataSet() method in your adapter to update list like below.
class YourAdapter extends RecyclerView.Adapter<>{
priavate List<> list = new ArrayList();
public void setDataSet(newList:List<>){
list.clear();
list.addAll(newList);
notifyDataSetChanged();
}
}
Don't use adapter.notifyDataSetChanged(); method because I think your main view must be wrap content so either set a fixed height like 150dp.
Try different methods like notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted()
You are setting adapter again and again when the response is changing, so you should change list and set adapter in onCreate.
Arraylist<ModelClass_adapter> list = new Arraylist<ModelClass_adapter>;
Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
//set adapter here
adapter = new Adapter(getApplicationContext(),list);
StaggeredGridLayoutManager layoutManager = new
StaggeredGridLayoutManager(1,StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
list.clear();
list.add(mModelClass_adapter);
}
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<CurrencyModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
You are setting a new adapter every time and a new layout manager response comes.
which may cause this type of problem. you need to set adapter and layout manager in onCreate. just update adapter list in response of the api.
according to this answer you need linear layout manager only.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = findViewById(R.id.recyclerViewId);
list= ArrayList<>();
adapter = new Adapter(getApplicationContext(),list);
LinearLayoutManager linearLayoutManager = new
LinearLayoutManager(context, OrientationHelper.VERTICAL, false);
recycleView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
// Toast.makeText(getApplicationContext(),"Updating",Toast.LENGTH_LONG).show();
listShow();
handler.postDelayed(this,1000);
}
},1000);
}
void listShow(){
retrofitApiCall = RetrofitInstance.getRetrofitInstance().create(RetrofitApiCall.class);
Call<ModelClass_JSONParse> getDetails = retrofitApiCall;
anime = ExtendedAnime.getAll();
getDetails.enqueue(new Callback<ModelClass_JSONParse>() {
#Override
public void onResponse(Call<ModelClass_JSONParse> call,
Response<ModelClass_JSONParse> response) {
Log.v("Res",response.toString());
getWithValues = new HashMap<>();
if (response.isSuccessful()){
adapter.getList().add(mModelClass_adapter);
}
adapter.notifyDataSetChanged();
}
}
#Override
public void onFailure(Call<CurrencyModelClass_JSONParse> call, Throwable t) {
Log.v("Res",call.toString());
}
});
}
you can do by following way
First get the count of your current datalist
int position = datalist.size();
after adding data into datalist
call DataAdapter.notifyDataSetChanged();
then move cursor to position in recyclerview
recyclerView.scrollToPosition(position);
Happy coding...!
I have multiple instances of the same CustomView inside one fragment.
I implemented savedInstance for this CustomView but the problem is since there are multiple instances of this CustomView, savedInstance of the last one, overrides them all.
for example, if there are 3 instances of this CustomView which has a recyclerview inside, If I scroll the last one, it applies to them all. because i'm using key value pairs and the key is the same for all of them. (I can change the key to differ for each one but I think there is a better way)
Here is the code for savedInstance saving and restoring inside my CustomView:
#Nullable
#Override
protected Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(SavedInstanceKey.SUPERSTATE.name(), super.onSaveInstanceState());
bundle.putParcelable(SavedInstanceKey.RECYCLERVIEW.name(), recyclerView.getLayoutManager().onSaveInstanceState()); // ... save stuff
return bundle;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) // implicit null check
{
Bundle bundle = (Bundle) state;
this.recyclerView.getLayoutManager().onRestoreInstanceState(bundle.getParcelable(SavedInstanceKey.RECYCLERVIEW.name())); // ... load stuff
state = bundle.getParcelable(SavedInstanceKey.SUPERSTATE.name());
}
super.onRestoreInstanceState(state);
}
and here is my fragment's OnCreateView:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_artist, container, false);
final GridListView gv_new = view.findViewById(R.id.gridlist_new_songs);
final GridListView gv_best = view.findViewById(R.id.gridlist_best);
final GridListView gv_singles = view.findViewById(R.id.gridlist_singles);
final GridListView gv_feats = view.findViewById(R.id.gridlist_feats);
final RecyclerView rc_albums = view.findViewById(R.id.rcview_album);
if(!alreadyInitialized) {
alreadyInitialized = true;
apiService = new ApiService(getContext());
try {
artistID = getArguments().getString(KeyIntent.ARTIST.name());
} catch (Exception e) {
Log.e(TAG, "onCreateView: Artist Fragment doesnt have args.\t", e);
}
apiService.getArtist(artistID, new ApiService.OnArtistReceived() {
#Override
public void onSuccess(Artist artist) {
ArtistFragment.this.artist=artist;
setArtistToViews(artist, view);
}
#Override
public void onFail() {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getNewSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.newSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_new.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getBestSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.bestSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_best.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Error on receiving artist.", Toast.LENGTH_LONG).show();
}
});
apiService.getSingleSongs(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.singleSongs=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_singles.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getFeats(artistID, new ApiService.OnSongsReceived() {
#Override
public void onSuccess(List<Song> songs) {
ArtistFragment.this.feats=songs;
List<GridListable> gridListables = new ArrayList<>();
gridListables.addAll(songs);
gv_feats.load(gridListables, 1);
}
#Override
public void onFail(ApiService.ApiResponse response) {
}
});
apiService.getAlbums(artistID, new ApiService.OnAlbumsReceived() {
#Override
public void onSuccess(List<Album> albums) {
ArtistFragment.this.albums=albums;
List<Projective> projectives = new ArrayList<>();
projectives.addAll(albums);
rc_albums.setAdapter(new AlbumAdapter(getContext(), projectives));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
#Override
public void onFail(ApiService.ApiResponse response) {
Toast.makeText(getContext(), "Loading albums failed.", Toast.LENGTH_SHORT).show();
}
});
}else {
Log.i(TAG, "onCreateView: Fragment already initialized, restoring from existing artist");
setArtistToViews(artist,view);
gv_new.load(new ArrayList<>(newSongs),1);
gv_best.load(new ArrayList<>(bestSongs),1);
gv_singles.load(new ArrayList<>(singleSongs),1);
gv_feats.load(new ArrayList<>(feats),1);
rc_albums.setAdapter(new AlbumAdapter(getContext(), new ArrayList<>(albums)));
rc_albums.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, true));
}
return view;
}
I think the problem is the Keys that you use for your Bundles. All your instances of the custom view use the same SavedInstanceKey.SUPERSTATE.name().
You could try to have the Fragment pass a different key to each of the custom views (BEST, NEW...). This way, each of your GridView has its own unique key to use in the saveInstanceState and restoreInstanceState methods.
This question already has answers here:
recyclerview No adapter attached; skipping layout
(38 answers)
Closed 4 years ago.
I have a simple program that connect to an API and get some data from it.I use Retrofit library.There are two activity. MainActivity includes a RecyclerView that show data received from the server. If press any item,application go to UserInfoActivity and show detailed information of that item.My app when go UserInfoActivity crashed after a few seconds and don't show information. However response correctly received but the program gives an error
on txtId.setText(user.getId) line. The error is E/RecyclerView: No adapter attached; skipping layout
In fact my problem starts when call Call<User> getUserInfo(#Path("id") int id); in second Activity.
This is MainActivity code.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userRequest();
}
private void userRequest(){
APIInterface apiInterface= APIClient.getClient().create(APIInterface.class);
retrofit2.Call<ArrayList<User>> call= apiInterface.getUsers();
call.enqueue(new Callback<ArrayList<User>>() {
#Override
public void onResponse(retrofit2.Call<ArrayList<User>> call, Response<ArrayList<User>> response) {
if(response.isSuccessful()){
ArrayList<User> users = response.body();
setupRecycelerView(users);
}
}
#Override
public void onFailure(retrofit2.Call<ArrayList<User>> call, Throwable t) {
Log.i("RETROFIT","response not successful");
}
});
}
private void setupRecycelerView(ArrayList<User> users){
recyclerView=findViewById(R.id.RcyView);
adapter=new RecycelerAdapter(this,users);
linearLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
}
This is onBindViewHolder function of RecycelerAdapter.
public void onBindViewHolder(#NonNull UserViewHolder holder, int position) {
final User user=values.get(position);
holder.txtName.setText(user.getName());
holder.txtPhone.setText(user.getPhone());
final Intent intent=new Intent(context, UserInfoActivity.class);
intent.putExtra("user",user);
holder.root.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
context.startActivity(intent);
}
});
}
This is UserInfoActivity code.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_user_info);
txtId=findViewById(R.id.txtId);
txtName=findViewById(R.id.txtName);
txtPhone=findViewById(R.id.txtPhone);
Intent intent=getIntent();
User user=(User)intent.getSerializableExtra("user");
id=user.getId();
userInfoRequest();
}
private void userInfoRequest(){
APIInterface apiInterface= APIClient.getClient().create(APIInterface.class);
retrofit2.Call<User> call= apiInterface.getUserInfo(id);
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
if(response.isSuccessful()){
User user=response.body();
Log.e("MF","ON Response");
Log.e("MF",user.getName());
Log.e("MF",user.getPhone());
txtId.setText(user.getId());
txtName.setText(user.getName());
txtPhone.setText(user.getPhone());
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.e("MF","on failure");
}
});
}
Try this:
linearLayoutManager=new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);
recyclerView.setLayoutManager(linearLayoutManager);
adapter=new RecycelerAdapter(this,users);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
recyclerView.setHasFixedSize(true);
recyclerView.setItemAnimator(new DefaultItemAnimator());
Used notifyDataSetChanged() to tell the RecyclerView that data changed-added and set the Adapter after setLayoutManager.
If this didn't help, initialize the RecyclerView inside onCreate method and set the Adapter in onResponse with runOnUiThread like following:
runOnUiThread(new Runnable() {
#Override
public void run() {
//Do something on UiThread
ArrayList<User> users = response.body();
adapter=new RecycelerAdapter(this,users);
recyclerView.setAdapter(users);
}
});
Then call notifyDataSetChanged() and it should work then.
You're calling the setupRecycelerView on the response of the Retrofit api call, the problem is that till the response comes the RecyclerView doesnt have an adapter or a layoutManager attached to itself, hence the error.
Instead, you can call the setupRecycelerView inside onCreate itself and then create a global object of the Users list. Something like this:
private final ArrayList<Users> users = new ArrayList();
This list would be empty and the adapter would be initialised with the same empty list, then when you get the response from the retrofit api, use:
users.addAll(response.getBody());
this will fill the contents and then call adapter.notifyDataSetChanged(); to make the adapter reflect the updated contents.
This happens because you initialize RecyclerView when/if the response from server is successful.
Initialize it (call setupRecycelerView() ) in onCreate(). And change adapter=new RecycelerAdapter(this,users); to adapter=new RecycelerAdapter(this);
Make your RecycelerAdapter's constructor take one argument only (probably Context)
In your adapter, make a public method that sets the users.
public setUserData(ArrayList<User> users) {
this.values = users;
notifyDataSetChanged();
}
Call RecycelerAdapter.setUserData(users); from Retrofit's response is successful.
Just call setAdapter() after setLayoutManager() in your setupRecycelerView()
I load a recyclerview based on Firebase data via the following method:
#Override
public void onStart() {
super.onStart();
mChildEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
String newPollEpoch = dataSnapshot.getKey();
if (mNewPollsAray.contains(newPollEpoch)) {
Log.v("POLL_ADDED", "POLL ADDED: " + newPollEpoch);
} else {
Log.v("Child_Added", "The new child is " + newPollEpoch);
String newPollImageURL = dataSnapshot.child(IMAGE_URL).getValue(String.class);
//TODO: On additional devices, numbesr are not appearing as the question
String newPollQuestion = dataSnapshot.child(QUESTION_STRING).getValue(String.class);
String convertedQuestion = newPollQuestion.toString();
mNewPollsAray.add(0, new Poll(convertedQuestion, newPollImageURL, newPollEpoch));
mNewPollsAdapter.notifyDataSetChanged();
Log.v("OnChildChanged", "OnCHILDCHANGEDCALLED " + dataSnapshot.getKey());
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mPollsRef.addChildEventListener(mChildEventListener);
}
#Override
public void onStop() {
super.onStop();
mPollsRef.removeEventListener(mChildEventListener);
}
Here is the method I call when an item in the recyclerview is clicked:
#Override
public void onClick(View view) {
view.getId();
int itemPosition = getAdapterPosition();
String passEpoch = mNewPollsAray.get(itemPosition).getPollID();
Log.v("PASSED_ID", "The passed ID is " + passEpoch);
Intent toPoll = new Intent(getActivity(), PollHostActivity.class);
toPoll.putExtra("POLL_ID", passEpoch);
startActivity(toPoll);
}
The fragment I am loading it from is part of a TabLayout. When I navigate between the tabs the recyclerview loads correctly.
However, when I click an item in the recyclerview (which takes me to a new activity) and then navigate back to the fragment containing the recyclerview, items get duplicated and the recyclerview items are all out of order. I think it has to do with onStart() being called multiple times and essentially "stacking" new items onto the recyclerview instead of replacing them, but I was hoping to confirm.
This happens because you add a listener, but never remove it. So the next time when you enter the view, you add a second listener and thus get two calls to onChildAdded() for each item in the database.
The solution is to remove the listener when you exit the view. Since you attach the listener in onStart(), you should remove it again in onStop():
#Override
public void onStop() {
mPollsRef.removeEventListener(mChildEventListener);
}
You can try with code, I was facing similar issue got resolved with bellow changes.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mMainLayout == null)
{
mMainLayout = inflater.inflate(R.layout.fragment_main, container,false);
...
}
return mMainLayout;
}
When mMainlayout is not null, it mean that your fragment instance has already one instance of the mMainLayout and already added to ViewGroup container no need to add it again. You may be facing issue as you are adding same view again to same container.
By Clear the data set you can avoid loading of similar items again in Recycler View. It worked for me.
listOftrailers.clear();
try {
JSONObject jsonObject = new JSONObject(data);
JSONArray jsonArray = jsonObject.getJSONArray("results");
for (int i = 0; i < jsonArray.length(); i++) {
MovieTrailer item = new MovieTrailer();
JSONObject js = jsonArray.getJSONObject(i);
item.setVideoID(js.getString("id"));
item.setVideoName(js.getString("name"));
item.setVideoKey(js.getString("key"));
item.setVideoSite(js.getString("site"));
item.setVideoType(js.getString("type"));
String name = item.getVideoName();
if (name.contains("Official Trailer") ||
name.startsWith("Official"))
listOftrailers.add(item);
}
} catch (JSONException e) {
e.printStackTrace();
}
videosadapter = new TrailerListAdapter(listOftrailers.size(),
listOftrailers, MoviePage.this);
recyclerView.setAdapter(videosadapter);
Some menu in the drawer will open Tab + ViewPager content. Each page (fragment) is list that its data is requested from server.
Every time I click that menu, I want the content will show the tab immediately even the data are still requested instead empty screen. I try to add progress bar in the TabFragment so the content will show a loading when preparing the ViewPager and the pagers' data. But, the content still show an empty screen without loading indicator. I found the problem is because the method to request data from server is called from each pager.
Should I move the method for requesting data to TabFragment?
My TabFragment class looks like:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
content = view.findViewById(R.id.content);
content.setVisibility(View.GONE);
tabLayout = (TabLayout) view.findViewById(R.id.tabs);
viewPager = (ViewPager) view.findViewById(R.id.pager);
progressBar = (ProgressBar) view.findViewById(R.id.progressBar1);
farmerViewPagerAdapter = new FarmerViewPagerAdapter(getChildFragmentManager(), titles);
viewPager.setOffscreenPageLimit(2);
viewPager.setAdapter(farmerViewPagerAdapter);
tabLayout.post(()->{
tabLayout.setupWithViewPager(viewPager);
for (int i = 0; i < titles.length; ++i){
tabLayout.getTabAt(i).setIcon(icons[i]);
}
});
}
And here is fragment for each page (each page requests different data):
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
this.view = view;
emptyView = view.findViewById(R.id.emptyView);
emptyText = (TextView) view.findViewById(R.id.emptyTextView);
recyclerView = (RecyclerView) view.findViewById(R.id.list);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setItemAnimator(new DefaultItemAnimator());
adapter = new FarmerAdapter(data, getContext());
recyclerView.setAdapter(adapter);
recyclerView.addItemDecoration(
new HorizontalDividerItemDecoration.Builder(getContext())
.showLastDivider()
.marginResId(R.dimen.divider_margin_left, R.dimen.divider_margin_right)
.build());
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout);
swipeRefreshLayout.setOnRefreshListener(() -> {
if (!Util.isNetworkAvailable(getContext())) {
if (swipeRefreshLayout.isRefreshing()) swipeRefreshLayout.setRefreshing(false);
} else {
currentPage = 1;
loadData(); //method to request data from server
}
}
);
if (user != null) {
getDataFromLocal();
addToAdapter();
loadData();
}
}
(Ed)loadData method :
Observable<Response<List<Data>>> dataApi = request.getServerData(currentPage,
NUMBER_DATA_PER_PAGE,
token);
dataApi.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(responseData -> {
if (swipeRefreshLayout.isRefreshing()) swipeRefreshLayout.setRefreshing(false);
if (responseData.isSuccessful() && responseData.code() == 200) {
currentPage++;
adapter.add(responseData.body());
if (adapter.getItemCount() < 1) {
emptyText.setText("Empty");
emptyView.setVisibility(View.VISIBLE);
}
} else {
try {
JSONObject json = new JSONObject(responseFarmer.errorBody().string());
Toast.makeText(getContext(), json.getString("message"), Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
}
}
}, error -> {
if (swipeRefreshLayout != null && swipeRefreshLayout.isRefreshing())
swipeRefreshLayout.setRefreshing(false);
if (error != null && error.getLocalizedMessage() != null)
Toast.makeText(getContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
});
This method is called from pager fragment.
Your AsyncTask has an empty doInBackground() body. That essentially makes it synchronous. Say you have this AsyncTask:
private class SetAdapterTask extends AsyncTask<Void,Void,Void> {
protected Void doInBackground(Void... params) {
return null;
}
#Override
protected void onPostExecute(Void result) {
doPostExecute();
}
#Override
protected void onPreExecute() {
doPreExecute();
}
}
and you call this in your code like this:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
new SetAdapterTask().execute();
}
but since your AsyncTask doesn't do anything in background, the postExecute fires off right after the preExecute, making the whole thing equivalent to this:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
.....
doPreExecute();
doPostExecute();
}
In other words, you make your ProgressBar visible in preExecute and immediatelly after that you make it disappear in postExecute.
The right way to approach this would be moving the ProgressBar visibility settings to the AsyncTask you use to load your data, which is located somewhere in the loadData() I presume. As for the data loading itself, it's hard to say what is wrong without seeing the actual methods which load the data.