Fragment has a method named getActivity() which returns the activity with which the fragment currently is associated.
Is it safe to not use this method, but instead save the Activity instance in the onAttach(Activity) method?
For example, change from:
public class MyFragment extends Fragment {
public void foo() {
((MainActivity) getActivity()).foo();
}
}
to:
public class MyFragment extends Fragment {
private MainActivity activity;
public void onAttach(Activity activity) {
this.activity = (MainActivity) activity;
}
public void foo() {
this.activity.foo();
}
}
Are there any differences between these two approaches? Which is better?
PS. One benefit of the second approach is that you don't have to do type conversion each time you use the activity (like (MainActivity) getActivity()). But I don't know whether it's safe to save the activity instance.
yes it's ok. I do that almost always to avoid calling method getActivity() and casting result every time (for better performance and better code)
Related
I call my DialogFragment like so:
If I am in an Activity:
MyDialogFragment dialogfragment = new MyDialogFragment();
dialogfragment.show(getFragmentManager(), "");
If I am already in a Fragment:
MyDialogFragment dialogfragment = new MyDialogFragment();
dialogfragment.show(getActivity().getFragmentManager(), "");
In MyDialogFragment, which inflates an XML and allows the user to input some values to EditTexts and so forth, I want to be able to return those values back to wherever I called the dialog from.
For the sake of the question let's say my dialog class wants to return some private variables String mName and int mValue.
Is there a proper way to do this without knowing where the dialog is being called from (either an Activity or a Fragment)? How do I pass the values back / how do I receive them?
If you want to send data to activity from fragment. You can do that by calling public method of activity by:
((MainActivity)getActivity()).sendData(Object object);
You can't do the same for sending data to a fragment.
As doc says:
All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly.
What you should do is:
Define an Interface in the fragment.
Implement that Interface in the activity
Deliver data to the activity
then activity will deliver data to some other fragment.
BTW, you can also use this practice to send data to activity (upto point 3).
Reference and example
Defining an interface:
public interface DataListener {
public void onDataReceived(Object obj);
}
inside the fragment:
public class MyFragment extends Fragment {
DataListener callback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
callback = (DataListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement DataListener");
}
}
}
Sending data from fragment;
callback.onDataReceived(object); // some object data
Receiving data in activity:
public static class MainActivity extends AppCompatActivity
implements DataListener{
public void onDataReceived(Object object) {
// Do something here with object data
}
}
Now if you want, you can send this data to any other fragment.
Sending data from activity to some other fragment:
AnotherFragment anotherFrag = (AnotherFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (anotherFrag != null) {
anotherFrag.receiveDataInFrag(object);
}else{
// create a new instance of the fragment and pass data to it.
}
Create a callback interface and have pass it into your dialogfragment
interface DialogValuesCallback {
void callThisFunctionWhenUserClicksOnOkInDialog(String passinmName,int passinmValue);
}
You can have your Activity or Fragment implement this interface.
Have a constructor in your MyDialogFragment which accepts the interface and assigns it to an member variable.
MyDialogFragment(DialogValuesCallback activityOrFragmentWhichImplementsThis){
mInterfaceCallbackObjectRef = activityOrFragmentWhichImplementsThis;
}
#Rohit Arya this is what you should know first that fragments needs to declare an interface that must be implemented by every activity that uses that fragment so you can pass data from the fragment to the activity(s)... and must cast the activity displaying the fragment currently into this interface like this in your onAttach method
//#param Listener is the declared interface in your fragment class
public class MyFragment extends Fragment {
Listener mInterface;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mInterface = (Listener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()`
+ " must implement DataListener");
}
}
public interface Listener{
//#param type = data type, cont = variable
public void sendData(type cont, type2 cont2)
;
}
}
make sure the interface declared in your fragment get implemented in your baseActivity then what ever data you pass to the interface using
mInterface.sendData(value, value1);
you can get it in the base activity like
public class baseActivity extends Activity implements MyFragment.Listener{
//#param cont & cont2 are data sent from MyFragment
#Override
public void sendData(type cont, type cont2){
//do what ever with your values here
}
}
got it now?
Im trying to implement fragment to activity communication.
Went through android developer doc where an Activity object is passed to onAttach life cycle and set up the Fragment-Activity communication.
This documentation asks to pass Context object instead of Activity. I replaced all the Activity objects by Context objects in the life cycle method onAttach. But it is throwing a NullPointerException while calling the method of the interface from Fragment.
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
colourChangerInterface = (ColourChangerInterface) context;
}
catch (Exception exp){
System.out.println("error!");
}
}
Can anyone please give a small example of the usage in the new way ?
Thanks
Edit :
Found this link where detail discussion is there on the same issue.
The issue is because of the broken API 'onAttach()'; it doesn't get called at all when Context object is passed.
A simple and quick solution found from the above link is to move the code from onAttach to onCreate.
Here is a small example that will describe you the communication between Activity and Fragment. Suppose you have a Interface ICommunication. This is given below:
public interface ICommunication {
public void testMethod();
}
Now you have a Activity name MainActivity that implements ICommunication then it must have implements the method testMethod(). This method will like this:
#Override
public void testMethod() {
Toast toast = Toast.makeText(getActivity(), "It's called from Fragment", Toast.LENGTH_SHORT).show();
}
Now, suppose this MainActivity belongs a Fragment name TestFragment . If you want to access testMethod() of MainActivity from TestFragment then you can simply call using this way :
((ICommunication)getActivity()).testMethod();
Here , TestFragment must be hold on MainActivity.
My related answer with source is here
Thats it :)
I have a single Activity application with a number of Fragments (15 or so). Some of the methods in my MyActivity are required by all the Fragments, such as displaying Dialogs. So what I have in a sample call from a Fragment (and they all extend MyFragment) is something like:
getMyActivity().displayDialog(msg);
and getMyActivity is defined as in MyFragment:
MyActivity getMyActivity() {
return (MyActivity) getActivity();
}
however, sometimes getActivity() is null so I get NPEs in that case. So what I'm doing is moving those methods into MyFragment such that:
protected void displayDialog(String msg) {
if (getMyActivity() != null) {
getMyActivity().displayDialog(msg);
} else {
// what do I do here?
}
}
Does this approach make sense for the 10 or so methods I need to reference from MyActivity (and are there any pitfalls to doing so)? Also, what would I do to provide feedback in the case where getActivity() is null?
Edit: A common example of a cause for a NullPointerException would be something like a network call being dispatched by the Fragment and on completion of said network call, trying to display a Dialog when the Activity was destroyed in the meantime.
Its better to use some ParentFragment for example
public abstract class ParentFragment extends Fragment {
public Activity activity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.activity = activity;
}
}
then you must owerride all yor fragments
public class SomeFragment extends ParentFragment {}
and use there activity for
activity.displayDialog(msg);
If the return value of getActivity () == null, then the Fragment is not attached in the FragmentManager of your parent activity.
A common mistake is to hold references of those fragments as object variables in the parent activity. This results in NPEs from Fragments.
I would recomend you to check whether this is your case.
If not, then:
See whether you are removing the Fragments from FragmentManager correctly
See whether you are adding the Fragments to the Framgnetmanager the right way.
if yes, remove the object variables and add the Fragments through the FragmentManager, see: http://www.survivingwithandroid.com/2013/04/android-fragment-transaction.html
Hope this helps
I'm writing an app that has a parent Activity and several child Fragments. I am trying to get the Fragment to communicate back to the parent Activity. I know there are several ways to do this, including making an interface and implementing that in the parent Activity. However, I am interested in this method:
ParentActivity activity = (ParentActivity) getActivity();
activity.someMethod();
This approach takes less code and is less complex. However, is this cast safe to do in production code? Or is an interface safer?
You can use this -
private ParentActivity callback;
#Override
public void onAttach(Activity activity)
{
//callback = (ParentActivity ) activity;
// make sure there is no cast exception
callback = (ParentActivty.class.isAssignableFrom(activity
.getClass())) ? (ParentActivity) activity : null;
super.onAttach(activity);
}
#Override
public void onDetach()
{
callback = null;
super.onDetach();
}
now when you do any method call , call it like this -
if(callback!=null)
{
callback.someMethod();
}
this method is safe .
It is safe (i.e. you won't get a ClassCastException), as long as you make sure that only ParentActivity ever creates/adds your Fragment.
These classes are now effectively coupled, which is, in general, not a good thing.
By casting to a specific Activity class (ParentActivity), you are losing the ability to re-use the fragment with different activities. It's safe to cast, as long as you only use the fragment with that one activity.
Using an interface allows the fragment to be used with multiple activities - you just need to implement the interface in the activities that use the fragment.
Another option is to use an Event Bus - like GreenRobot's EventBus or Square's Otto
Directly from the Android documentation:
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.
Here is an example of Fragment to Activity communication:
// HeadlinesFragment.java
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
public void setOnHeadlineSelectedListener(Activity activity) {
mCallback = activity;
}
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
// ...
}
// MainActivity.java
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
// ...
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof HeadlinesFragment) {
HeadlinesFragment headlinesFragment = (HeadlinesFragment) fragment;
headlinesFragment.setOnHeadlineSelectedListener(this);
}
}
}
It is safe if you know that the fragment won't be used in another activity. You can also do checks with instanceof so you can be sure that it will be the right type.
There are some possibilities where getActivity() might return null (when the fragment is not attached) so it's a good habit to check if the activity is null, or even better: myFragment.isAdded(). Otherwise you would get a NullPointerException when calling activity.someMethod().
So the safe code would be:
if (isAdded() && getActivity() instanceof ParentActivity){
ParentActivity activity = (ParentActivity) getActivity();
activity.someMethod();
}
Of course there are some other approaches, like passing listeners to the fragments or using a shared eventbus like Guava or Otto, which also have their pros and cons. The easiest way is the one described above, and if you use it carefully (check against null, correct class) it will work as expected.
I guess ParentActivity is derived from Activity.
getActivity() will provide you a pointer to the parent activity. So, there's no problem in the cast.
It is not "the Android way" to do things, but anyway, neither Google does lots of thing "the Android way" and this cast will surely continue to work ok in future Android versions.
As you said yourself, this not a good approach, google own documentation recommends using the interface, but if you choose to use this approach try something like:
Activity mActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.mActivity = activity;
}
to avoid the type NullPointerException error
if(this.mActivity != null){
this.mActivity.someMethod();
}
For Fragment-Activity communication, this is the suggested way of doing it, by using a listener.
In my case I have two fragments and a button at each and I would like them to do the exact same thing when pressed.
Should I create a separate listener class that the Activity implements and then instantiate a listener in each fragment or there is a better design that I am not aware of?
EDIT
I am sorry, I probably didn't communicate that properly. I am not looking for communication between fragments. I have a Fragment A with a buttonA and a Fragment B with a buttonB. When I click on buttonA, there is a listener in my Activity and method doSomething() is called. Now I want buttonB calling doSomething() too. Should I A) create a second listener and have the activity implement that too, B) create one separate listener class and use this one for both or C) a better choice ??
For communication between fragment to frament or activity to fragment communication via events. There are few alternatives are there e.g. this otto eventbus I know. and the tutorial about this can be found Here or just google it.
As from the documentation :
Two Fragments should never communicate directly.
So you best follow the pattern explained in the article and communicate thru the activity on which the fragments are attached.
When a listener is called from fragment A then get the fragment B from the fragmentManger
YourFragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_b);
fragment.doSomething();
I'm using this pattern and it works well for me:
public class Fragment1 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction1();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction2() {
// do your stuff...
}
}
public class Fragment2 extends Fragment {
FragmentListener mCallback;
public interface FragmentListener {
public void onAction2();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceof FragmentListener) {
mCallback = (FragmentListener) activity;
}
}
public void onAction1() {
// do your stuff...
}
}
public class MainActivity implements Fragment1.FragmentListener, Fragment2.FragmentListener {
private Fragment1 fragment1;
private Fragment2 fragment2;
/**
* Listening to events from first fragment and forwarding to second fragment
*/
#Override
public void onAction1() {
fragment2.onAction1();
}
/**
* Listening to events from second fragment and forwarding to first fragment
*/
#Override
public void onAction2() {
fragment1.onAction2();
}
}
The Activity listens to "events" from the fragments and if needed forward it to the other fragment(s).