Android. Fragment getActivity() sometimes returns null - android

In developer console error reports sometimes I see reports with NPE issue. I do not understand what is wrong with my code. On emulator and my device application works good without forcecloses, however some users get NullPointerException in fragment class when the getActivity() method is called.
Activity
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
pager = (ViewPager) findViewById(R.id.pager);
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) adapter.getItem(0));
firstTask.execute();
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
}
AsyncTask class
public class FirstTask extends AsyncTask{
private TaskListener taskListener;
...
#Override
protected void onPostExecute(T result) {
...
taskListener.onTaskComplete(result);
}
}
Fragment class
public class FirstFragment extends Fragment immplements Taskable, TaskListener{
public FirstFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.first_view, container, false);
}
#Override
public void executeTask() {
FirstTask firstTask = new FirstTask(MyActivity.this);
firstTask.setTaskListener(this);
firstTask.execute();
}
#Override
public void onTaskComplete(T result) {
// NPE is here
Resources res = getActivity().getResources();
...
}
}
Maybe this error happens when applications resumed from background. In this case how I should handle this situation properly?

It seems that I found a solution to my problem.
Very good explanations are given here and here.
Here is my example:
pulic class MyActivity extends FragmentActivity{
private ViewPager pager;
private TitlePageIndicator indicator;
private TabsAdapter adapter;
private Bundle savedInstanceState;
#Override
public void onCreate(Bundle savedInstanceState) {
....
this.savedInstanceState = savedInstanceState;
pager = (ViewPager) findViewById(R.id.pager);;
indicator = (TitlePageIndicator) findViewById(R.id.indicator);
adapter = new TabsAdapter(getSupportFragmentManager(), false);
if (savedInstanceState == null){
adapter.addFragment(new FirstFragment());
adapter.addFragment(new SecondFragment());
}else{
Integer count = savedInstanceState.getInt("tabsCount");
String[] titles = savedInstanceState.getStringArray("titles");
for (int i = 0; i < count; i++){
adapter.addFragment(getFragment(i), titles[i]);
}
}
indicator.notifyDataSetChanged();
adapter.notifyDataSetChanged();
// push first task
FirstTask firstTask = new FirstTask(MyActivity.this);
// set first fragment as listener
firstTask.setTaskListener((TaskListener) getFragment(0));
firstTask.execute();
}
private Fragment getFragment(int position){
return savedInstanceState == null ? adapter.getItem(position) : getSupportFragmentManager().findFragmentByTag(getFragmentTag(position));
}
private String getFragmentTag(int position) {
return "android:switcher:" + R.id.pager + ":" + position;
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("tabsCount", adapter.getCount());
outState.putStringArray("titles", adapter.getTitles().toArray(new String[0]));
}
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
Fragment currentFragment = adapter.getItem(position);
((Taskable) currentFragment).executeTask();
}
#Override
public void onPageScrolled(int i, float v, int i1) {}
#Override
public void onPageScrollStateChanged(int i) {}
});
The main idea in this code is that, while running your application normally, you create new fragments and pass them to the adapter. When you are resuming your application fragment manager already has this fragment's instance and you need to get it from fragment manager and pass it to the adapter.
UPDATE
Also, it is a good practice when using fragments to check isAdded before getActivity() is called. This helps avoid a null pointer exception when the fragment is detached from the activity. For example, an activity could contain a fragment that pushes an async task. When the task is finished, the onTaskComplete listener is called.
#Override
public void onTaskComplete(List<Feed> result) {
progress.setVisibility(View.GONE);
progress.setIndeterminate(false);
list.setVisibility(View.VISIBLE);
if (isAdded()) {
adapter = new FeedAdapter(getActivity(), R.layout.feed_item, result);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
}
If we open the fragment, push a task, and then quickly press back to return to a previous activity, when the task is finished, it will try to access the activity in onPostExecute() by calling the getActivity() method. If the activity is already detached and this check is not there:
if (isAdded())
then the application crashes.

Ok, I know that this question is actually solved but I decided to share my solution for this. I've created abstract parent class for my Fragment:
public abstract class ABaseFragment extends Fragment{
protected IActivityEnabledListener aeListener;
protected interface IActivityEnabledListener{
void onActivityEnabled(FragmentActivity activity);
}
protected void getAvailableActivity(IActivityEnabledListener listener){
if (getActivity() == null){
aeListener = listener;
} else {
listener.onActivityEnabled(getActivity());
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) activity);
aeListener = null;
}
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (aeListener != null){
aeListener.onActivityEnabled((FragmentActivity) context);
aeListener = null;
}
}
}
As you can see, I've added a listener so, whenever I'll need to get Fragments Activity instead of standard getActivity(), I'll need to call
getAvailableActivity(new IActivityEnabledListener() {
#Override
public void onActivityEnabled(FragmentActivity activity) {
// Do manipulations with your activity
}
});

The best to get rid of this is to keep activity reference when onAttach is called and use the activity reference wherever needed, for e.g.
#Override
public void onAttach(Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onDetach() {
super.onDetach();
mContext = null;
}
Edited, since onAttach(Activity) is depreciated & now onAttach(Context) is being used

Don't call methods within the Fragment that require getActivity() until onStart in the parent Activity.
private MyFragment myFragment;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
myFragment = new MyFragment();
ft.add(android.R.id.content, youtubeListFragment).commit();
//Other init calls
//...
}
#Override
public void onStart()
{
super.onStart();
//Call your Fragment functions that uses getActivity()
myFragment.onPageSelected();
}

I've been battling this kind of problem for a while, and I think I've come up with a reliable solution.
It's pretty difficult to know for sure that this.getActivity() isn't going to return null for a Fragment, especially if you're dealing with any kind of network behaviour which gives your code ample time to withdraw Activity references.
In the solution below, I declare a small management class called the ActivityBuffer. Essentially, this class deals with maintaining a reliable reference to an owning Activity, and promising to execute Runnables within a valid Activity context whenever there's a valid reference available. The Runnables are scheduled for execution on the UI Thread immediately if the Context is available, otherwise execution is deferred until that Context is ready.
/** A class which maintains a list of transactions to occur when Context becomes available. */
public final class ActivityBuffer {
/** A class which defines operations to execute once there's an available Context. */
public interface IRunnable {
/** Executes when there's an available Context. Ideally, will it operate immediately. */
void run(final Activity pActivity);
}
/* Member Variables. */
private Activity mActivity;
private final List<IRunnable> mRunnables;
/** Constructor. */
public ActivityBuffer() {
// Initialize Member Variables.
this.mActivity = null;
this.mRunnables = new ArrayList<IRunnable>();
}
/** Executes the Runnable if there's an available Context. Otherwise, defers execution until it becomes available. */
public final void safely(final IRunnable pRunnable) {
// Synchronize along the current instance.
synchronized(this) {
// Do we have a context available?
if(this.isContextAvailable()) {
// Fetch the Activity.
final Activity lActivity = this.getActivity();
// Execute the Runnable along the Activity.
lActivity.runOnUiThread(new Runnable() { #Override public final void run() { pRunnable.run(lActivity); } });
}
else {
// Buffer the Runnable so that it's ready to receive a valid reference.
this.getRunnables().add(pRunnable);
}
}
}
/** Called to inform the ActivityBuffer that there's an available Activity reference. */
public final void onContextGained(final Activity pActivity) {
// Synchronize along ourself.
synchronized(this) {
// Update the Activity reference.
this.setActivity(pActivity);
// Are there any Runnables awaiting execution?
if(!this.getRunnables().isEmpty()) {
// Iterate the Runnables.
for(final IRunnable lRunnable : this.getRunnables()) {
// Execute the Runnable on the UI Thread.
pActivity.runOnUiThread(new Runnable() { #Override public final void run() {
// Execute the Runnable.
lRunnable.run(pActivity);
} });
}
// Empty the Runnables.
this.getRunnables().clear();
}
}
}
/** Called to inform the ActivityBuffer that the Context has been lost. */
public final void onContextLost() {
// Synchronize along ourself.
synchronized(this) {
// Remove the Context reference.
this.setActivity(null);
}
}
/** Defines whether there's a safe Context available for the ActivityBuffer. */
public final boolean isContextAvailable() {
// Synchronize upon ourself.
synchronized(this) {
// Return the state of the Activity reference.
return (this.getActivity() != null);
}
}
/* Getters and Setters. */
private final void setActivity(final Activity pActivity) {
this.mActivity = pActivity;
}
private final Activity getActivity() {
return this.mActivity;
}
private final List<IRunnable> getRunnables() {
return this.mRunnables;
}
}
In terms of its implementation, we must take care to apply the life cycle methods to coincide with the behaviour described above by Pawan M:
public class BaseFragment extends Fragment {
/* Member Variables. */
private ActivityBuffer mActivityBuffer;
public BaseFragment() {
// Implement the Parent.
super();
// Allocate the ActivityBuffer.
this.mActivityBuffer = new ActivityBuffer();
}
#Override
public final void onAttach(final Context pContext) {
// Handle as usual.
super.onAttach(pContext);
// Is the Context an Activity?
if(pContext instanceof Activity) {
// Cast Accordingly.
final Activity lActivity = (Activity)pContext;
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(lActivity);
}
}
#Deprecated #Override
public final void onAttach(final Activity pActivity) {
// Handle as usual.
super.onAttach(pActivity);
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextGained(pActivity);
}
#Override
public final void onDetach() {
// Handle as usual.
super.onDetach();
// Inform the ActivityBuffer.
this.getActivityBuffer().onContextLost();
}
/* Getters. */
public final ActivityBuffer getActivityBuffer() {
return this.mActivityBuffer;
}
}
Finally, in any areas within your Fragment that extends BaseFragment that you're untrustworthy about a call to getActivity(), simply make a call to this.getActivityBuffer().safely(...) and declare an ActivityBuffer.IRunnable for the task!
The contents of your void run(final Activity pActivity) are then guaranteed to execute along the UI Thread.
The ActivityBuffer can then be used as follows:
this.getActivityBuffer().safely(
new ActivityBuffer.IRunnable() {
#Override public final void run(final Activity pActivity) {
// Do something with guaranteed Context.
}
}
);

#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// run the code making use of getActivity() from here
}

I know this is a old question but i think i must provide my answer to it because my problem was not solved by others.
first of all : i was dynamically adding fragments using fragmentTransactions.
Second: my fragments were modified using AsyncTasks (DB queries on a server).
Third: my fragment was not instantiated at activity start
Fourth: i used a custom fragment instantiation "create or load it" in order to get the fragment variable.
Fourth: activity was recreated because of orientation change
The problem was that i wanted to "remove" the fragment because of the query answer, but the fragment was incorrectly created just before. I don't know why, probably because of the "commit" be done later, the fragment was not added yet when it was time to remove it. Therefore getActivity() was returning null.
Solution :
1)I had to check that i was correctly trying to find the first instance of the fragment before creating a new one
2)I had to put serRetainInstance(true) on that fragment in order to keep it through orientation change (no backstack needed therefore no problem)
3)Instead of "recreating or getting old fragment" just before "remove it", I directly put the fragment at activity start.
Instantiating it at activity start instead of "loading" (or instantiating) the fragment variable before removing it prevented getActivity problems.

In Kotlin you can try this way to handle getActivity() null condition.
activity?.let { // activity == getActivity() in java
//your code here
}
It will check activity is null or not and if not null then execute inner code.

Related

Android : Fragment and Observer

Main goal is to update Fragment info mainly from its own class.
Main activity:
public class MainActivity extends AppCompatActivity {
final Handler GUIHandler = new Handler();
final Runnable r = new Runnable()
{
public void run()
{
updateFragments();
GUIHandler.postDelayed(this, 1000);
}
};
#Override
protected void onPause() {
super.onPause();
GUIHandler.removeCallbacks(r);
}
#Override
protected void onResume() {
super.onResume();
GUIHandler.postDelayed(r, 600);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mViewPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
mViewPager.setAdapter(mPagerAdapter);
...
}
private void updateFragments() {
mPagerAdapter.updateFragments();
}
PagerAdapter:
public class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private Observable mObservers = new FragmentObserver();
public PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
mObservers.deleteObservers(); // Clear existing observers.
switch (position) {
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
case 1:
FragmentMemo tab2 = new FragmentMemo();
return tab2;
case 2:
FragmentHardware tab3 = new FragmentHardware();
return tab3;
default:
return null;
}
}
public void updateFragments() {
mObservers.notifyObservers();
}
}
FragmentObserver
public class FragmentObserver extends Observable {
#Override
public void notifyObservers() {
setChanged(); // Set the changed flag to true, otherwise observers won't be notified.
super.notifyObservers();
Log.d("Observer", "Sending notification");
}
}
FragmentWeather:
public class FragmentWeather extends Fragment implements Observer {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
return layout;
}
public void setTemperatures(){
Log.d("Android", "setTemperatures is called");
}
#Override
public void update(Observable observable, Object data) {
setTemperatures();
}
}
Problem now is, that PagerAdapter::getItem() doesnt get called when Fragments are created at the start of application. That means WeatherFragment dont get associated with mObservers. If I swipe to the 3rd view and then swipe back, everything is working properly. How to restructurize this to make it working?
this line:
mObservers.deleteObservers(); // Clear existing observers.
is removing all the observers, but the method getItem gets called several times, that means only the last time it calls anything stays there. REMOVE this line.
Also, the following code is a very bad pattern and it will go wrong on several occasions:
case 0:
FragmentWeather weatherTab = new FragmentWeather();
weatherTab.setActivity(mActivity);
if(weatherTab instanceof Observer)
mObservers.addObserver((Observer) weatherTab);
return weatherTab;
that's because fragments get re-created by the system when necessary, so setActivity is pointless, so as is addObserver. The moment the system needs to destroy/recreate the fragments, you'll have a memory leak of those old fragments, the old activity, and the new ones won't have the activity and won't be on the observers.
The best situation here is to rely on the natural callbacks from the fragments. An example follows (ps.: that was typed by heart, I'm sure there might be some mistakes, but you'll get the idea)
public interface ObservableGetter{
public Observable getObservable();
}
public void MyFragment extends Fragment implements Observer {
#Override onAttach(Activity activity){
super.onAtttach(activity);
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
addObserver(this);
}
}
#Overrude onDetach(){
Activity activity = getActivity();
if(activity instanceof ObservableGetter){
((ObservableGetter)activity).getObservable().
removeObserver(this);
}
super.onDetach();
}
}
then you can just make the activity implements ObservableGetter and have the Observable on it.
Then your adapter code will be just:
case 0:
return new FragmentWeather();
all the rest of the logic uses the regular callbacks.
I hope it helps.

IllegalStateException fragment not attached to activity in onPostExecute after rotation but onDetach not called

I've an AppCompatActivity that uses the NavigationDrawer pattern, managing some fragments. In one of these, that has no setRetainInstance(true), I show a DialogFragment with a ProgressDialog inside and an AsyncTask with this code:
SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();
Where the SavingLoader class is this one:
public class SavingLoader extends DialogFragment {
private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";
public static SavingLoader newInstance(int max_value){
SavingLoader s = new SavingLoader();
Bundle args = new Bundle();
args.putInt(MAX_VALUE_TAG, max_value);
s.setArguments(args);
return s;
}
private ProgressDialog dialog;
public SavingLoader(){}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setCancelable(false);
}
#NonNull
#Override
public Dialog onCreateDialog(Bundle savedInstanceState){
dialog = new ProgressDialog(getActivity(), getTheme());
dialog.setTitle(getString(R.string.dialog_title_saving));
dialog.setMessage(getString(R.string.dialog_message_saving));
dialog.setIndeterminate(false);
int max = (savedInstanceState == null ?
getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
if (max >= 1){
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setProgress((savedInstanceState == null ?
0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
dialog.setMax(max);
} else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(MAX_VALUE_TAG, dialog.getMax());
outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
}
public int getProgress(){
return dialog.getProgress();
}
public int getMax(){
return dialog.getMax();
}
public void incrementProgressBy(int value){
if (dialog.getProgress() + value <= dialog.getMax())
dialog.incrementProgressBy(value);
}
}
In the onPostExecute() method I need to perform some UI update so here's my problem: if I start the dialog and the AsyncTask (like above) and I don't rotate my phone, all works as expected. Same thing if I rotate phone AFTER the onPostExecute() method. But if I rotate my phone WHILE the AsyncTask is still running, when it completes and reach the onPostExecute() method it gives me the IllegalStateException saying that the fragment hosting the AsyncTask and the Dialogfragment is no longer attached to the activity. So I tried to override both the onAttach() and the onDetach() methods (with a simple System.out.println) of my fragment, to see when the onPostExecute() gets called. The result is that when I rotate my phone, I always got this output:
onDetach
onAttach
... (if I rotate more my phone)
onPostExecute
So shouldn't the fragment be attached when the AsyncTask completes? Thank you all for your time and attention.
I've finally managed to solve this problem by stop using AsyncTask and using LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks interface and manage the AsyncTaskLoader. A skeleton fragment could be something like this:
public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
// Inflate here your view as you usually do and find your components
// For example imagine to have a button tha will fire the task
Button b = (Button) view.findViewById(R.id.my_button);
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Use this to start task for the first time
getLoaderManager().initLoader(0, null, this);
// .. or this for restart the task, details in
// the provided article
// getLoaderManager().restartLoader(0, null, this);
}
});
// Get fragments load manager
LoaderManager lm = getLoaderManager();
if (lm.getLoader(0) != null) {
// Reconnect to an existing loader
lm.initLoader(0, null, this);
}
// Return your view here
return view;
}
// LoaderCallbacks methods to override
#Override
public Loader onCreateLoader(int id, Bundle args) {
// Create an instance of the loader passing the context
MyTaskLoader loader = new MyTaskLoader(getActivity());
loader.forceLoad();
return loader;
}
#Override
public void onLoadFinished(Loader loader, Object data) {
// Use this callback as you would use the AsyncTask "onPostExecute"
}
#Override
public void onLoaderReset(Loader loader) {}
// Now define the loader class
private static class MyTaskLoader extends AsyncTaskLoader {
public MyTaskLoader(Context context){
super(context);
}
#Override
public Object loadInBackground() {
// Do here your async work
}
}
}

When findFragmentByTag is called on a fragment with setRetainInstance set to true, the same instance is not returned

I have a a single activity application with two fragments:
A fragment with all the UI;
A fragment without a view that has an AsyncTask as its member, and setRetainInstance set to true.
The goal is to keep the AsyncTask running even after the activity gets destroyed, and reuse it when the application comes back to focus.
I am not using setTargetFragment, all communication between fragments is done via the MainActivity.
What I thought setRetainInstance did is prevent the fragment from being recreated and keep the exact same instance at my disposal, so when I call findFragmentByTag when recreating a destroyed activity, it should return the same retained instance as when it got created, but that does not seem to be the case.
The result is that I end up with a noUi fragment that keeps counting in the background (I can see the bastard in the debugger), and another, recreated one that does not have the reference to my running AsyncTask...
What am I doing wrong?
Here's some code:
public class MainActivity extends Activity
implements FragmentCounter.Callback, FragmentMainScreen.Callback {
private static final String TAG_MAINFRAGMENT = "TAG_MAINFRAGMENT";
private static final String TAG_COUNTERFRAGMENT = "TAG_COUNTERFRAGMENT";
private FragmentMainScreen mFragmentMain;
private FragmentCounter mFragmentCounter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mFragmentMain = FragmentMainScreen.getInstance();
mFragmentCounter = FragmentCounter.getInstance();
getFragmentManager().beginTransaction()
.add(R.id.container, mFragmentMain, TAG_MAINFRAGMENT)
.add(mFragmentCounter, TAG_COUNTERFRAGMENT)
.commit();
} else {
mFragmentMain = (FragmentMainScreen) getFragmentManager()
.findFragmentByTag(TAG_MAINFRAGMENT);
//The fragment that gets returned here is not the same instance as the one
//I returned with getInstance() above.
mFragmentCounter = (FragmentCounter) getFragmentManager()
.findFragmentByTag(TAG_COUNTERFRAGMENT);
}
}
}
The noGui Fragment:
public class FragmentCounter extends Fragment
implements CounterAsyncTask.Callback, FragmentMainScreen.Callback {
private Callback mListener;
private CounterAsyncTask mCounterTask;
public static FragmentCounter getInstance(){
return new FragmentCounter();
}
public interface Callback {
public void onData(int aValue);
}
#Override
public void onAttach(Activity activity){
super.onAttach(activity);
if (activity instanceof Callback)
mListener = (Callback) activity;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return null;
}
#Override
public void onValueChanged(int value) {
//In the debugger, I can see this callback beeing called,
//even after my activity gets destroyed.
//The mListener is null since I set it that way in the onDetach to
//prevent memory leaks.
//The new activity has a different instance of this Fragment.
if (mListener != null)
mListener.onData(value);
}
#Override
public void startCounting(int from) {
mCounterTask = new CounterAsyncTask(this);
mCounterTask.execute(from);
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The AsyncTask:
public class CounterAsyncTask extends AsyncTask<Integer, Integer, Void>{
private int counter;
private Callback mListener;
private static final int SKIP = 5000;
public CounterAsyncTask(Callback listener) {
mListener = listener;
}
#Override
protected Void doInBackground(Integer...values) {
if (values != null)
counter = values[0];
while(!this.isCancelled()){
publishProgress(counter+=SKIP);
try{
Thread.sleep(SKIP);
}
catch(InterruptedException e){
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
mListener.onValueChanged(values[0]);
}
public interface Callback{
public void onValueChanged(int value);
}
}
Thanks in advance!
My mistake. With setRetainInstance the Fragment is retained only upon configuration change. So the fragment state will be maintained on screen rotation or if the activity is in the background but not if the activity gets destroyed.
To achieve the result I wanted I should probably use a Service.

ViewPager and Activity recreation / persisting data in fragments after activity onDestroy-onCreate

I wrote an activity with ViewPager, which gets populated after an AsyncTask is executed. Each TestDataObject is tied to the relevant TestFragment. When the screen is rotated the application crushes due to a NullPointerException inside onCreateView method. I believe this is because of ViewPager/Adapter onSaveInstanceState methods, onCreateView tries to restore data prior to the AsyncTask data load when data isn't available yet.
I could just if onCreateView code but it doesn't feel to me like a right solution, because amount of fragments inside ViewPager might vary so it might end up doing unnecessary job: restore altered viewpager content and then replace with initial. In this case onSaveInstanceState seems to be excessively harmful. Presumably, I could extend ViewPager or Adapter to cancel save procedure - I find it weird as well.
Do you have any better suggestions to offer?
public class MainActivity extends LoggerActivity {
private ArrayList<TestDataObject> mDataObjects = new ArrayList<MainActivity.TestDataObject>();
private ViewPager mViewPager;
private TestFragmentAdapter mViewPagerAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPagerAdapter = new TestFragmentAdapter(
getSupportFragmentManager(), mDataObjects);
mViewPager.setAdapter(mViewPagerAdapter);
new TestAsyncTask().execute();
}
private class TestAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
mDataObjects.add(new TestDataObject());
mDataObjects.add(new TestDataObject());
mDataObjects.add(new TestDataObject());
mViewPagerAdapter.notifyDataSetChanged();
}
}
public static class TestFragment extends Fragment {
private TestDataObject mDataObject;
public static TestFragment getInstance(TestDataObject obj) {
TestFragment f = new TestFragment();
f.mDataObject = obj;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// layout.find...
mDataObject.toString();
return inflater.inflate(R.layout.fragment_test, null, false);
}
}
public static class TestFragmentAdapter extends FragmentStatePagerAdapter {
private List<TestDataObject> mDataObjects;
public TestFragmentAdapter(FragmentManager fm, List<TestDataObject> objs) {
super(fm);
mDataObjects = objs;
}
#Override
public Fragment getItem(int position) {
return TestFragment.getInstance(mDataObjects.get(position));
}
#Override
public int getCount() {
return mDataObjects == null ? 0 : mDataObjects.size();
}
}
public static class TestDataObject {
}
}
I believe this is because of ViewPager/Adapter onSaveInstanceState
methods. onCreateView tries to restore data prior to the asynctask
dataload when data isn't available yet.
That is not what is happening(I'm assuming you get the exception at mDataObject.toString();), even if the AsyncTask would finish its job instantaneously the exception will still be thrown. After the first run of the app the ViewPager will have three fragments in it. When you'll turn the phone the Activity will be destroyed an recreated again. The ViewPager will try to recreate the fragments in it, but this time it will do it by using the default empty constructor(that is why you shouldn't use a non empty constructor to pass data). As you can see, the first time the Fragment is created by the adapter it will be created by the getInstance method(that is also the only point where you initialize mDataObject) to which you pass a TestDataObject object. When the ViewPager reinitializes its fragments that field will not be initialized as well.
If TestDataObject can be put in a Bundle then you could simply adapt your getInstance method to pass some arguments to your fragments(so the data field will be initialized when the ViewPager will recreate them). I'm sure you've seen:
public static TestFragment getInstance(TestDataObject obj) {
TestFragment f = new TestFragment();
// f.mDataObject = obj; <- don't do this
// if possible
Bundle args = new Bundle();
args.put("data", obj); // only if obj can be put in a Bundle
f.setArguments(args);
return f;
}
private TestDataObject mDataObject;
#Override
public void onCreate(Bundle savedInstance) {
mDataObject = getArguments().get("data"); // again, depends on your TestDataObject
}
Another approach would be to pass the smallest amount of data to the Fragment(like above) so it has enough information to recreate it's data whenever it's recreated.

Fragment MyFragment not attached to Activity

I've created a small test app which represents my problem.
I'm using ActionBarSherlock to implement tabs with (Sherlock)Fragments.
My code:
TestActivity.java
public class TestActivity extends SherlockFragmentActivity {
private ActionBar actionBar;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setupTabs(savedInstanceState);
}
private void setupTabs(Bundle savedInstanceState) {
actionBar = getSupportActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
addTab1();
addTab2();
}
private void addTab1() {
Tab tab1 = actionBar.newTab();
tab1.setTag("1");
String tabText = "1";
tab1.setText(tabText);
tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class));
actionBar.addTab(tab1);
}
private void addTab2() {
Tab tab1 = actionBar.newTab();
tab1.setTag("2");
String tabText = "2";
tab1.setText(tabText);
tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class));
actionBar.addTab(tab1);
}
}
TabListener.java
public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener {
private final SherlockFragmentActivity mActivity;
private final String mTag;
private final Class<T> mClass;
public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) {
mActivity = activity;
mTag = tag;
mClass = clz;
}
/* The following are each of the ActionBar.TabListener callbacks */
public void onTabSelected(Tab tab, FragmentTransaction ft) {
SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
// Check if the fragment is already initialized
if (preInitializedFragment == null) {
// If not, instantiate and add it to the activity
SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName());
ft.add(android.R.id.content, mFragment, mTag);
} else {
ft.attach(preInitializedFragment);
}
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag);
if (preInitializedFragment != null) {
// Detach the fragment, because another one is being attached
ft.detach(preInitializedFragment);
}
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// User selected the already selected tab. Usually do nothing.
}
}
MyFragment.java
public class MyFragment extends SherlockFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
return null;
}
#Override
protected void onPostExecute(Void result){
getResources().getString(R.string.app_name);
}
}.execute();
}
}
I've added the Thread.sleep part to simulate downloading data. The code in the onPostExecute is to simulate use of the Fragment.
When I rotate the screen very fast between landscape and portrait, I get an Exception at the onPostExecute code:
java.lang.IllegalStateException: Fragment MyFragment{410f6060} not
attached to Activity
I think it's because a new MyFragment has been created in the meantime, and was attached to the Activity before the AsyncTask finished. The code in onPostExecute calls upon a unattached MyFragment.
But how can I fix this?
I've found the very simple answer: isAdded():
Return true if the fragment is currently added to its activity.
#Override
protected void onPostExecute(Void result){
if(isAdded()){
getResources().getString(R.string.app_name);
}
}
To avoid onPostExecute from being called when the Fragment is not attached to the Activity is to cancel the AsyncTask when pausing or stopping the Fragment. Then isAdded() would not be necessary anymore. However, it is advisable to keep this check in place.
The problem is that you are trying to access resources (in this case, strings) using getResources().getString(), which will try to get the resources from the Activity. See this source code of the Fragment class:
/**
* Return <code>getActivity().getResources()</code>.
*/
final public Resources getResources() {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
return mHost.getContext().getResources();
}
mHost is the object that holds your Activity.
Because the Activity might not be attached, your getResources() call will throw an Exception.
The accepted solution IMHO is not the way to go as you are just hiding the problem. The correct way is just to get the resources from somewhere else that is always guaranteed to exist, like the application context:
youApplicationObject.getResources().getString(...)
I've faced two different scenarios here:
1) When I want the asynchronous task to finish anyway: imagine my onPostExecute does store data received and then call a listener to update views so, to be more efficient, I want the task to finish anyway so I have the data ready when user cames back. In this case I usually do this:
#Override
protected void onPostExecute(void result) {
// do whatever you do to save data
if (this.getView() != null) {
// update views
}
}
2) When I want the asynchronous task only to finish when views can be updated: the case you're proposing here, the task only updates the views, no data storage needed, so it has no clue for the task to finish if views are not longer being showed. I do this:
#Override
protected void onStop() {
// notice here that I keep a reference to the task being executed as a class member:
if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true);
super.onStop();
}
I've found no problem with this, although I also use a (maybe) more complex way that includes launching tasks from the activity instead of the fragments.
Wish this helps someone! :)
Their are quite trick solution for this and leak of fragment from activity.
So in case of getResource or anything one which is depending on activity context accessing from Fragment it is always check activity status and fragments status as follows
Activity activity = getActivity();
if(activity != null && isAdded())
getResources().getString(R.string.no_internet_error_msg);
//Or any other depends on activity context to be live like dailog
}
}
The problem with your code is the way the you are using the AsyncTask, because when you rotate the screen during your sleep thread:
Thread.sleep(2000)
the AsyncTask is still working, it is because you didn't cancel the AsyncTask instance properly in onDestroy() before the fragment rebuilds (when you rotate) and when this same AsyncTask instance (after rotate) runs onPostExecute(), this tries to find the resources with getResources() with the old fragment instance(an invalid instance):
getResources().getString(R.string.app_name)
which is equivalent to:
MyFragment.this.getResources().getString(R.string.app_name)
So the final solution is manage the AsyncTask instance (to cancel if this is still working) before the fragment rebuilds when you rotate the screen, and if canceled during the transition, restart the AsyncTask after reconstruction by the aid of a boolean flag:
public class MyFragment extends SherlockFragment {
private MyAsyncTask myAsyncTask = null;
private boolean myAsyncTaskIsRunning = true;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null) {
myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning");
}
if(myAsyncTaskIsRunning) {
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning);
}
#Override
public void onDestroy() {
super.onDestroy();
if(myAsyncTask!=null) myAsyncTask.cancel(true);
myAsyncTask = null;
}
public class MyAsyncTask extends AsyncTask<Void, Void, Void>() {
public MyAsyncTask(){}
#Override
protected void onPreExecute() {
super.onPreExecute();
myAsyncTaskIsRunning = true;
}
#Override
protected Void doInBackground(Void... params) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {}
return null;
}
#Override
protected void onPostExecute(Void result){
getResources().getString(R.string.app_name);
myAsyncTaskIsRunning = false;
myAsyncTask = null;
}
}
}
if (getActivity() == null) return;
works also in some cases. Just breaks the code execution from it and make sure the app not crash
I faced the same problem i just add the singletone instance to get resource as referred by Erick
MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);
you can also use
getActivity().getResources().getString(R.string.app_name);
I hope this will help.
I faced similar issues when the application settings activity with the loaded preferences was visible. If I would change one of the preferences and then make the display content rotate and change the preference again, it would crash with a message that the fragment (my Preferences class) was not attached to an activity.
When debugging it looked like the onCreate() Method of the PreferencesFragment was being called twice when the display content rotated. That was strange enough already. Then I added the isAdded() check outside of the block where it would indicate the crash and it solved the issue.
Here is the code of the listener that updates the preferences summary to show the new entry. It is located in the onCreate() method of my Preferences class which extends the PreferenceFragment class:
public static class Preferences extends PreferenceFragment {
SharedPreferences.OnSharedPreferenceChangeListener listener;
#Override
public void onCreate(Bundle savedInstanceState) {
// ...
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// check if the fragment has been added to the activity yet (necessary to avoid crashes)
if (isAdded()) {
// for the preferences of type "list" set the summary to be the entry of the selected item
if (key.equals(getString(R.string.pref_fileviewer_textsize))) {
ListPreference listPref = (ListPreference) findPreference(key);
listPref.setSummary("Display file content with a text size of " + listPref.getEntry());
} else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) {
ListPreference listPref = (ListPreference) findPreference(key);
listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once");
}
}
}
};
// ...
}
I hope this will help others!
If you extend the Application class and maintain a static 'global' Context object, as follows, then you can use that instead of the activity to load a String resource.
public class MyApplication extends Application {
public static Context GLOBAL_APP_CONTEXT;
#Override
public void onCreate() {
super.onCreate();
GLOBAL_APP_CONTEXT = this;
}
}
If you use this, you can get away with Toast and resource loading without worrying about lifecycles.
I had a similar error message "Fragment MyFragment not attached to Context" in Xamarine Android.
this error messege getting because of this resource calling
button.Text = Resources.GetString(Resource.String.please_wait)
I did fix that by using in Xamarine Android.
if (Context != null && IsAdded){
button.Text = Resources.GetString(Resource.String.please_wait);
}
In my case fragment methods have been called after
getActivity().onBackPressed();
An old post, but I was surprised about the most up-voted answer.
The proper solution for this should be to cancel the asynctask in onStop (or wherever appropriate in your fragment). This way you don't introduce a memory leak (an asynctask keeping a reference to your destroyed fragment) and you have better control of what is going on in your fragment.
#Override
public void onStop() {
super.onStop();
mYourAsyncTask.cancel(true);
}
Add This on your Fragemnt
Activity activity;
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
activity = context instanceof Activity ? (Activity) context : null;
}
Then change getContext() , getActivity() , requireActivity() or requireContext() with activity
simple solution and work 100%
if (getActivity() == null || !isAdded()) return;

Categories

Resources