How to Update Current Fragment in ViewPager using Interfaces with ClassCastException? - android

I am trying to refresh a currently selected fragment inside of a ViewPager. I am using one MainActivity as a way to communicate between multiple fragments via interface calls. The fragment I am working with is implementing an interface call to the ViewPagerAdapter, however, when attaching the listener to the fragment I receive a ClassCastException error "Activity cannot be cast to Fragment". This is the code below:
#Override
public void onAttach(Context context) {
super.onAttach(context);
mListener = (UpdateableFragment) context; **//ERROR HERE**
}
I am trying to follow the answer posted here https://stackoverflow.com/a/17855730, however i cannot figure out where the interface call should be handled. Currently i have it implemented in the ViewPagerAdapter, so when i need to update i can just notifyDataSetChange(), but it returns the ClassCastException error.
I have tried to handle the interface call in the MainActivity i am using to communicate with the rest of my fragments and this eliminates the ClassCastException error, however, i have no way to call the ViewPagerAdapter and notifyDatasetChange. Below is my code:
CurrentFragment (in ViewPager)
private UpdateableFragment mListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mListener = (UpdateableFragment) context; **//ERROR HERE**
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public void click(){
mListener.update();
}
public interface UpdateableFragment {
void update();
}
ViewPagerAdapter
public class ViewPagerAdapter extends FragmentPagerAdapter implements CurrentFragment.UpdateableFragment{
public ViewPagerAdapter(FragmentManager fm) {
super(fm);}
//Note: unnecessary code left out.
public void update() {
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if (object instanceof CurrentFragment.UpdateableFragment) {
((CurrentFragment.UpdateableFragment) object).update();
}
return super.getItemPosition(object);
}
Any feedback would be appreciated. Thanks.

In onAttach(Context context) , the context is means of Activity, so cast context to your interface will throw the cast exception, if you want to communication with each fragments, i think you can try Event Bus library.

Try to use onAttachFragment() instead of onAttach():
#Override
public void onAttachFragment(Fragment childFragment) {
confirmListener = (ConfirmListener) childFragment;
}

Related

How can a BottomSheetDialogFragment communicate with its host fragment?

I have a button in my fragment which opens a BottomSheetDialogFragment. I want to notify the host fragment if the user selected an item on the BottomSheetDialogFragment. In order to achieve this, I have made an interface in my BottomSheetDialogFragment. However, that interface only communicates with the host activity, not the fragment. How can I send the information from the dialog to the fragment?
This is my interface:
public interface BottomSheetListener {
void onButtonClicked(int index);
}
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
getParentFragment will return the parent fragment, if the current fragment is attached to a fragment else it will return null if it is attached directly to an Activity
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
try {
mListener = (BottomSheetListener) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement BottomSheetListener");
}
}
When you use a lot of fragments, nested fragments or dialogfragments it becomes messy for communicate between them. I am suggesting to use ViewModel with LiveData for passing and updating data.
first add this to build gradle :
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
then create ViewModel class :
public class YourViewModel extends AndroidViewModel {
private MutableLiveData<Integer> yourMutableLiveData=new MutableLiveData<>();
public YourViewModel(#NonNull Application application) {
super(application);
}
public MutableLiveData<Integer> getYourMutableLiveData() {
return yourMutableLiveData;
}
}
This the fragment you want set value :
public class FragmentA extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().setValue(0);
}
}
And this is the fragment you want to get value when updated :
public class FragmentB extends Fragment{
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
YourViewModel yourViewModel =new ViewModelProvider(getActivity()).get(YourViewModel.class);
yourViewModel.getYourMutableLiveData().observe(getViewLifecycleOwner(), new Observer<Integer>() {
#Override
public void onChanged(Integer integer) {
}
});
}
}
It can work on dialog fragment as well as I tested.
Notes :
-Do not pass context or any view into view model.
-Remember that onActivityCreated comes after onCreateView.
-Do not set this key to
YourViewModel yourViewModel =new ViewModelProvider(this).get(YourViewModel.class);
in fragment if you want to pass data fragment to fragment but you can pass in activity.
-You can set more than one observer to the data.

Extend BottomSheetDialogFragment from a class instantiated in a fragment in Android

I have a fragment (FragmentSearchResults) that contains results retrieved from a database, in which there is a button "filters". When the user taps on such a button, a class (FiltersDialog) extending a BottomSheetDialogFragment is instantiated, so that the user can set his filters. When the user closes the FiltersDialog activity, the values are passed from FiltersDialog to FragmentSearchResults.
public class FragmentSearchResults extends Fragment implements FiltersDialog.FilterListener {
/* code */
ImageButton btnFilter = myFragment.findViewById(R.id.btn_filters);
btnFilter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showFilters();
}
});
}
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onFiltersSet(Map filters) {
// apply filters selected by user
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
}
public class FiltersDialog extends BottomSheetDialogFragment {
private FilterListener mListener;
private Map<String, Object> mFilters;
public FiltersDialog() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_filters_dialog, container, false);
TextView txtSave = v.findViewById(R.id.txt_save_filters);
mTxtSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onFiltersSet(mFilters);
}
});
return v;
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
if (context instanceof FilterListener) {
mListener = (FilterListener) context;
}
else {
// Here's the error, as the activity Home.java containing FragmentSearchResults
// does not implement FilterListener, FragmentSearchResults does
throw new RuntimeException(context.toString() + " must implement FilterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The problem is that FilterListener needs to be implemented in FragmentSearchResults, but I am passing the activity Home context.
How can I implement FilterListener in the fragment?
Why don't you create method inside your FiltersDialog, like
public void setFiltersListener(FiltersDialog.FilterListener listener) {
mListener = listener;
}
and simply call it after you instantiate the dialog.
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setFiltersListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
Then you can use the listener inside dialog. something like this
if (mListener != null) {
mListener.onFiltersSet(mFilters);
}
How can I setup listener to the dialog?
parameter of onAttach in Fragment is FragmentHost(Activity). thus, it can't typecast to FilterListener.
I suggest a simple way to implement FilterListener setter in FragmentDialog as below code.
... in FiltersDialog
public void setListener(FilterListener listener) {
mListener = listener;
}
...
... in FragmentSearchResults
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
...
//When FragmentSearchResults recreated, FiltersDialog must also need to be recreated.
A better approach will be to use LiveData, ViewModel in this case. Use Shared ViewModel Approach, An Activity Level ViewModel can be accessed via all the fragments lying in its environment.
Make an Activity Level ViewModel
Define a LiveData in ViewModel
When your "FragmentSearchResults" opens for the first time, start
observing it.
When You open "FiltersDialog" screen and click save button, Then post
to LiveData changes in the filter (You have activity context here,
You can fetch ActivityViewModel here, get LiveData from it, post
changes to this LiveData)
Now As "FragmentSearchResults" is already observing changes in the
LiveData, You will get callback here, make changes accordingly. This way your code will be completely decoupled. You will be escaped from
hustles of Interfaces.

Calling Fragment method from attached RecyclerView.Adapter

I recently started coding my first Android project using Android Studio 3.1.2 and SDK 19.
One of my fragments contains a RecyclerView with a custom RecyclerView.Adapter attached. On the CardView the Adapter gets by its ViewHolder, there can be a button. The target is, if the button is pressed, a method of my fragment should be called, though it's an instance of a custom subclass of Fragment:
From RequestingFragment:
public abstract class RequestingFragment extends Fragment implements RequestCallbacks {
public final static void startRequest(final RequestOperation, String param) {
//this is the guy i want to call
}
//these are the RequestCallbacks, they're all getting called in startRequest()
public void onSuccess(JSONObject json, String parsingkey) { }
public void onError() { }
public void onFinished() { }
Now one of my RequestingFragments contains a RecyclerView, on which a custom ErrorCompactAdapter is attached. Inside the Adapters ViewHolder, where I load the layout for the single CardViews, there's a button, which should call startRequest() onClick from my RequestingFragment
From ErrorCompactAdapter:
public class ErrorCompactAdapter extends RecyclerView.Adapter<ErrorCompactAdapter.ErrorCompactViewHolder> {
private Context context;
private ArrayList<Error> errors;
public ErrorCompactAdapter(Context context, ArrayList<Error> errors) {
this.context = context;
this.errors = errors;
}
public void onBindViewHolder(ErrorCompactViewHolder, int position) {
//...
holder.errorTakeOverButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//here's were i'm stuck
}
});
//...
}
}
My first approach was to change the context attribute of ErrorCompactAdapter to a RequestingFragment, so that I can call startRequest() on this.
private Context context; // private RequestingFragment attacher;
public void onClick(View v) {
attacher.startRequest(/*params*/);
}
But i'm very unsure, if the fragment that contains the RecyclerView will be the one which receives the response of the request, or if a somehow "pseudo-anonymous" Fragment will receive the response and simply does nothing with it then. Can someone enlight me, if this is the correct path? Thanks in advance.
Pass the Fragment in you ErrorCompactAdapter class's constructor. This works for me the way I want. I had the same issue.
RequestingFragment mFragment;
public ErrorCompactAdapter(Context context, ArrayList<Error> errors,
RequestingFragment fragment)
{
this.context = context;
this.errors = errors;
this.mFragment = fragment;
}
// While passing the fragment into your adapter, do it this way.
ErrorCompactAdapter errorCompactAdapter = new ErrorCompactAdapter(
context, errors, RequestingFragment.this);
holder.errorTakeOverButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// use you method of fragment here
mFragment.startRequest();
}
});

Enter EditText on a Fragment and fill also all other Fragments

I have implemented the Android Tutorial from the Android Training Site and everything works so far.
Inside of a Fragment, I added an EditText field, where the user can enter a simple text string.
What I want to achieve is: If the user entered something, and then swipes to left or right, the input should be "copied" the new fragment as well.
As in the training, I have got a ScreenSlidePageFragment.java class and a ScreenSlideActivity.java class.
I've implemented an Interface in the ScreenSlidePageFragment, as mentioned here:
Communicator mCallback;
public interface Communicator {
void sendData(String inpString);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (Communicator) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + "must Implement OnEditChangedListener");
}
}
But at the end, I just managed to receive it on the ScreenSlideActivity. How do I update a specified fragment?
In ScreenSlideActivity I tried to send it back to the new fragment.
#Override
public void sendData(String inpString) {
WHATFRAGMENT.changeData(inpString);
}
But how do I access another one (WHATFRAGMENT), which are created in the ScreenSlidePagerAdapter:
#Override
public Fragment getItem(int position) {
return NewReservationSlidePageFragment.create(position);
}
Do I need to create a whole Fragment and overwrite the new position? Or is it even necessary to work around the Activity? Is it possible to use FragmentTransaction somehow?
What would be the simplest solution for that?
You can achieve with your interface and public void setUserVisibleHint(boolean isVisibleToUser)
Add one more method in your interface like this,
public interface Communicator {
void sendData(String inpString);
String getData();
}
And in you activity create one String variable Globally to store the value.
And assign the data coming form sendData in that
#Override
public void sendData(String inpString) {
this.value = inpString;
//WHATFRAGMENT.changeData(inpString);
}
and implement getData too in your Activity
#Override
public void getData() {
return this.value;
}
Add setUserVisibleHint in your fragment
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if(isVisibleToUser) {
mCallback.sendData("[string from your edit text]");
} else {
String value = mCallback.getData();
}
}
Now you can see the data which you edited in one fragment can show in other visible fragment.

Call an fragment method from a fragment

Current my fragment A opens a new dialog fragment B. Is there any way to call method from a fragment A method from a dialog fragment B?
Regarding to android web site, you shouldn't call a fragment from another fragment, instead you should let activity to handle switching fragments.
To do that,
You can create an interface and implement it in activity,
public interface MyListener{
public void switchFragment(Fragment f);
}
public class MyActivity extends Activity implements MyListener{
#Override
public void switchFragement(Fragment f){
getSupportFragmentManager().beginTransaction
.replace(R.id.container, f)
.commit();
}
}
and in fragment classes,
public class MyFragment extends Fragment{
private MyListener listener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
listener = (MyListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement MyListener");
}
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
}
and simply use
listener.switchFragment(fragment);
You will need to one of 2 things:
Wire a reference to each Fragment in your Activity
Use an Enterprise Service Bus (Otto is an open-source option) to communicate directly between Fragments
There is NO way to talk direct Fragment to Fragment in the standard framework. You must manually create this linkage.

Categories

Resources