Android BottomSheetDialogFragment data changes the data in parent Fragment - java

Problem Statement
I have a problem of data being changed in fragment whenever the data is modified in the BottomSheetDialogFragment
App Description
In my app I have the MainActivity which host 2 Fragment in it's ViewPager. 1st Fragment for app content and 2nd Fragment (let's call it GalleryFragment) that shows the gallery view. User can tap on gallery item which loads up the BottomSheet (let's call it GalleryBottomSheet) - that hosts RecyclerView to show gallery item in full screen. Now initially app feeds the GalleryFragment with the ArrayList to show the gallery items. When user clicks on the Gallery item, this ArrayList is passed to the GalleryBottomSheet.
So What's Happening
What's happening is whenever I am removing an item from the ArrayList in my GalleryBottomSheet, It automatically also remove that item in the ArrayList of GalleryFragment. In short, Any update in the Arraylist from GalleryBottomSheet impacting the ArrayList in GalleryFragment
What do you want
I want seperation of concerns. I don't want the change made in the ArrayList of GalleryBottomSheet to impact the original ArrayList of GalleryFragment
Show me the damn code
To make this question concise, I am adding only the important part of the code.
~ GalleryFragment.java
public class GalleryFragment extends Fragment {
private RecyclerView recyclerView;
private ArrayList<String> arrayList = new ArrayList<>(); //This is the one which will be passed to the GalleryBottomSheet
private GalleryAdapter galleryAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_gallery, container, false);
//setting up all the UI work...
arrayList = FindFiles(); //FindFiles is a private function searching for all file in the dir and adding the path as a string to the arraylist
galleryAdapter = new GalleryAdapter(getActivity()); //init the adapter
recyclerView.setAdapter(galleryAdapter); //setting the adapter
return view;
}
}
private class GalleryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final Activity activity;
GalleryAdapter(Activity context) {
this.activity = context;
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gallery_content_item, parent, false);
return new ItemViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ItemViewHolder){
//setting up stuff..
}
}
#Override
public int getItemCount() {
return arrayList.size();
}
private class ItemViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
ImageView imageView;
ItemViewHolder(View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.imageView);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("list", arrayList);
GalleryBottomSheet galleryBottomSheet= GalleryBottomSheet.newInstance();
galleryBottomSheet.setOnRefreshListener( new GalleryBottomSheet.GalleryInterface() {
#Override
public void onRefresh() {
//here the actual arrayList size reduced even though the arrayList that was modified exist in GalleryBottomSheet
System.out.println("CURRENT LIST SIZE: " + arrayList.size());
}
});
galleryBottomSheet.setArguments(bundle);
galleryBottomSheet.show(getParentFragmentManager(), "galleryPager");
}
}
~ GalleryBottomSheet.java
public class GalleryBottomSheet extends BottomSheetDialogFragment {
static GalleryBottomSheet newInstance() {
return new GalleryBottomSheet();
}
public interface GalleryInterface {
void onRefresh();
}
private RecyclerView recyclerView;
private ViewAdapter viewAdapter;
private Button deleteBtn;
private GalleryInterface galleryInterface;
public void setOnRefreshListener( GalleryInterface galleryInterface){
this.galleryInterface = galleryInterface;
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
//.. setting up sheetDialog
return bottomSheetDialog;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(BottomSheetDialogFragment.STYLE_NO_FRAME, R.style.GalleryBottomStyle);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gallery_sheet, container, false);
Bundle bundle = getArguments();
ArrayList<String> filePathArr = bundle.getStringArrayList("list"); //here arrayList from GalleryFragment
//Setting up all UI views...
deleteBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int positionPager = recyclerView.computeVerticalScrollOffset(); //just for demo, in the actual app we are using addOnScrollListener for position
filePathArr.remove(positionPager);
viewAdapter.notifyItemRemoved(positionPager);
galleryInterface.onRefresh(); //this is where GalleryFragment shows that the arraylist is modified too.
Toast.makeText(getActivity(), "Deleted Successfully",Toast.LENGTH_SHORT).show();
}
});
return view;
}
//setting up viewAdapter and other stuff
}

Well, it turns out the main problem is that object is stored in memory by reference. By modifying the arrayList from GalleryBottomSheet it also changes the original arrayList since the exact arrayList was referred in GalleryBottomSheet from GalleryFragment. The solution is to simply initializing a new ArrayList in GalleryBottomSheet
Changing from:
ArrayList<String> filePathArr = bundle.getStringArrayList("list");
to:
ArrayList<String> filePathArr = new ArrayList<>(bundle.getStringArrayList("list"));

Related

Calling a fragment and getting the id in the RecyclerView adapter

I have a list using the RecyclerView, what I want is that when I click on the list item, it opens a fragment, I've been searching and I only found it with an activity, but I'm working only with fragments, if I use activity it disappears with my action bar, it's all set in the fragment.
This is my adapter class:
public class CreditCardAdapter extends RecyclerView.Adapter<CreditCardHolder> {
private final List<CreditCard> creditList;
public CreditCardAdapter(List<CreditCard> creditList) {
this.creditList = creditList;
}
#NonNull
#Override
public CreditCardHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.credit_card_item, parent, false);
return new CreditCardHolder(view);
}
#Override
public void onBindViewHolder(#NonNull CreditCardHolder holder, int position) {
CreditCard credit = creditList.get(position);
String flag = credit.getFlag();
holder.flag.setText(flag);
String owner = credit.getOwner();
holder.owner.setText(owner);
String valueLimit = String.format("%.2f", credit.getLimit());
holder.limit.setText(valueLimit.replace(".", ","));
holder.creditCardId = credit.getId();
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Method on click
}
});
}
#Override
public int getItemCount() {
return creditList.size();
}
}
I need to send the credit card id to the fragment, to call the controller and get the information from the database, I tried it with the intent, but it only worked if I call the activity with startActivity(intent).
So here it is the fragment class:
public class InfoCreditCardFragment extends Fragment {
public InfoCreditCardFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_info_credit_card, container, false);
String id; //Here I need the id from the Adapter
TextView flagInfoTxt = view.findViewById(R.id.flagInfoTxt);
TextView ownerInfoTxt = view.findViewById(R.id.ownerInfoTxt);
CreditCard cc = CreditCardController.getWithId(id);
flagInfoTxt.setText(cc.getFlag());
ownerInfoTxt.setText(cc.getOwner());
return view;
}
}
If anyone knows anything to help, please, I'm desperate.
So, I managed to go to the fragment using this:
Bundle bundle = new Bundle();
bundle.putString("id", holder.creditCardId);
InfoCreditCardFragment fragment = new InfoCreditCardFragment();
AppCompatActivity activity = (AppCompatActivity) view.getContext();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.fragmentContainer, fragment).addToBackStack(null).commit();
And inside the fragment I just get the bundle with "getArguments().getString("id");"

Data is duplicating when navigate back to previous fragment

I am using navigation component in my app I have 2 fragments one fragments list of items and another shows detail of an item when user clicks on an item in fragments 1 it goes to detail fragment and when I switch back to first fragment then all the listing duplicates again.
Below is my code:
CakeFragment.java
public class CakeFragment extends Fragment {
List<AllCakes> allCakeList = new ArrayList<>();
AllCakesAdapter adapter;
BottomNavigationView navView;
FragmentCakeBinding fragmentCakeBinding;
public CakeFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
fragmentCakeBinding = FragmentCakeBinding.inflate(inflater,container,false);
navView = getActivity().findViewById(R.id.navView);
navView.setVisibility(View.GONE);
getAllCakes();
return fragmentCakeBinding.getRoot();
}
private void getAllCakes(){
Retrofit retrofit = RetrofitClient.getInstance();
ApiService apiService = retrofit.create(ApiService.class);
Call<List<AllCakes>> call = apiService.getAllCake();
call.enqueue(new Callback<List<AllCakes>>() {
#Override
public void onResponse(Call<List<AllCakes>> call, Response<List<AllCakes>> response) {
fragmentCakeBinding.cakeProgress.setVisibility(View.INVISIBLE);
fragmentCakeBinding.allCakeRecycler.setHasFixedSize(true);
fragmentCakeBinding.allCakeRecycler.setLayoutManager(new LinearLayoutManager(getActivity()));
allCakeList.addAll(response.body());
adapter = new AllCakesAdapter(getActivity(),allCakeList);
fragmentCakeBinding.allCakeRecycler.setAdapter(adapter);
}
#Override
public void onFailure(Call<List<AllCakes>> call, Throwable t) {
fragmentCakeBinding.cakeProgress.setVisibility(View.INVISIBLE);
TastyToast.makeText(getActivity(),t.getMessage(),TastyToast.LENGTH_SHORT,TastyToast.ERROR).show();
}
});
}
}
AllCakesAdapter.java
public class AllCakesAdapter extends RecyclerView.Adapter<AllCakesAdapter.ViewHolder> {
Context context;
List<AllCakes> allCakeList;
public AllCakesAdapter(Context context, List<AllCakes> allCakeList) {
this.context = context;
this.allCakeList = allCakeList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.allcakes_row,parent,false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
AllCakes model = allCakeList.get(position);
Glide.with(context).load(model.getImgurl()).into(holder.allCakeImg);
holder.allCakeName.setText(model.getName());
holder.cakeDisPrice.setPaintFlags(holder.cakeDisPrice.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
holder.moreCake.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
NavController navController = Navigation.findNavController((Activity) context,R.id.fragment);
navController.navigate(R.id.cakeDetailFragment);
}
});
}
#Override
public int getItemCount() {
return allCakeList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ImageView allCakeImg;
Button moreCake;
TextView allCakeName;
public ViewHolder(#NonNull View itemView) {
super(itemView);
allCakeImg = itemView.findViewById(R.id.allCakeImg);
moreCake = itemView.findViewById(R.id.moreCake);
allCakeName = itemView.findViewById(R.id.allCakeName);
}
}
}
CakeDetailFragment.java
public class CakeDetailFragment extends Fragment {
FragmentCakeDetailBinding fragmentCakeDetailBinding;
public CakeDetailFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
fragmentCakeDetailBinding = FragmentCakeDetailBinding.inflate(inflater,container,false);
return fragmentCakeDetailBinding.getRoot();
}
}
Why is this happening?
private void getAllCakes(){
...
allCakeList.addAll(response.body());
adapter = new AllCakesAdapter(getActivity(),allCakeList);
fragmentCakeBinding.allCakeRecycler.setAdapter(adapter);
}
...
you're calling:
allCakeList.addAll(response.body());
every time without clearing your list out.
you have to clear that list:
allCakeList.clear();
allCakeList.addAll(response.body());
this is something you can easily determine yourself by just putting a breakpoint on your allCakeList to see what's inside it, if you haven't ever done this before, you should consider trying it out
Try the following and see if it solves your issue
CakeFragment.java
allCakeList.clear();
allCakeList.addAll(response.body());
//rest of the logic remains same

How do I place my image adapter (it is an infinite cycler view) in a fragment?

I'm setting up an image adapter in my fragments. I recently learn the usefulness of Fragments and Therefore I am trying to switch from the traditional activities to a central activity with multiple fragments. But I cant get to display my images' infinite cycler view.
I have tried reading answers from previous questions here on Stack overflow (ImageAdapter cannot be applied to fragment class and ImageAdapter cannot be applied to a Fragment Class) but I didnt really understand anything perhaps because I am a beginner in android studio with no educational background in coding. I have also tried youtube and everywhere.
I havent found tutorials to do this, I understand that fragments is relatively new in android studio
This is the fragment I am trying to switch to
public TrendingFragment() {
// Required empty public constructor
}
private static final String TAG = "Trending";
HorizontalInfiniteCycleViewPager viewPager;
List<TrendingHolder> TrendingList = new ArrayList<>();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragme
View view = inflater.inflate(R.layout.fragment_trending, container, false);
initData();
viewPager = view.findViewById(R.id.view_pager);
viewPager.setAdapter(new TrendingAdapter(getActivity()));
return view;
}
private void initData() {
//Adding the Images on board
TrendingList.add(new TrendingHolder(R.drawable.ad_test));
TrendingList.add(new TrendingHolder(R.drawable.burger_test));
TrendingList.add(new TrendingHolder(R.drawable.italian_test));
TrendingList.add(new TrendingHolder(R.drawable.pizza_test));
}
}
This is my adapter class
public class TrendingAdapter extends PagerAdapter {
Context context;
List<TrendingHolder> TrendingList;
private static final String TAG = "TrendingAdapter";
public TrendingAdapter(Context context, List<TrendingHolder> trendingList) {
this.context = context;
this.TrendingList = trendingList;
}
#Override
public int getCount() {
return TrendingList.size();
}
#Override
public boolean isViewFromObject(View view, #NonNull Object o){
return view.equals(o);
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
container.removeView((View)object);
}
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
//Inflate View
View view = LayoutInflater.from(context).inflate(R.layout.card_item, container, false);
//View
ImageView trending_image = (ImageView)view.findViewById(R.id.trending_holder);
//Set Data
trending_image.setImageResource(TrendingList.get(position).getImage());
//set On Event Click
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//shop activity comes here
Log.d(TAG, "onClick: added to Cart");
}
});
container.addView(view);
return view;
}
}
This is my model for the images
public class TrendingHolder {
private int image;
public TrendingHolder(){
}
public TrendingHolder(int image) {
this.image = image;
}
public int getImage() {
return image;
}
public void setImage(int image) {
this.image = image;
}
}
I am getting this error TrendingAdapter( ) 
in TrendingAdapter cannot be applied
to
(androidx.fragment.app.FragmentActivity) in my Trending Fragment
It seems like missing constructor parameter. The constructor of TrendingAdapter accepts 2 parameters public TrendingAdapter(Context context, List<TrendingHolder> trendingList). But you pass 1 parameter viewPager.setAdapter(new TrendingAdapter(getActivity()));.

Having problems while trying to populate a todo listview using a custom adaptor?

I want a user to input data through an editable text and I want to receive that data through a custom made listview, for that I am trying to use a custom adapter to add a textfield into my listview, through the tostring() method I have converted the data from the editable textview to a string and I am adding that string within my custom adapter to an Arraylist variable values and I’m trying to display that data through get(0) but either the Arraylist is not populating correctly or the data is not displaying properly because whenever I type something within my editable text and press the add button nothing happens, before this I added the string to an Array Adapter and the listview was populating normally, what am I doing wrong?
public class todoFragment extends ListFragment {
private EditText mToDoField;
private Button mAdd;
UsersAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.todo_title);
}
public class UsersAdapter extends ArrayAdapter<String> {
public Context context;
public ArrayList<String> values;
public UsersAdapter(Context context, ArrayList<String> values) {
super(context, 0, values);
this.context = context;
this.values = values;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = LayoutInflater.from(getContext()).inflate(R.layout.todo_list, parent, false);
TextView todoTextView = (TextView) convertView.findViewById(R.id.todo_TextView);
todoTextView.setText(values.get(0));
return convertView;
}
}
#TargetApi(9) // remember this for isEmpty()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_todo, container, false);
ArrayList<String> todoList = new ArrayList<String>();
mAdapter = new UsersAdapter(getActivity(), todoList);
ListView listViewToDo = (ListView) v.findViewById (android.R.id.list);
listViewToDo.setAdapter(mAdapter);
mToDoField = (EditText) v.findViewById(R.id.todo_editText);
mAdd = (Button)v.findViewById(R.id.add_button);
mAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String toDo = mToDoField.getText().toString().trim();
if (toDo.isEmpty()){
return;
}
mAdapter.values.add(toDo);
mToDoField.setText("");
}
});
return v;
}
}
Firstly, you should not be doing
todoTextView.setText(values.get(0));
Because this will always return the first element of the values list. You should do
todoTextView.setText(values.get(position));
Secondly,
mAdapter.values.add(toDo);
is not really right. It will work, but its not the best practise. Try using something like
mAdapter.add(toDo);
or
values.add(toDo);
Now once you've added the data to the list, you need to notify the adapter that the data set has been changed. This is done by
mAdapter.notifyDataSetChanged();
When you manually update the data don't forget to call:
mAdapter.notifyDataSetChanged();
Instead of mAdapter.values.add(toDo); UsemAdapter.add(toDo);
Look at the Add Method Of ArrayAdpter Class, it Itself use notifyDataSetChanged() so need to write any extra line of code:
public void add(T object) {
synchronized (mLock) {
if (mOriginalValues != null) {
mOriginalValues.add(object);
} else {
mObjects.add(object);
}
}
if (mNotifyOnChange) notifyDataSetChanged();
}

How can I through an editable textview populate a custom todo list using a custom adaptor?

I want a user to input data through an editable text and I want to receive that data through a custom made listview for that I am trying to use a custom adapter to add a textfield into my listview, through the tostring() method I have converted the data from the editable textview to a string and I am adding that string to the custom adaptor but I am having a type mismatch error, before this I added the string to a basic adapter and the listview was populating normally, what am I doing wrong?
User.java
public class User {
public String itemView;
public User(String itemView) {
this.itemView = itemView;
}
todoFragment.java
public class todoFragment extends ListFragment {
private CheckBox mCheckBox;
private TextView mTextView;
private EditText mToDoField;
private Button mAdd;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActivity().setTitle(R.string.todo_title);
}
public class UsersAdapter extends ArrayAdapter<User> {
public UsersAdapter(Context context, ArrayList<User> users) {
super(context, 0, users);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
User user = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.todo_list, parent, false);
}
TextView todoTextView = (TextView) convertView.findViewById(R.id.todo_TextView);
todoTextView.setText(user.itemView);
return convertView;
}
}
#TargetApi(9) // remember this for isEmpty()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_todo, container, false);
ArrayList<User> todoList = new ArrayList<User>();
final UsersAdapter adapter = new UsersAdapter(getActivity(), todoList);
ListView listViewToDo = (ListView) v.findViewById (android.R.id.list);
listViewToDo.setAdapter(adapter);
mAdd = (Button)v.findViewById(R.id.add_button);
mAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String toDo = mToDoField.getText().toString().trim();
if (toDo.isEmpty()){
return;
}
adapter.add(toDo); // type mismatch error here
mToDoField.setText("");
}
});
return v;
}
}
Your variable toDo is a String, and your ArrayAdapter adapter is of type User. So, when you try to call adapter.add(), the adapter is expecting a User object but you are passing it a string.
It appears to me that you don't really know what exactly you are doing. As of right now, the structure of your code implies that each item in your ArrayAdapter is its own user, with their own item. What you should be doing is creating a User object with an ArrayList<String> to hold all their to-do's. Your ArrayAdapter should extend ArrayAdapter<String> and you pass the to-do list to the Adapter, instead of a list of Users.
You are adding A string Type to the adapter object, which extends ArrayAdapter of Type User, thats why you are getting type mismatch error, just set the value todo to your User Objet and then add object to adapter. hope this help you.

Categories