Question

getActivity() within a Fragment will return null if called before fragment.onAttach(activity).

fragment.onActivityCreated(savedInstanceState) is called after fragment.onAttach(activity). See developer.android for info on fragment lifecycyle.

I have a method within my fragment that attempts to instantiate a database connection. When the method initially is called, it works fine.

public void onActivityCreated(Bundle savedInstanceState){
   super.onActivityCreated(savedInstanceState);
    Log.d("MyTag", "Activity being created");
    if (savedInstanceState == null){
        listAdapter.setContext(getActivity());
        execSearch(0);
    } else { listAdapter.setContext(getActivity()); }
}

public void execSearch(Long searchId) {
    MySQLiteHelper dbHelper = new MySQLiteHelper(getActivity());
    SQLiteDatabase database = dbHelper.getReadableDatabase();
    ... 
}

Now when this same method is called via an interface after a button click within a listview, getActivity() returns null (in the fragment.

Here is the button callback from within the ListAdapter:

private final View.OnClickListener editSearchListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Long search_id = (Long) v.getTag();
        ((MyInterface) context).searchCalled(search_id);
    }
};

And the interface implementation within the MainActivity:

@Override
public void searchCalled(Long search_id) {
    fragment.execSearch(id);
}

This second time around I get the null response from getActivity() within fragment.execSearch(id).

I'm stuck!! Does this have something to do with the interface call coming from a UI action. Do I need to implement mainActivity.searchCalled as a runnable on the UI thread???

Was it helpful?

Solution

Since this was my first time developing an Android app, wanted to share some of the things I learned to make solving this problem more intuitive for other new Android developers. Background on what I was developing:

  • Three fragment that allowed for creation of searches, a list of searches, and lists of search results. These all could be initialized on their own, but could send messages and receive messages from a shared interface (ie the SearchManager interface below).
  • A FragmentPagerAdapter which was used for assigning fragment to their respective tabs and moving between them. This was more appropriate for my design because I only had three tabs to swipe between. For more numerous tab sets use FragmentStatePagerAdapter.
  • a SearchManager Interface to communicate between fragments.

Why I had the problem above and numerous similar problems

Basically what I was trying to do is preserve any changes to the UI that the user had made across configuration changes (It's helpful for noobs to understand that activity creation is not the same as app launch - activities are repeatedly created when an app experiences configuration changes, or is replaced by another app. This was not intuitive to me at first.).

There are two ways to do this. You can either save the necessary data in Fragment.onSaveInstanceState and then re-create your fragments with the saved data when a new activity is created OR you can call setRetainInstance in your Fragment.onCreate.

I originally used the savedInstanceState method. Since this did not preserve my fragment instances, anytime I got getActivity() == null, it was because code was running from an old fragment instance not attached to the activity. This can happen when an interface is called, say from an onClickListener, that has not been updated to point to the current Activity instance (ie the one attached to the current activity). i.e. the listener was created before a configuration change, and called after the configuration change without ever being told that a new Activity instance had been created.

I eventually elected to go the setRetainInstance(true) route. This means:

  • onCreate is not called when a new activity is created. onDestroy is not called withen the activity is destroyed.
  • savedInstanceState in your onActivityCreated will always be null
  • any attributes on your fragments will be preserved.

This makes things easy and difficult. Basically I recommend this route if you have a lot of attributes/data that are not dependent on knowing what the current activity is (eg form values, lists to display). However, in this situation, any attributes that need to know about the current activity must be told when a new activity is created. You can do this as follows:

     @Override
     public void onAttach(Activity activity) {
        super.onAttach(activity);
        this.activity = (MainActivity) activity;
        // custom method on list adapter, so any calls to SearchManager interface 
        // are properly routed to current activity
        myListAdapter.setContext(activity); 
     }

Other Useful Things that Would've Helped to Know

Communication between fragment instances

I've heard conflicting suggestions about how to communicate between fragments. On one hand, I've heard that you shouldn't have references to your fragment instances outside of the adapter. On the other hand, you hear fragment instances should not communicate directly with each other. This would imply using your FragmentPagerAdapter to implement communication interface for your fragments. However, even in this case you'd have to be clever about getting your fragment instances within the adapter (eg you can get the adapter's tag for your fragments or better leverage the adapter's instantiateItem method).

Ultimately, because any way you cut the pie you've got to hack around to get your fragment instances, I went outside the adapter and created a headless fragment that implemented my SearchManager interface. That keeps the interface separated from the adapter, avoids communication between my functional fragments, and out of the MainActivity.

Preserving Views across configuration Change

One last note. When I created my New Search Form, I noticed all of the inputs were erased on my configuration changes (ie when new fragment was attached/created). This is because even if setRetainInstance is set to true, the fragment's onCreateView method is still called, requiring you to inflate your view and return it. It's this process that erases any user manipulation of the view. So I did the following in all of my fragment's onCreateView methods:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    if (view == null){
        view = inflater.inflate(R.layout.doc_list_fragment,
            container, false);
    } else {
        ((ViewGroup) view.getParent()).removeView(view);
    }
    setContext();
    return view;
}

Because setRetainInstance is true, the view is preserved within the fragment instance after it has been inflated the first time. The removeView business prevents this error:

FATAL EXCEPTION: main java.lang.IllegalStateException: 
The specified child already has a parent. You must call removeView() on the child's parent first. 

Also, I made a call to my custom setContext method here because it is at this point that i have both my view and my activity. Therefore, if any click listeners need to know what the current activity is, I now have my prepared view and can create listeners to properly point them to the current activity.

Completed Project

You can check out the completed app on GitHub.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top