Why RecylerView.notifyDataSetChanged() stop working after adding Navigation Components? - android

I implemented Navigation from Android Jetpack, and then ALL RecyclerViews in my app stopped updating after the data changing. In debug mode I saw that datas really changed and notifyDataSetChanged() is invoked, but RVs actually not updated (new items not added).
I've tried to use other methods, such as notifyItemInserted() and so on. Tried to notify the adapter outside. Nothing helps.
Fragment:
...
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.profnastil_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
...
ImageButton addRoofBtn = view.findViewById(R.id.roof_add_btn);
addRoofBtn.setOnClickListener(v -> {
...
Square roof = new Square(width, height);
mViewModel.addRoof(roof);
});
mViewModel = ViewModelProviders.of(this).get(ProfnastilViewModel.class);
mProfnastilAdapter = new SquaresAdapter(mViewModel, SquaresAdapter.SQUARE_TYPE_ROOF);
RecyclerView roofRv = view.findViewById(R.id.roof_list);
RecyclerView.LayoutManager wallsLayoutManager = new LinearLayoutManager(getContext());
roofRv.setLayoutManager(wallsLayoutManager);
roofRv.setHasFixedSize(true);
roofRv.setAdapter(mProfnastilAdapter);
mViewModel.getRoofs().observe(this, roofs -> mProfnastilAdapter.setSquares(roofs));
}
Adapter:
...
public SquaresAdapter(SquareViewModelBase model, String squareType) {
mModel = model;
mSquares = new ArrayList<>();
setHasStableIds(true);
mSquareType = squareType;
}
public static class SquareHolder extends RecyclerView.ViewHolder {
View view;
TextView heightText;
TextView widthText;
TextView squareText;
private SquareHolder(#NonNull View itemView) {
super(itemView);
view = itemView;
heightText = itemView.findViewById(R.id.square_height);
widthText = itemView.findViewById(R.id.square_width);
squareText = itemView.findViewById(R.id.result_square);
}
}
#Override
public int getItemCount() {
if (mSquares == null) return 0;
return mSquares.size();
}
#Override
public long getItemId(int position) {
return position;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_square, parent, false);
return new SquareHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder viewHolder, final int position) {
final Square square = mSquares.get(position);
SquareHolder vh = (SquareHolder) viewHolder;
vh.heightText.setText(StringUtils.format(square.getHeight()));
vh.widthText.setText(StringUtils.format(square.getWidth()));
vh.squareText.setText(StringUtils.format(square.getSquare()));
vh.view.findViewById(R.id.item_del_btn).setOnClickListener(v -> {
mModel.removeSquare(square, mSquareType);
});
}
public void setSquares(List<Square> newSquares) {
mSquares.clear();
mSquares.addAll(newSquares);
notifyDataSetChanged();
}
ViewModel:
...
private MutableLiveData<List<Square>> mRoofs = new MutableLiveData<>();
public void addRoof(Square roof) {
List<Square> roofs = mRoofs.getValue();
if (roofs == null) roofs = new ArrayList<>();
roofs.add(roof);
mRoofs.setValue(roofs);
}
Before I implemented the Navigation graph, everything worked correctly. What happens and how to make it work?

In the end adding view.requireLayout() at the Fragment helped me:
mViewModel.getRoofs().observe(this, roofs -> {
mProfnastilAdapter.setSquares(roofs));
view.requireLayout();
};
But I still don't understand what happened and why it helped me =)
I will be grateful if someone will explain to me.

Related

android simple animation is skipping on older device

I have a RecyclerView with multiple TextViews and a Button. The button triggers an animation like the gif below:
I am doing this by creating a RecyclerView.Adapter and creating an OnClickListener like so:
public class recyclerViewAdapter extends RecyclerView.Adapter<recyclerViewAdapter.ViewHolder> {
private ArrayList<MyObject> objects;
// constructor and other stuff ...
public void setDefaultRequestBtnClickListener(View.OnClickListener defaultRequestBtnClickListener) {
this.defaultRequestBtnClickListener = defaultRequestBtnClickListener;
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, int position) {
MyObject object = objects.get(position);
if (object.getRequestBtnClickListener() != null) {
holder.Btn.setOnClickListener(object.getRequestBtnClickListener());
}else {
holder.Btn.setOnClickListener(defaultRequestBtnClickListener);
}
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView value;
TextView currentVal;
TextView Btn;
ConstraintLayout parent;
public ViewHolder(#NonNull View itemView) {
super(itemView);
value = itemView.findViewById(R.id.totalPrice);
currentVal = itemView.findViewById(R.id.checkBtn);
Btn = itemView.findViewById(R.id.animateBtn);
parent = itemView.findViewById(R.id.parent);
}
public void bind(final OnMyClickListener listener) {
parent.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onMyClick(v, getLayoutPosition());
}
});
}
}
public interface OnMyClickListener{
void onMyClick(View view, int position);
}
}
this creates and binds a view holder in a recycler view to the TextViews and buttons. At the moment a user has to click the element to activate the Btn and animation. The below is the fragment that is initiating the adapter:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.frag_object_layout, container, false);
final RecyclerView recyclerView = rootView.findViewById(R.id.list);
final ArrayList<MyObject> objects = MyObject.getTestingList();
final recyclerViewAdapter adapter = new recyclerViewAdapter(objects, getContext());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter.setDefaultRequestBtnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
recyclerViewAdapter.ViewHolder holder = (recyclerViewAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);
if (holder != null) {
if (holder.value.getAlpha() == 0.0f) {
holder.value.animate().alpha(1.0f).setDuration(500);
holder.value.animate().translationY(-38).setDuration(1000);
holder.checkValue.animate().translationY(38).setDuration(1000);
} else {
holder.value.animate().alpha(0.0f).setDuration(500);
holder.value.animate().translationY(0).setDuration(500);
holder.checkValue.animate().translationY(0).setDuration(500);
}
}
}
});
adapter.setOnMyClickListener(new recyclerViewAdapter.OnMyClickListener() {
#Override
public void onMyClick(View view, int pos) {
Position = pos;
holder = (recyclerViewAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);
}
});
return rootView;
}
}
This is all working great on an emulator, but on an older device the animation is skipping and the views go directly to their final position. Why is that? I checked the processing time and it is below the 16ms threshold.
How can I improve this?
(Posting the whole code is difficult here. ask for code and I will update the question)
The issue here is that I was doing this programmatically. For some reason, if I call Animator object and pass anim xml files then the animation works (on old device). Does anyone know why?

simple animation is skipping on older devices

I have a RecyclerView with multiple TextViews and a Button. The button triggers an animation like the gif below:
I am doing this by creating a RecyclerView.Adapter and creating an OnClickListener like so:
public class recyclerViewAdapter extends RecyclerView.Adapter<recyclerViewAdapter.ViewHolder> {
private ArrayList<MyObject> objects;
// constructor and other stuff ...
public void setDefaultRequestBtnClickListener(View.OnClickListener defaultRequestBtnClickListener) {
this.defaultRequestBtnClickListener = defaultRequestBtnClickListener;
}
#Override
public void onBindViewHolder(#NonNull final ViewHolder holder, int position) {
MyObject object = objects.get(position);
if (object.getRequestBtnClickListener() != null) {
holder.Btn.setOnClickListener(object.getRequestBtnClickListener());
}else {
holder.Btn.setOnClickListener(defaultRequestBtnClickListener);
}
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView value;
TextView currentVal;
TextView Btn;
ConstraintLayout parent;
public ViewHolder(#NonNull View itemView) {
super(itemView);
value = itemView.findViewById(R.id.totalPrice);
currentVal = itemView.findViewById(R.id.checkBtn);
Btn = itemView.findViewById(R.id.animateBtn);
parent = itemView.findViewById(R.id.parent);
}
public void bind(final OnMyClickListener listener) {
parent.setOnClickListener(new View.OnClickListener() {
#Override public void onClick(View v) {
listener.onMyClick(v, getLayoutPosition());
}
});
}
}
public interface OnMyClickListener{
void onMyClick(View view, int position);
}
}
this creates and binds a viewholder in a recyclerview to the TextViews and buttons. At the moment a user has to click the element to activate the Btn and animation. The below is the Fragment that is initiating the adapter:
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.frag_object_layout, container, false);
final RecyclerView recyclerView = rootView.findViewById(R.id.list);
final ArrayList<MyObject> objects = MyObject.getTestingList();
final recyclerViewAdapter adapter = new recyclerViewAdapter(objects, getContext());
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter.setDefaultRequestBtnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
recyclerViewAdapter.ViewHolder holder = (recyclerViewAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);
if (holder != null) {
if (holder.value.getAlpha() == 0.0f) {
holder.value.animate().alpha(1.0f).setDuration(500);
holder.value.animate().translationY(-38).setDuration(1000);
holder.checkValue.animate().translationY(38).setDuration(1000);
} else {
holder.value.animate().alpha(0.0f).setDuration(500);
holder.value.animate().translationY(0).setDuration(500);
holder.checkValue.animate().translationY(0).setDuration(500);
}
}
}
});
adapter.setOnMyClickListener(new recyclerViewAdapter.OnMyClickListener() {
#Override
public void onMyClick(View view, int pos) {
Position = pos;
holder = (recyclerViewAdapter.ViewHolder) recyclerView.findViewHolderForAdapterPosition(Position);
}
});
return rootView;
}
}
This is all working great on an emulator, but on an older device the animation is skipping and the views go directly to their final position. Why is that? I checked the processing time and it is below the 16ms threshold.
How can I improve this?
(Posting the whole code is difficult here. ask for code and I will update the question)

Fragment isn't displaying RecyclerView [duplicate]

This question already has answers here:
How to implement Firebase Recycler Adapter in newer version of Android 3.1 and higher?
(2 answers)
Closed 3 years ago.
So, I watched some tutorial on how to do the RecyclerView mainly this. I tweaked the code to fit my needs. But when i try to ran it, the RecyclerView isn't displaying on the fragment. My guess is the problem lies in adapter and viewholder part, because i'm not sure how to implement it yet. Can anyone help me?
The Fragment.java
public class JobListFragment extends Fragment {
RecyclerView mRecyclerView;
FirebaseDatabase firebaseDatabase;
DatabaseReference databaseReference;
public JobListFragment(){
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
((MainHomeActivity)getActivity()).setActionBarTitle("Job List");
View view = inflater.inflate(R.layout.fragment_job_list, container, false);
firebaseDatabase = FirebaseDatabase.getInstance();
databaseReference = firebaseDatabase.getReference().child("Data");
mRecyclerView = view.findViewById(R.id.recycleView);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mRecyclerView.setHasFixedSize(true);
return view;
}
#Override
public void onStart() {
super.onStart();
FirebaseRecyclerOptions<Model> options =
new FirebaseRecyclerOptions.Builder<Model>()
.setQuery(databaseReference, Model.class).build();
FirebaseRecyclerAdapter adapter = new FirebaseRecyclerAdapter<Model, ViewHolder>(options) {
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.row, viewGroup, false);
return new ViewHolder(view);
}
#Override
protected void onBindViewHolder(#NonNull ViewHolder holder, int position, #NonNull Model model) {
holder.setDetails(model.getTitlept(), model.getDescpt(), model.getImagept());
}
};
mRecyclerView.setAdapter(adapter);
}
}
ViewHolder.java
public class ViewHolder extends RecyclerView.ViewHolder {
View mView;
public ViewHolder(View itemView){
super(itemView);
mView = itemView;
}
public void setDetails(String title, String description, String image){
TextView mTitleTv = mView.findViewById(R.id.rTitleTv);
TextView mDescTv = mView.findViewById(R.id.rDescIv);
ImageView mImageIv = mView.findViewById(R.id.rImageIv);
mTitleTv.setText(title);
mDescTv.setText(description);
Picasso.get().load(image).into(mImageIv);
}
}
Model.java
public class Model {
String titlept, imagept, descpt;
public Model(){}
public String getTitlept() {
return titlept;
}
public void setTitlept(String titlept) {
this.titlept = titlept;
}
public String getImagept() {
return imagept;
}
public void setImagept(String imagept) {
this.imagept = imagept;
}
public String getDescpt() {
return descpt;
}
public void setDescpt(String descpt) {
this.descpt = descpt;
}
}
Can you write the problem a little bit more clearly? Picture not displayed? #AdhityoPutro

Recycle view not showing items when I start the app but after going to another fragment and back it works

I have to set up a recycle view inside a fragment. The problem is that when I start the app it shows nothing but if i switch to another fragment and go back all items are there. If i start the app and try to scroll through a the recycle view the first item and the last one will load. I tried searching for an answer but there is nothing useful. I mention i just started working in Android Studio.
That's the fragment code
public class Main_fragment extends Fragment {
TextView titleText;
ArrayList<String> titles = new ArrayList<>();
// Access it from anywhere
static String FoodChoiceVar = "All food"; // Value set for test only. Maybe make an ENUM instead?
public Main_fragment() {
// Needed empty constructor.
}
// Don't edit this method as you won't be able tu use "R".
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
titles.add("Breakfast");titles.add("Morning Snack");titles.add("Lunch");titles.add("AfterNoon Snack");titles.add("Dinner"); titles.add("Night Snack");
}
//Use this instead.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.main_fragment, container, false);
final FragmentActivity c = getActivity();
titleText = view.findViewById(R.id.textViewFoodType);
titleText.setText(FoodChoiceVar + "| MENU");
RecyclerView recyclerView = view.findViewById(R.id.fragmentRecycleView);
LinearLayoutManager layoutManager = new LinearLayoutManager(c);
recyclerView.setLayoutManager(layoutManager);
ViewAdapter adapter = new ViewAdapter(c,titles);
recyclerView.setHasFixedSize(false);
recyclerView.setAdapter(adapter);
return view;
}
The ViewAdapter
public class ViewAdapter extends RecyclerView.Adapter<FoodMenuViewHolder> {
private LayoutInflater layoutInflater;
private List<String> titleData;
public ViewAdapter (Context context,List<String> titleData){
this.layoutInflater = LayoutInflater.from(context);
this.titleData = titleData;
}
#NonNull
#Override
public FoodMenuViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view = layoutInflater.inflate(R.layout.layout_menu_item,viewGroup,false);
FoodMenuViewHolder holder = new FoodMenuViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(#NonNull FoodMenuViewHolder foodMenuViewHolder, int i) {
String title = titleData.get(i);
foodMenuViewHolder.titleText.setText(title);
}
#Override
public int getItemCount() {
return titleData.size();
}
}
The view holder
public class FoodMenuViewHolder extends RecyclerView.ViewHolder {
public TextView titleText;
public FoodMenuViewHolder(#NonNull View itemView) {
super(itemView);
titleText = itemView.findViewById(R.id.ItemText);
}
}
I have captured 2 short videos to show what is happening
https://drive.google.com/file/d/17wvlhPUT5R0SN38C2TbgBv_AP8awQUI3/view?usp=sharing
https://drive.google.com/file/d/1LJ0Xg4p8Q4a4nyGPSHdVQhBlYOYYi1Pv/view?usp=sharing
I think that your adapter it's losing itself, so try to get the context for layout inflater from viewgroup in onCreateViewHolder.
this:
public FoodMenuViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
return new FoodMenuViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_menu_item, viewGroup, false);
}
And to avoid anoying error, put a execption in getItemCount().
This:
#Override
public int getItemCount() {
if(titleData != null) {
return titleData.size();
} else {
return 0;
}
}
And try to use context instead of Fragment Activity
final FragmentActivity c = getActivity();
to
final Context c = getContext();

RecyclerView shows wrong views

RecyclerView shows first items after some correct items. And one of incorrect items can change own data several times... It is really strange and i dont know why, in another app i wrote the same code and all works nice. I tried to find the resolve, but did not found nothing what helps. You can see video to understand what is going on.
video
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable
ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_car_list, container, false);
mRecyclerView = view.findViewById(R.id.car_recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
updateUI();
return view;
}
private class CarHolder extends RecyclerView.ViewHolder implements
View.OnClickListener {
private Car mCar;
public CarHolder(#NonNull final View itemView) {
super(itemView);
itemView.setOnClickListener(this);
mProducerTextView = itemView.findViewById(R.id.producerCardTextView);
mModelTextView = itemView.findViewById(R.id.modelCardTextView);
mPriceTextView = itemView.findViewById(R.id.priceCardTextView);
}
public void bind(Car car) {
mCar = car;
mProducerTextView.setText(getString(R.string.producer_params,
mCar.getProducer()));
mModelTextView.setText(getString(R.string.model_params,
mCar.getModel()));
mPriceTextView.setText(getString(R.string.price_params,
mCar.getPrice()));
}
#Override
public void onClick(View v) {
mListCallBack.onCarSelected(mCar);
}
}
private class CarAdapter extends RecyclerView.Adapter<CarHolder> {
private List<Car> mCars;
public CarAdapter(List<Car> cars) {
mCars = cars;
}
#NonNull
#Override
public CarHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View view = inflater.inflate(R.layout.list_item_car, viewGroup, false);
return new CarHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CarHolder carHolder, int i) {
Car car = mCars.get(i);
carHolder.bind(car);
}
#Override
public int getItemCount() {
return mCars.size();
}
public void setCars(List<Car> cars) {
this.mCars = cars;
}
}
public void updateUI(){
List<Car> cars = CarLab.getCarLab(getActivity()).getCars();
if (mAdapter == null) {
mAdapter = new CarAdapter(cars);
mRecyclerView.setAdapter(mAdapter);
} else {
mAdapter.setCars(cars);
mAdapter.notifyDataSetChanged();
}
}
may be you can try your RecyclerView not to recyle items using:
mRecyclerView.getRecycledViewPool().setMaxRecycledViews(0,0);
in your onCreate() method below mRecyclerView.setLayoutManager(...);
The problem occurs when you use the field variable mCar (which manipulates by all bind events) to update your viewHoleder, just simply use the car variable you pass to the bind to update views.
The other problem with your code is inside onClick, again you should not use the field variable mCar, instead get the item it needs like this:
#Override
public void onClick(View v) {
Car car = mCars.get(getAdapterPosition());
mListCallBack.onCarSelected(car);
}

Categories

Resources