Android Activity lifecycle problems - android

I'm working on the project initially created by another developer. There is a root Activity (let's call it CustomActivity) with code below.
private static SomeOtherClass instance = null;
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(null);
if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof CustomExceptionHandler)) {
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler());
}
// #see http://stackoverflow.com/questions/19545889/app-restarts-rather-than-resumes)
if (!isTaskRoot() && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER) && null != getIntent().getAction()
&& getIntent().getAction().equals(Intent.ACTION_MAIN)) {
finish();
return;
}
instance = new SomeOtherClass();
this.screen = new CustomFragment();
this.getFragmentManager.beginTransaction()
.replace(R.id.content_frame, screen).commit();
...
}
#Override
protected void onDestroy()
{
this.instance = null;
...
}
static public SomeOtherClass getInstance()
{
return instance;
}
this.screen has a button with CustomActivity.getInstance().methodCall() on tap. And one of beta testers said he just tapped that button and got crash with this method in stacktrace: Attempt to invoke virtual method '...CustomActivity.getInstance().methodCall()' on a null object reference.
I don't understand - how it's possible due to Activity lifecycle.
According to stacktrace, onCreate shouldn't be called after some previous onDestroy. Even if the last one can happen when we were in another Activity (and yep, this.screen is not nullified anywhere), but this.screen fragment can't be rendered without Activity recreation. Am I right?
P.S.: there is no more instance variable management at all and SomeOtherClass has no custom parent class (just default object).
P.P.S.: nope, device wasn't locked / app just launched. Tester worked with it, rotated phone to remove sim card, removed, rotated back and saw crash alert.
P.P.P.S: don't know why null in super.onCreate() but anyhow this Activity has no code to support saved states.

Related

Save custom object on screen rotate in Fragment or Activity

I know this question is very common and I have read so many different answers but none fits in my problem. In my application, I have an activity and in rhye activity I load a fragment. I also send some data(in the form of Bundle) to the fragment. So my Problem is when the screen is rotated, I save the fragment in onSaveInstanceState Activity method and check in onCreate Method weather savedInstance is null or not and on that basis I load the fragment.
Activity code :
#Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
outState.putParcelable(Const.TAG_REQ_CUSTOM,DetailsItems);
outState.putString(Const.TAG_FLOW, Const.TAG_MAIN_FLOW);
getSupportFragmentManager().putFragment(outState,"current_fragment",fragment);
}
onCreate Method :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
// this invoke when screen rotate but the app crash
DetailsItems = savedInstanceState.getParcelable(Const.TAG_REQ_CUSTOM);
String flow = savedInstanceState.getString(Const.TAG_FLOW);
ft = getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
mFragmentManager=getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
bundle= new Bundle();
bundle.putString(Const.TAG_FLOW, flow);
bundle.putParcelable(Const.TAG_REQ_BOOKING_DETAILS, bookingDetailsItems);
ft.setArguments(bundle);
mFragmentTransaction.replace(R.id.fragment_frame, ft).commit();
}else{
// load fragment on first time
}
}
So my Question is: Where do I have to save the custom Object(in parent Activity or in fragment) ?
When my saved Instance is not null than app crashesh and logs is :
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Class java.lang.Object.getClass()' on a null object reference
You should use ViewModel. ViewModel is specifically made for this purpose.
From the docs:
ViewModel is a class that is responsible for preparing and managing the data for an Activity or a Fragment. It also handles the communication of the Activity / Fragment with the rest of the application (e.g. calling the business logic classes).
use this code in Activity :
if (findViewById(R.id.fragment_frame) != null) {
if (savedInstanceState != null) {
fragment =getSupportFragmentManager().getFragment(savedInstanceState,"current_fragment");
}else{
// load fragment on first time
}
}
and in fragment :
//save custom object
#Override
public void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putParcelable("key",customObject);
}
//now retrieve here
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
customObject= savedInstanceState.getParcelable("key");
}
Take a look at onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()
From docs:
Called by the system, as part of destroying an activity due to a configuration change, when it is known that a new instance will immediately be created for the new configuration. You can return any object you like here, including the activity instance itself, which can later be retrieved by calling getLastNonConfigurationInstance() in the new activity instance.

Android - App crashes at re-launch after app being force closed by OS because of permission disabled

Steps to produce the problem.
1) Launch Tasks.class activity
2) Hit home button puting the app in the background
3) Go to Settings->permissions and disable a permission which results in my app being force closed by the OS
4) Open app from launcher ---> app resumes Tasks.class activity instead of opening MainActivity --> CRASH
It crashed at listView.setAdapter(mAdapter); because listView was now null as were all class variables; Here is my activity. It uses the TasksFragment class as data retainer to handle orientation changes. When the TasksFragment is created it starts loading data with an AsyncTask and notifies the Activity when new data are fetched using the callbacks
public class Tasks extends FragmentActivity implements TasksFragment.TaskCallbacks{
TasksFragment f;
FragmentManager fm;
String TASKS_TAG="TASKS";
searchList = new ArrayList<Task>();
TaskAdapter mAdapter;
ListView listView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tasks);
.
.
.
fm = getSupportFragmentManager();
f = (TasksFragment) fm.findFragmentByTag(TASKS_TAG);
if (f == null) {
f = new TasksFragment();
fm.beginTransaction().add(f, TASKS_TAG).commit();
}
else{
preLoad();
postLoad();
}
}
#Override //TaskFragmentCallback
public void onPreExecute() {
preLoad();
}
#Override //TaskFragmentCallback
public void onProgressUpdate(Task task) {
searchList.add(task);
mAdapter.notifyDataSetChanged();
}
#Override //TaskFragmentCallback
public void onCancelled() { }
#Override //TaskFragmentCallback
public void onPostExecute() {
postLoad();
}
public void preLoad(){
searchList = new ArrayList<Task>();
mAdapter = new TaskAdapter(this,searchList,true);
listView.setAdapter(mAdapter);
}
public void postLoad(){
searchList.clear();
searchList.addAll(f.taskList);
mAdapter.notifyDataSetChanged();
}
}
I managed to make it not crash replacing the onPreExecute callback with
#Override //TaskFragmentCallback
public void onPreExecute() {
if(listView!=null){
preLoad();
}
}
I used log statements to determine the sequence of events and I saw the following:
First app launch and lauch Tasks.class
ON ACTIVITY CREATE
ON FRAGMENT CREATE
on pre execute callback
preload called by callback
ON ACTIVITY RESUME
On reopen after app termination while Tasks.class was open and app was put to background
ON FRAGMENT CREATE
on pre execute callback //this is where it would crash
ON ACTIVITY CREATE
preload called by activity because fragment!=null
ON ACTIVITY RESUME
I can't understand why the fragment is created before the activity...who called this fragment to be created? Though the if(listView!=null) condition solved my problem I think it's a bandaid and not a real solution. Can someone shed some wisdom on this problem ?
What I believe is happening (somebody correct me if I'm wrong) is that because you're artificially shutting down the application with System.exit(0) which is not a recommended practice (so far as I know) in Android, your Fragment isn't being cleanly detached. This results in the Fragment being resumed when you open the app back up.
The check you're doing if (listView != null) works because your Fragment is sort of a ghost. It still exists, but it's not attached to the activity and so it can't obtain a reference to the ListView you're trying to use. A similar, but more "valid" check might be if (this.getActivity() != null).
From android System documentation
Causes the VM to stop running and the program to exit with the given exit status. If runFinalizersOnExit(boolean) has been previously invoked with a true argument, then all objects will be properly garbage-collected and finalized first.
Not sure it it will make a difference. Generally I never exit() an android program.

java.lang.IllegalStateException: Fragment not attached to Activity

I am rarely getting this error while making an API call.
java.lang.IllegalStateException: Fragment not attached to Activity
I tried putting the code inside isAdded() method to check whether fragment is currently added to its activity but still i rarely gets this error. I fail to understand why I am still getting this error. How can i prevent it?
Its showing error on the line-
cameraInfo.setId(getResources().getString(R.string.camera_id));
Below is the sample api call that i am making.
SAPI.getInfo(getActivity(),
new APIResponseListener() {
#Override
public void onResponse(Object response) {
cameraInfo = new SInfo();
if(isAdded()) {
cameraInfo.setId(getResources().getString(R.string.camera_id));
cameraInfo.setName(getResources().getString(R.string.camera_name));
cameraInfo.setColor(getResources().getString(R.string.camera_color));
cameraInfo.setEnabled(true);
}
}
#Override
public void onError(VolleyError error) {
mProgressDialog.setVisibility(View.GONE);
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(getActivity(), errormsg, Toast.LENGTH_LONG).show();
}
}
});
This error happens due to the combined effect of two factors:
The HTTP request, when complete, invokes either onResponse() or onError() (which work on the main thread) without knowing whether the Activity is still in the foreground or not. If the Activity is gone (the user navigated elsewhere), getActivity() returns null.
The Volley Response is expressed as an anonymous inner class, which implicitly holds a strong reference to the outer Activity class. This results in a classic memory leak.
To solve this problem, you should always do:
Activity activity = getActivity();
if(activity != null){
// etc ...
}
and also, use isAdded() in the onError() method as well:
#Override
public void onError(VolleyError error) {
Activity activity = getActivity();
if(activity != null && isAdded())
mProgressDialog.setVisibility(View.GONE);
if (error instanceof NoConnectionError) {
String errormsg = getResources().getString(R.string.no_internet_error_msg);
Toast.makeText(activity, errormsg, Toast.LENGTH_LONG).show();
}
}
}
Fragment lifecycle is very complex and full of bugs, try to add:
Activity activity = getActivity();
if (isAdded() && activity != null) {
...
}
I Found Very Simple Solution isAdded() method which is one of the fragment method to identify that this current fragment is attached to its Activity or not.
we can use this like everywhere in fragment class like:
if(isAdded())
{
// using this method, we can do whatever we want which will prevent **java.lang.IllegalStateException: Fragment not attached to Activity** exception.
}
Exception: java.lang.IllegalStateException: Fragment
DeadlineListFragment{ad2ef970} not attached to Activity
Category: Lifecycle
Description: When doing time-consuming operation in background thread(e.g, AsyncTask), a new Fragment has been created in the meantime, and was detached to the Activity before the background thread finished. The code in UI thread(e.g.,onPostExecute) calls upon a detached Fragment, throwing such exception.
Fix solution:
Cancel the background thread when pausing or stopping the
Fragment
Use isAdded() to check whether the fragment is attached
and then to getResources() from activity.
i may be late but may help someone .....
The best solution for this is to create a global application class instance and call it in the particular fragment where your activity is not being attached
as like below
icon = MyApplication.getInstance().getString(R.string.weather_thunder);
Here is application class
public class MyApplication extends Application {
private static MyApplication mInstance;
private RequestQueue mRequestQueue;
#Override
public void onCreate() {
super.onCreate();
mInstance = this;
}
public static synchronized MyApplication getInstance() {
return mInstance;
}
}
In Fragment use isAdded()
It will return true if the fragment is currently attached to Activity.
If you want to check inside the Activity
Fragment fragment = new MyFragment();
if(fragment.getActivity()!=null)
{ // your code here}
else{
//do something
}
Hope it will help someone
This error can happen if you are instantiating a fragment that somehow can't be instantiated:
Fragment myFragment = MyFragment.NewInstance();
public classs MyFragment extends Fragment {
public void onCreate() {
// Some error here, or anywhere inside the class is preventing it from being instantiated
}
}
In my case, i have met this when i tried to use:
private String loading = getString(R.string.loading);
So the base idea is that you are running a UI operation on a fragment that is getting in the onDetach lifecycle.
When this is happening the fragment is getting off the stack and losing the context of the Activity.
So when you call UI related functions for example calling the progress spinner and you want to leave the fragment check if the Fragment is added to the stack, like this:
if(isAdded){ progressBar.visibility=View.VISIBLE }
This will solve your problem.
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
I adopted the following approach for handling this issue. Created a new class which act as a wrapper for activity methods like this
public class ContextWrapper {
public static String getString(Activity activity, int resourceId, String defaultValue) {
if (activity != null) {
return activity.getString(resourceId);
} else {
return defaultValue;
}
}
//similar methods like getDrawable(), getResources() etc
}
Now wherever I need to access resources from fragments or activities, instead of directly calling the method, I use this class. In case the activity context is not null it returns the value of the asset and in case the context is null, it passes a default value (which is also specified by the caller of the function).
Important This is not a solution, this is an effective way where you can handle this crash gracefully. You would want to add some logs in cases where you are getting activity instance as null and try to fix that, if possible.
this happen when the fragment does not have a context ,thus the getActivity()method return null.
check if you use the context before you get it,or if the Activity is not exist anymore . use context in fragment.onCreate and after api response usually case this problem
Sometimes this exception is caused by a bug in the support library implementation. Recently I had to downgrade from 26.1.0 to 25.4.0 to get rid of it.
This issue occurs whenever you call a context which is unavailable or null when you call it. This can be a situation when you are calling main activity thread's context on a background thread or background thread's context on main activity thread.
For instance , I updated my shared preference string like following.
editor.putString("penname",penNameEditeText.getText().toString());
editor.commit();
finish();
And called finish() right after it. Now what it does is that as commit runs on main thread and stops any other Async commits if coming until it finishes. So its context is alive until the write is completed. Hence previous context is live , causing the error to occur.
So make sure to have your code rechecked if there is some code having this context issue.

Fragment's reference to mActivity becomes null after orientation change. Ineffective fragment state maintenance

My application consists of several fragments. Up until now I've had references to them stored in a custom Application object, but I am beginning to think that I'm doing something wrong.
My problems started when I realized that all my fragment's references to mActivity becomes null after an orientation change. So when I call getActivity() after an orientation change, a NullPointerException is thrown.
I have checked that my fragment's onAttach() is called before I make the call to getActivity(), but it still returns null.
The following is a stripped version of my MainActivity, which is the only activity in my application.
public class MainActivity extends BaseActivity implements OnItemClickListener,
OnBackStackChangedListener, OnSlidingMenuActionListener {
private ListView mSlidingMenuListView;
private SlidingMenu mSlidingMenu;
private boolean mMenuFragmentVisible;
private boolean mContentFragmentVisible;
private boolean mQuickAccessFragmentVisible;
private FragmentManager mManager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/*
* Boolean variables indicating which of the 3 fragment slots are visible at a given time
*/
mMenuFragmentVisible = findViewById(R.id.menuFragment) != null;
mContentFragmentVisible = findViewById(R.id.contentFragment) != null;
mQuickAccessFragmentVisible = findViewById(R.id.quickAccessFragment) != null;
if(!savedInstanceState != null) {
if(!mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(true);
} else if(mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
}
return;
}
mManager = getSupportFragmentManager();
mManager.addOnBackStackChangedListener(this);
final FragmentTransaction ft = mManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if (!mMenuFragmentVisible && mContentFragmentVisible) {
/*
* Only the content fragment is visible, will enable sliding menu
*/
setupSlidingMenu(true);
onToggle();
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
} else if (mMenuFragmentVisible && mContentFragmentVisible) {
setupSlidingMenu(false);
/*
* Both menu and content fragments are visible
*/
ft.replace(R.id.menuFragment, getCustomApplication().getMenuFragment(), MenuFragment.TAG);
ft.replace(R.id.contentFragment, getCustomApplication().getSportsFragment(), SportsFragment.TAG);
}
if (mQuickAccessFragmentVisible) {
/*
* The quick access fragment is visible
*/
ft.replace(R.id.quickAccessFragment, getCustomApplication().getQuickAccessFragment());
}
ft.commit();
}
private void setupSlidingMenu(boolean enable) {
/*
* if enable is true, enable sliding menu, if false
* disable it
*/
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// launch the fragment that was clicked from the menu
}
#Override
public void onBackPressed() {
// Will let the user press the back button when
// the sliding menu is open to display the content.
if (mSlidingMenu != null && mSlidingMenu.isMenuShowing()) {
onShowContent();
} else {
super.onBackPressed();
}
}
#Override
public void onBackStackChanged() {
/*
* Change selected position when the back stack changes
*/
if(mSlidingMenuListView != null) {
mSlidingMenuListView.setItemChecked(getCustomApplication().getSelectedPosition(), true);
}
}
#Override
public void onToggle() {
if (mSlidingMenu != null) {
mSlidingMenu.toggle();
}
}
#Override
public void onShowContent() {
if (mSlidingMenu != null) {
mSlidingMenu.showContent();
}
}
}
The following is a stripped version of the CustomApplication. My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle.
public class CustomApplication extends Application {
private Fragment mSsportsFragment;
private Fragment mCarsFragment;
private Fragment mMusicFragment;
private Fragment mMoviesFragment;
public Fragment getSportsFragment() {
if(mSsportsFragment == null) {
mSsportsFragment = new SportsFragment();
}
return mSsportsFragment;
}
public Fragment getCarsFragment() {
if(mCarsFragment == null) {
mCarsFragment = new CarsFragment();
}
return mCarsFragment;
}
public Fragment getMusicFragment() {
if(mMusicFragment == null) {
mMusicFragment = new MusicFragment();
}
return mMusicFragment;
}
public Fragment getMoviesFragment() {
if(mMoviesFragment == null) {
mMoviesFragment = new MoviesFragment();
}
return mMoviesFragment;
}
}
I am very interested in tips on how to best implement multiple fragments and how to maintain their states. For your information, my applicaion consists of 15+ fragments so far.
I have done some research and it seems that FragmentManager.findFragmentByTag() is a good bet, but I haven't been able to successfully implement it.
My implementation seems to work good except for the fact that mActivity references become null after orientation changes, which lets me to believe that I may have some memory leak issues as well.
If you need to see more code, please let me know. I purposely avoided including fragment code as I strongly believe issues are related to my Activity and Application implementations, but I may be wrong.
Thanks for your time.
My thoughts behind this implementation was to guarantee only one instance of each fragment throughout my application's life cycle
This is probably part, if not all, of the source of your difficulty.
On a configuration change, Android will re-create your fragments by using the public zero-argument constructor to create a new instance. Hence, your global-scope fragments will not "guarantee only one instance of each fragment".
Please delete this custom Application class. Please allow the fragments to be re-created naturally, or if they need to live for the life of a single activity, use setRetainInstance(true). Do not attempt to reuse fragments across activities.
I don't see where are you using the reference to mActivity. But don't hold a reference to it. Always use getActivity since the Activity can be recreated after orientation change. Also, don't ever set the fragment's fields by setters or by assigning always use a Bundle and Arguments
Best practice for instantiating a new Android Fragment
Also you can use setRetainInstance(true) to keep all the fragment's members during orientation change.
Understanding Fragment's setRetainInstance(boolean)
To resolve this problem you have to use the activity object provided by onAttach method of fragment so when you change the orientation fragment is recreated so onAttach give you the current reference
you can use onAttach(Context context) to create a private context variable in fragment like this
#Override
public void onAttach(Context context) {
this.context = context;
super.onAttach(context);
}
on changing orientation, onAttach gives you new reference to the context, if you want reference to activity, you can typecast context to activity.
Context can also be reassigned inside onCreate in fragments as OnCreate is called when device is rotated
private Context mContext;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//get new activity reference here
mContext = getActivity();
}
pass this mContext throughout the fragment
If you don't setRetainInstance(true) in onCreate ... the collection e.g List<Object>, Vector<Object> in Application class will get null. Make sure you setRetainInstance(true) to make them alive.

Android Activity Tests - Testing Restarts

In both the Testing Fundamentals and the Activity Testing section entitled "Adding state management tests" in the Android developer documentation, it suggests testing activity restarts using:
mActivity.finish();
mActivity = this.getActivity();
Having tried this with the addition of a sleep between the two statements above, I can see that the Activity is not redrawn on the screen when the mActivity = this.getActivity() is executed. My test appears to work, but I am intrigued as to why the Activity isn't redrawn on the screen as this doesn't seem to be mentioned in the API documentation.
I'd be grateful for any insight into this anyone can give. At the point the finish() method is called, the Activity disappears from the screen, but doesn't reappear when the this.getActivity() is called. I've also tried putting an mActivity.setVisible(true) after the getActivity(), but that doesn't help.
My code snippet is now:
...
mActivity.finish();
Thread.sleep(5000);
mActivity = this.getActivity();
Thread.sleep(5000);
...
I've searched extensively, but can't find any explanation of why the Activity doesn't reappear when getActivity() is called.
I've tested this on Android 2.3.5, 2.3.3 and 2.2.2 all with the same result.
It seems that class ActivityInstrumentationTestCase2 needs an additional finish method in which some cleanup must be done. In meanwhile you can work around this problem by cleaning up yourself after finishing the activity. So change your code as follows:
mActivity.finish();
setActivity(null);
mActivity = this.getActivity();
This can be explained as follows. Method getActivity in class ActivityInstrumentationTestCase2 calls setActivity(a)
public T getActivity() {
Activity a = super.getActivity();
if (a == null) {
// set initial touch mode
getInstrumentation().setInTouchMode(mInitialTouchMode);
final String targetPackage =
getInstrumentation().getTargetContext().getPackageName();
// inject custom intent, if provided
if (mActivityIntent == null) {
a = launchActivity(targetPackage, mActivityClass, null);
} else {
a = launchActivityWithIntent(targetPackage,
mActivityClass,
mActivityIntent);
}
setActivity(a);
}
return (T) a;
}
Method setActivity sets internal variable mActivityIntent.
public void setActivityIntent(Intent i) {
mActivityIntent = i;
}
All calls after this first call will now use the new value mActivityIntent instead of a null-value. As a result
a = launchActivityWithIntent(targetPackage, mActivityClass, mActivityIntent);
will be called. Probably your app can not be started with this intent.
Note that method rearDown does a proper cleanup:
protected void tearDown() throws Exception {
// Finish the Activity off (unless was never launched anyway)
Activity a = super.getActivity();
if (a != null) {
a.finish();
setActivity(null);
}
}

Categories

Resources