I am calling a Fragment Dialogue from a Fragment, and when I try to impliment a callback it isn't calling back to the Fragment, I think it is calling back to the Activity. I believe this is by design from Google, is there anyway around that? How can couple the Dialogue Fragment to the Fragment?
My code is as follows;
Dialogue Fragment
public class CustomDatePicker extends DialogFragment {
private DateListener listener;
...
public interface DateListener {
void onDateComplete();
}
}
Fragment
public class User extends Fragment implements CustomDatePicker.DateListener {
...
#Override
public void onDateComplete() {
Log.i("TEST", "onDateComplete: " + date);
}
}
For those purposes you can use setTargetFragment()and onActivityResult().
Check this: Receive result from DialogFragment or Callback to a Fragment from a DialogFragment. In this case you dont need any interfaces.
Interfaces:
public class CustomDatePicker extends DialogFragment {
private DateListener listener;
public interface DateListener {
void onDateComplete(long date);
}
public void setListener(DateListener listener) {
this.listener = listener;
}
public void sendToFragment(long value) { //value you want to pass
if (listener != null) {
listener.onDateComplete(value);
}
}
}
and
public class User extends Fragment implements CustomDatePicker.DateListener {
public void showDialog(){
CustomDatePicker datePicker = new CustomDatePicker();
datePicker.setListener(this); // since User fragment implements it
...
datePicker.show(getFragmentManager(), "TAG");
}
#Override
public void onDateComplete(long date) {
Log.d("Interface", "onDateComplete: " + date);
}
}
Call sendToFragment() with your value where you need, hope this help.
instead of implementing your listener to your fragment get a new instance of your dialog fragment and new your listener when you want to call it
add newInstance to your dialog fragment like this:
public static CustomDatePicker newInstance(DateListener listener) {
return new CustomDatePicker();
}
and show your dialog fragment in this way:
CustomDatePicker.newInstance(new CustomDatePicker.DateListener() {
#Override
public void onDialogPositiveListener(DialogFragment dialog) {
}).show(getFragmentManager(), "tag");
so you can get result in your code
What I ended up doing, based off of comments and answers was the following.
Dialogue Fragment;
public class CustomDatePicker extends DialogFragment {
private DateListener listener;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
Button btnSave = (Button) v.findViewById(R.id.save);
btnSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getDate();
dismiss();
listener.onDateComplete();
}
});
...
}
...
public interface DateListener {
void onDateComplete();
}
}
Fragment;
public class User extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
CustomDatePicker customDatePicker = new CustomDatePicker();
customDatePicker.setListener(new CustomDatePicker.DateListener() {
#Override
public void onDateComplete() {
adapter.notifyDataSetChanged();
}
});
...
}
...
}
Related
So basically, I have a dialog where the user inputs data. This dialog is launched from a recyclerView with a custom adapter. I need to call notifyDataSetChanged on the fragment from the dialog after the user clicks "finish".
How do I do this?
Code:
public class FoodListAdapter extends RecyclerView.Adapter<FoodListAdapter.ViewHolder>
implements View.OnClickListener {
//blabla
#Override
public void onClick(View v) {
int position = (int) v.getTag();
String foodItemTitle = mFoodItemList.get(position).getTitle();
FoodEditDietDialog dialog = new FoodEditDietDialog();
dialog.show(fm, mFoodItemList.get(position).getTimeStamp());
}
public class Fragment extends Fragment {
//blabla
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View fragment = inflater.inflate(R.layout.fragment, container, false);
foodList = databaseHelper.getList("somethingsomething");
recyclerView = Fragment.findViewById(R.id.list);
recyclerView.setHasFixedSize(false);
layoutManager = new LinearLayoutManager(this.getContext());
recyclerView.setLayoutManager(layoutManager);
adapter = new FoodListAdapter(fm, foodList);
recyclerView.setAdapter(adapter);
}
public class FoodEditDietDialog extends DialogFragment {
//blabla
button = dialog.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
databaseHelper.doSomething();
dismissDialog();
//TODO: call method in dietFragment to notify adapters
}
});
}
You need to do this through your hosting Activity.
public interface DataChangeListener {
void onDataChange();
}
public class YourActivity extends Activity implements DataChangeListener {
private List<DataChangeListener> dataChangeListeners = new ArrayList<>();
#Override
public void onDataChange() {
for(DataChangeListener listener in dataChangeListeners) {
listener.onDataChange();
}
}
public void addDataChangeListener(DataChangeListener listener) {
dataChangeListeners.add(listener);
}
public void removeDataChangeListener(DataChangeListener listener) {
dataChangeListeners.remove(listener);
}
}
public class Fragment extends Fragment implements DataChangeListener {
#Override
public void onAttach(Activity activity) {
if (activity instanceOf YourActivity) {
activity.addDataChangeListener(this)
}
}
#Override
public void onDetach() {
Activity activity = getActivity();
if (activity instanceOf YourActivity) {
activity.removeDataChangeListener(this)
}
}
#Override
public void onDataChange() {
adapter.notifyDataSetChanged();
}
}
public class FoodEditDietDialog extends DialogFragment {
//blabla
button = dialog.findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
databaseHelper.doSomething();
dismissDialog();
//TODO: call method in dietFragment to notify adapters
Activity activity = getActivity();
if (activity instanceOf DataChangeListener) {
activity.onDataChange();
}
}
});
}
So basically, dialog will call activity, activity will notify fragment, fragment will notify adapters. Alternatively, you can pass your fragment (as DataChangeListener) to your adapter, and to your dialog. And call it from there.
I am trying to send data from activity to fragment and vice versa using interfaces but getting an error of cycling inheritance involving MyFragment.
Implementing interface created in MyFragment:
public class MyActivity implements OnSendFromMyFragListener {
OnSendFromMyActivityListener mCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCallback.sendFromMyActivity(2);
}
#Override
public void sendFromMyFrag (int a) {
//do something
}
public interface OnSendFromMyActivityListener {
public void sendFromMyActivity(int b);
}
}
Implementing interface created in MyActivity:
public class MyFragment extends Fragment implements OnSendFromMyActivityListener {
OnSendFromMyFragListener mCallback;
public interface OnSendFromMyFragListener {
void sendFromMyFrag(int a);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (OnSendFromMyFragListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString());
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallback.sendFromMyFrag(1);
}
}
});
return view;
}
#Override
public void sendFromMyActivity(int b) {
//do something
}
}
Well the reason you are getting this error is because your Activity depends on your Fragment and Fragment depends on your Activity. Don't Agree?
Let me show you. Imagine you are a compiler in your work you stumbled across:
class A implements B.A {
interface B {
void foo1();
}
#Override
public void foo2()
{
// do something;
}
}
Now you know that class A depends on (implements) B.A, so before you go further into class A you move on to class B:
class B implements A.B {
interface A {
void foo2();
}
#Override
public void foo1()
{
// Do Something;
}
}
Now you see that class B depends on (implements) class A specifically class A.B! What do you (compiler) do? Go back to class A? But that depends on class B. So you see this becomes an unending cycle thus causing a cyclic dependency where both your class' definitions depend on each other.
As an alternative you could either create a member event listener or an anonymous one. Or if you don't like either of those options, you could also create a separate java interface class file for any one of the two interfaces.
Maybe you have to do this for Activity
public class MyActivity extends AppCompatActivity implements MyFragment.OnSendFromMyFragListener {
public interface OnSendFromMyActivityListener {
void sendFromMyActivity(int b);
}
OnSendFromMyActivityListener mCallback;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyFragment myFragment = MyFragment.newInstance();
//Do the transaction.....
//And after this
mCallback.sendFromMyActivity(0);
}
public void setOnSendFromMyActivityListener(OnSendFromMyActivityListener mCallback){
this.mCallback = mCallback;
}
#Override
public void sendFromMyFrag(int a) {
//do something
}}
And this for Fragment
public class MyFragment extends Fragment implements OnSendFromMyActivityListener {
OnSendFromMyFragListener mCallback;
public interface OnSendFromMyFragListener {
void sendFromMyFrag(int a);
}
public static MyFragment newInstance() {
Bundle args = new Bundle();
MyFragment fragment = new MyFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (OnSendFromMyFragListener) getActivity();
((MyActivity) getActivity()).setOnSendFromMyActivityListener(this);
} catch (ClassCastException e) {
throw new ClassCastException(context.toString());
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_place_suggest, container, false);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mCallback.sendFromMyFrag(1);
}
});
return view;
}
#Override
public void sendFromMyActivity(int b) {
//do something
}
if you want you can put setters of the interface also to the Fragment for better abstraction.
Something you have to watch out is to look for nulls interfaces
Thanks
I have an Activity that starts a Fragment. Inside that fragment i have an EditText. And when i get the text that the user types there, i want to get that from my Activity with the help of an interface. I'm using this guide
On my MainActivity i'm implementing the commentListener interface and i've managed to get the result inside the onCommentEntered method. But on doneListener, which is triggered when the user presses a button finishing the activity, i'm getting null.
Obviously onCommentEntered runs after doneListener.
Any suggestions on how to get the result on doneListener?
class MainActivity implements fragment.commentListener{
static String comment;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtransaction);
done=(TextView)findViewById(R.id.done_button);
}
// called when user presses a button and MainActivity finishes.
public void doneListener(View v){
// Here i get NULL
System.out.println(comment);
finish();
}
#Override
public void onCommentEntered(String data) {
comment=data;
// Here i get what the user typed
System.out.println(comment);
}
}
My fragment
public class thefragment extends Fragment {
commentListener cListener;
static TextView note;
EditText comment;
public interface commentListener{
void onCommentEntered(String data);
}
public static thefragment newInstance(){
return new thefragment ();
}
public thefragment (){
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
cListener=(commentListener) context;
}
#Override
public void onDetach() {
super.onDetach();
cListener=null;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v;
v=inflater.inflate(R.layout.fragment, container, false);
comment=(EditText)v.findViewById(R.id.comment_picker);
comment.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
cListener.onCommentEntered(comment.getText().toString());
}
}
});
return v;
}
}
Create a new Interface, as well as implement the same in Fragment. create a method in the interface and override the same in Fragments. While calling the fragment from Activity create an Interface type fragment and call the interface method.
eg:
public class thefragment extends Fragment implement fragmentNofyInterface {
...
#Override
protected void notify(String txt){
mTvTxt.setText(txt);
}
.....
}
Interface Format
public interface fragmentNofyInterface {
protected void notify(String txt);
}
Activity format
class MainActivity implements fragment.commentListener{
.....
private fragmentNofyInterface mFragmentNotifier;
.........
thefragment mFragment = new thefragment();
mFragmentNotifier = (fragmentNofyInterface ) mFragment;
FragmentTransaction transaction = mFragmentMngr.beginTransaction().
add(R.id.rl_fragment_navigation_container,
mFragment);
transaction .commit();
......
//Notify the fragment when you required
mFragmentNotifier.notify("hello world");
}
Switching to addTextChangedListener and TextWatcher for my EditText, like this here,
seems to fix my problem.Now onCommentEntered method is called before doneListener and thus String commentgets whatever was typed in the EditText.Thanks everyone for helping.
I have a main activity class, and a private inner class within the main activity. The private inner class has methods that when called will display fragments. This inner class implements an interface defined in the Fragment's class, to be used as a sort of callback. It is probably easiest to show through code.
public class MainActivity extends FragmentActivity {
//on a button clicked
EditItemManger em = new EditItemManager();
em.begin();
private class EditItemManager implements on EditItemFragment.EditedItemClickedListener{
//consructor, other stuff. no onCreate method because this inner class does not (explicity??) extend activty
public void begin(){
EditItemFragment editItemFrag = new EditItemFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(editItemFrag, EDIT_FRAG_TAG)
fragmentTransaction.commit();
}
#Override
public void onEditItemClicked() {
editFinish();
}
public void editFinish()
{
// other stuff
}
}
}
My EditItemFragment class, where the onAttach method always has a null activity parameter
public class EditItemFragment extends DialogFragment {
protected EditedItemClickedListener editedItemClickedListener;
protected ImageButton button;
public EditItemFragment(){}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
final Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.edit_name_fragment, container, false);
button = (ImageButton) view.findViewById(R.id.submit_new_item_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
editedItemClickedListener.onEditedItemButtonClicked();
}
});
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
editedItemClickedListener= (EditedItemClickedListener) activity;
} catch (ClassCastException e) {
e.printStackTrace();
}
}
public interface OnEditNameButtonClickedListener {
public void onEditNameButtonClicked();
}
So because the parameter in onAttach() in my Fragment class is always null, it eventually causes a null pointer exception. I am wondering if it is because the fragment is called from a class that is not extending activity. The problem is that if this class extends activity, there will be an issue with trying to commit the Fragment Transaction
I guest your onAttach returns nullpointer exception. Its because your parent Activity the main activity doesnt implement your custom interface so it return null. See the code below let me know if it help you:
public class MainActivity extends FragmentActivity implements EditItemFragment.EditedItemClickedListener{
private DialogFragment editItemDialog;
//Do your code here
#Override
public void onEditItemClicked() {
editFinish();
}
public void showDialog(){
//this is for showing your custom dialog fragment
editItemDialog = EditItemFragment.newInstance();
editItemDialog.show(getSupportFragmentManager(),"editItemDialog");
}
}
this is for your EditItemFragment:
public class EditItemFragment extends DialogFragment{
//Do your code here
public static EditItemFragment newInstance(){
EditItemFragment editItemDialog = new EditItemFragment();
return editItemDialog;
}
}
I am going to explain my problem as short as possible.
I have a Fragment called FragmentA which displays a DialogFragment after clicking on a specific button.
public class FragmentA extends Fragment implements OnClickListener {
...
#Override
public void OnClick(View v) {
if (v == dialogButton) {
showDialog();
}
}
public void showDialog() {
String diagName = getResources().getString(R.string.dialog_title);
MyDialog myDialog = MyDialog.newInstance(getFragmentAValue());
myDialog.show(getFragmentManager(), diagName);
}
}
public class MyDialog extends DialogFragment implements OnClickListener {
...
#Override
public void onClick(View view) {
if (view == acceptButton) {
...
}
else if (view == cancelButton) {
...
}
}
}
The dialog is dispalyed without any problem. But my problem consists in after myDialog is dismissed onResume() method in FragmentA is never called and FragmentA is shown and you can interact with it without any problem.
public class FragmentA extends Fragment implements OnClickListener {
...
#Override
public void onResume() {
super.onResume();
resumeFragmentA();
}
}
So, what I have done in order to fix this issue is copying an instance of FragmentA in the end of onActivityCreated() method and call the method resumeFragmentA() when user has closed the dialog.
public class FragmentA extends Fragment implements OnClickListener {
FragmentA fragmentA = null;
...
#Override
public void onResume() {
super.onResume();
resumeFragmentA();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
...
fragmentA = this;
}
...
}
public class MyDialog extends DialogFragment implements OnClickListener {
...
#Override
public void onClick(View view) {
if (view == acceptButton) {
storeData();
dismissDialog();
}
else if (view == cancelButton) {
dismissDialog();
}
}
public void dismissDialog() {
FragmentA.fragmentA.resumeFragmentA();
dismiss();
}
}
I know this solution is tricky but I do not know to solve in a more brilliant way. Do you know it? Any Idea?
Thanks in advance!
For a better reading of my code, here you have my full code:
public class FragmentA extends Fragment implements OnClickListener {
...
FragmentA fragmentA = null;
...
#Override
public void OnClick(View v) {
if (v == dialogButton) {
showDialog();
}
}
public void showDialog() {
String diagName = getResources().getString(R.string.dialog_title);
MyDialog myDialog = MyDialog.newInstance(getFragmentAValue());
myDialog.show(getFragmentManager(), diagName);
}
#Override
public void onResume() {
super.onResume();
resumeFragmentA();
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
...
fragmentA = this;
}
...
}
public class MyDialog extends DialogFragment implements OnClickListener {
...
static MyDialog newInstance(int value) {
MyDialog fragment = new MyDialog ();
Bundle args = new Bundle();
args.putInt("value", value);
fragment.setArguments(args);
return fragment;
}
#Override
public void onClick(View view) {
if (view == acceptButton) {
storeData();
dismissDialog();
}
else if (view == cancelButton) {
dismissDialog();
}
}
public void dismissDialog() {
FragmentA.fragmentA.resumeFragmentA();
dismiss();
}
...
}
If you just want to call onResume of FragmentA, make a call to startActivityForResult inside your DialogFragment, and start your FragmentActivity. This will call onResume.
public void dismissDialog() {
getActivity().startActivityForResult(getActivity().getIntent(), 10);
dismiss()
}
Now Google has released Android Architecture Components library, the best solution is to use a shared ViewModel between both Fragments with your Activity as the one that implements LifecycleRegistryOwner.
Doing this, we will avoid refreshing UI calling onResume and we don't need to code any tricky solution.
A solution could be the following one:
public MyActivity extends AppCompatActivity {
...
}
public class FragmentA extends Fragment implements OnClickListener {
...
private SharedViewModel mSharedViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSharedViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
mSharedViewModel.getData().observe(this, value -> {
updateUIValue(value);
});
}
#Override
public void OnClick(View v) {
if (v == dialogButton) {
showDialog();
}
}
#Override
public void OnClick(View v) {
if (v == dialogButton) {
showDialog();
}
}
public void showDialog() {
String diagName = getResources().getString(R.string.dialog_title);
MyDialog myDialog = MyDialog.newInstance(getFragmentAValue());
myDialog.show(getFragmentManager(), diagName);
}
}
public class MyDialog extends DialogFragment implements OnClickListener {
...
private SharedViewModel mSharedViewModel;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSharedViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
mSharedViewModel.getData().observe(this, value -> {
updateUIValue(value);
});
}
#Override
public void onClick(View view) {
if (view == acceptButton) {
...
mSharedViewModel.setValue(newValue);
}
else if (view == cancelButton) {
dismiss();
}
}
}
public class SharedViewModel extends ViewModel {
final MutableLiveData<Resource<Value>> data = new MutableLiveData<>();
public MutableLiveData<Value> getData() {
return data;
}
public void setValue(Value newValue) {
this.data.setValue(newValue)
}
}
Another way is by implementing an interface. Create an interface and let the activity/fragment (which one you need) implements it, then pass itself as an interface object to your DialogFragment.
CustomDialogFragment df = new CustomDialogFragment();
Bundle b = new Bundle();
b.putSerializable(KEY, this); // current activity/fragment that implements the interface
df.setArguments(b);
df.show(getFragmentManager(), TAG);
Then in your DialogFragment get the listener, and call the listener's method when you need to (in this case after/before you called dismiss()).