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
Related
I have 1 main activity and 4 fragments. Each fragment have the same method name, each does a different thing (initializing data is the same, what each fragment does with the data is different).
At the moment I check which fragment is active and I call that method:
if (getSupportFragmentManager().findFragmentByTag("f1") != null)
Objects.requireNonNull((FragmentF1) getSupportFragmentManager().findFragmentByTag("f1")).setupData(true);
else if (getSupportFragmentManager().findFragmentByTag("f2") != null)
Objects.requireNonNull((FragmentF2) getSupportFragmentManager().findFragmentByTag("f2")).setupData(true);
I want to call that method, no matter the fragment I have now. Is it possible?
Also, the requireNonNull is there to avoid the lint warning despite the null check I do one line above, is there a way to make this code cleaner?
You can create base class or abstract class for your fragments
public abstract class BaseFragment extends AppCompatActivity {
abstract void setupData(boolean b);
}
Then each fragments inherit from BaseFragment class and overrides setupData(boolean b).
After you find the fragment check if he instace of BaseFragment and call the method
Fragment fragment = getSupportFragmentManager().findFragmentByTag("tag")
if (fragment instanceof BaseFragment){
fragment.setupData(true);
}
you can declare this method in your activity in which take fragment as parameter and in each time you call this method check on passed fragment and do your logic based on it.
public static void initData(Fragment fragment){
if (fragment instanceof FirstFragment){
// Do your logic here
}else if(fragment instanceof SecondFragment){
// Do your logic here
}
}//initData()
When you should call the method ?
I think in your cause you should use Event bus,
so with eventbus when something happen you should post the event to the active fragment
EventBus.getDefault().post("your event here");
and register the event in the fragments so when the fragment is currently launched and the event posted the fragment will receive the event
read more about event bus
I have a main fragment with a viewpager inside it. This viewpager has 2 pages (list fragments). When I start the activty, the main fragment is shown and I also show the first paged fragment. This paged fragment displays data from a db using AsyncTask.
In the main fragment I have:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
onPageSelected(0);
}
#Override
public void onPageSelected(int position) {
Fragment fragment = (Fragment) pagerAdapter.instantiateItem(viewPager, position);
if (fragment instanceof IPagedFragment) {
((IPagedFragment) fragment).onShown(getActivity());
}
}
And the interface is:
public interface IPagedFragment {
void onShown(FragmentActivity activity);
}
The first issue I have is that I have to pass the activity as a parameter because when onShown gets called, the activity is still null.
Furthermore, the paged fragments use progressbar logic similar to the LoginActivity sample. I also get the following exception:
IllegalStateException: Fragment PagedFragment1{4201f758} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:620)
So what is the correct stage to start retrieving data from db once the paged fragment is fully available to the UI?
Issues like yours is the reason some developers are starting to question if fragments are really that good or useful.
Also "the correct" is debatable as you can do it in a variety of places and different developers will give you different answers, But let me try to supply you some useful info.
The attach/detach callbacks:
public void onAttach(Activity activity);
public void onDetach();
between those two methods any call to getActivity() will return the non-null activity the fragments is connected to. You can override them and use a private boolean isAttached to keep track of that call.
Also useful is the:
public void onActivityCreated (Bundle savedInstanceState)
this method is called AFTER the Activity.onCreate method. That is very important if you rely on some initialisation that happened there.
Also it's important to remember that on the moment the fragment transaction happens, the Fragment.onCreate happens after the Activity.onCreate and during rotation it happens before it.
As a general rule of thumb I use the Fragment.onStart() / Fragment.onStop() for getting/listening to data. On those calls, all the UI have been created, the fragment is attached to the activity and those callbacks don't get called if there's a dialog/popup (pause/resume does)
From the documentation:
public void onActivityCreated (Bundle savedInstanceState)
[...] tells the fragment when it is fully associated with the new activity instance.
source: http://developer.android.com/reference/android/app/Fragment.html#onActivityCreated(android.os.Bundle)
To get the reference of your activity, create a local object of fragmentActivity and get your activity reference as shown below.
private FragmentActivity fragmentActivity;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
fragmentActivity=activity;
}
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();
}
Description of what I'm trying to accomplish:
I have an app that uses a FragmentActivity with a LinearLayout as a container for the fragments. I click different buttons on the FragmentActivity UI and add and remove Fragments to the container in the FragmentActivity. In addition to clicking buttons on the FragmentActivity UI, each Fragment also has buttons that can be clicked which will remove the current fragment and add a different fragment in its place.
The Android way of doing things as I understand it:
I have been reading up on how to do this and as I understand it, the 'proper' way of doing things is to use the FragmentActivity as sort of a relay station and have each fragment do callbacks to the FragmentActivity to communicate events and deal with them.
Scenario:
So let's say that the FragmentActivity is displaying Fragment A and when the user clicks a button in FragmentA I want to stop showing FragmentA and start showing FragmentB. To do this I have created an interface in FragmentA called AListener. In the onAttach() method of FragmentA I use the suggested method of checking that the FragmentActivity implements AListener. When the button in FragmentA is clicked I use one of the callback methods from AListener to communicate the click event to the FragmentActivity. In the FragmentActivity I create an instance of FragmentB and add it to the container in FragmentActivity. Then if some event happens in FragmentB I use the same scheme to communicate the event to the FragmentActivity and do something interesting.
So what's the problem?
For my application I have found this scheme of having Fragments call back to the FragmentActivity and then having the FragmentActivity create a new fragment or call forward to and existing fragment very cumbersome. I have many fragments that need to be displayed by the FragmentActivity and therefore I am implementing an interface for every type of fragment that needs to be displayed (Each fragment is different so they each have their own interface). This causes clashes when I have two interfaces that have the same method signatures and I'm forced to rename one of the methods.
For instance, if I want to attach a listener to a fragment using the onAttach() method of the fragment, then my FragmentActivity must implement the interface. I have found several instances where I have callback methods that have the same name (or I'm forced to name them something similar but different because of a namespace collision). One solution to this would be to use an anonymous classes as callbacks instead of having the FragmentActivity implement the interface. This seems to work well enough, but goes against what the Android documentation says about using the onAttach() method to set the listener.
Are there any elegant ways to approach this problem? It seems to me the tradeoff is that you either force the FragmentActivity to implement an interface for each Fragment that you want to display in it and have the fun problem of watching out for method signature collisions, or you go against the Android documentation and use Anonymous classes to handle the callbacks (not sure of the implications of this).
I am fairly new to Java and feel like I could be missing a concept here that would solve my problem. Can anyone set me straight on how to solve this problem elegantly?
I completely understand your problem since i was dealing it for a long time. Here is the solution i came up right now! It may need some modification based on your need but i it works well.
first of all to to make communicating of event easier in your app use an EventBus! here is the most famous one https://goo.gl/nAEW6
event bus allows you to send event from anywhere to anywhere without need to worry about implementing interfaces, broadcast receivers, threading, etc.
Then add FragmentOrganizer to your app. It's a base class for all of your Fragment Organizers. basically you need one for each activity. Here is the code
public abstract class FragmentOrganizer {
protected FragmentManager fragmentManager;
public FragmentOrganizer(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
openFragment(getInitialFragment());
EventBus.getDefault().register(this);
}
protected abstract Fragment getInitialFragment();
protected abstract void onEvent(Object event);
public abstract boolean handleBackNavigation();
public void freeUpResources(){
EventBus.getDefault().unregister(this);
}
protected Fragment getOpenFragment(){
String tag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() -1).getName();
return fragmentManager.findFragmentByTag(tag);
}
protected boolean isFragmentOpen(Fragment fragment){
return isFragmentOpen(fragment, true);
}
protected boolean isFragmentOpen(Fragment fragment, boolean useArgs){
String fragmentTag = createFragmentTag(fragment, useArgs);
if (fragmentManager.getBackStackEntryCount() != 0) {
String name = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();
if(!useArgs)
name = name.substring(0, name.indexOf("-"));
return name.equals(fragmentTag);
}
return false;
}
private String createFragmentTag(Fragment fragment, boolean addArgs) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(fragment.getClass().getSimpleName());
if(addArgs) {
stringBuilder.append("-");
if (fragment.getArguments() != null)
stringBuilder.append(fragment.getArguments().toString());
}
return stringBuilder.toString();
}
public void openFragment(Fragment fragment) {
if(isFragmentOpen(fragment))
return;
String fragmentTag = createFragmentTag(fragment, true);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.activity_main_fragment_container, fragment, fragmentTag);
transaction.addToBackStack(fragmentTag).commit();
}
}
Now you need to create your fragment organizer that inherit from FragmentOrganizer and implements 3 required methods. here the sample
public class MainFragmentOrganizer extends FragmentOrganizer {
public MainFragmentOrganizer(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
protected Fragment getInitialFragment() {
return HomeFragment.newInstance();
}
#Override
public void onEvent(Object event){
if(event instanceof ClickedOnPhotoEvent){
String photoCode = ((ClickedOnPhotoEvent) event).photoCode;
openFragment(PhotoFragment.newInstance(photoCode));
}
}
#Override
public boolean handleBackNavigation(){
Fragment fragment = getOpenFragment();
if (fragment instanceof HomeFragment){
return false;
} else {
fragmentManager.popBackStack();
return true;
}
}
}
And in your activity you just need to insatiate your FragmentManager and let it do the magic!
fragmentManager = getSupportFragmentManager();
fragmentOrganizer = new MainFragmentOrganizer(getSupportFragmentManager());
#Override
public void onBackPressed() {
//first let fragment organizer handle back. If it does not activity takes cares of it!
if(!fragmentOrganizer.handleBackNavigation()){
finish();
}
}
#Override
protected void onDestroy() {
fragmentOrganizer.freeUpResources();
super.onDestroy();
}
It may seem a lot of code but as you see most of the code encapsulated in FragmentOrganizer base class and it does all the general works so you just have to copy this file from one project to another.
As i said in the beginning i just came up with this solution right now, so it may not be perfect. I Plan to use this in my next project i hope you do to. And if you do i really appritiate if you share your though. have a good time
A co-worker of mine came up with what I consider an elegant solution to this problem.
Remember, what we're trying to achieve is a way for fragments to callback to the parent activity without having the activity implement the interface. Also, we need to be able to automatically set the listener again if the activity is destroyed and then recreated.
Activities have a lifecycle callback called onAttachFragment(Fragment fragment) which is called whenever a fragment is being attached to the activity. So, for instance, when a new fragment is created within the activity, this gets called. It also gets called if an activity that was previously destroyed gets recreated. What you can do is use an interface or an anonymous class to set a listener on the new fragment in onAttachFragment like this:
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
//Determine which fragment this is by checking its tag
if(fragment.getTag().contains(TextFrag.FRAG_TAG)){
//set a listener on this fragment using an anonymous class or interface
((TextFrag)fragment).setListener(new TextFragButtonListener() {
#Override
public void onButtonClicked() {
count++;
counterTV.setText(String.valueOf(count));
}
});
}
}
Using this technique we are able to avoid the activity having to implement an interface for the callback and thus we avoid any naming conflicts with our callback methods. Also, if the activity is destroyed, once it is recreated the listener will be automatically reset so our callbacks will still work.
There are probably many other ways to do this and I'd love to here anyone's criticisms of this technique and suggestions for any other techniques.
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)