Android Activity Tests - Testing Restarts - android

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);
}
}

Related

Android Activity lifecycle problems

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.

Android - Fragment is null after Picture Intent (REQUEST_PICTURE_CAPTURE)

I have an app which has a PagerAdapter with a few Fragments.
From the main Activity, I start a REQUEST_PICTURE_CAPTURE intent, it works ok, when it ends it calls onActivityResult().
In onActivityResult(), I have simple code:
m_PagerAdapter.GetTheFrag().DoStuff(..., ...);
Where m_PagerAdapter = derived from a PagerAdapter,
GetTheFrag() = // returns the derived Fragment object, which wasn't null before starting the Intent. This is the problem,
DoStuff() ==> never gets called, because m_PagerAdapter.GetTheFrag() is null .
The weird thing is that sometimes it does work and sometimes not, doesn't matter what picture quality or any capture related properties.
class MyPagerAdapter extends PagerAdapter {
// In PagerAdapter
public MyFrag GetTheFrag() {
return m_MyFrag;
}
// In PagerAdapter
private View getViewByID(Integer integerID) {
switch (integerID) {
case R.layout.my_frag:
if (m_MyFrag == null) {
m_MyFrag = new MyFrag(m_MainActivity);
}
return m_MyFrag.GetMyFragView();
}
return null;
}
}
// In MyFrag
public View GetMyFragView() {
return m_ThisView;
}
Most likely, your process was terminated while the camera app was in the foreground. A fresh process was created for you as part of returning control to your app after the camera finishes. A fresh activity will have been created, along with fresh fragments. However, whatever m_MyFrag is, it is not being filled in by your code when this occurs, and so it is null.
You will be encountering the same sort of problem if the user leaves your app (e.g., via HOME), Android terminates your process, but then the user returns to your app within 30 minutes or so of having left. So, while you happen to be experiencing it as part of launching a third-party camera app, the same problem will occur elsewhere.

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.

How to test activity kill -> recreate lifecycle with saveInstanceState

I try to write an instrumentation test that tests the activity lifecycle where my activity gets killed and it's instance state gets saved and then gets recreated with this instance state.
I can test this behavior manually by limiting the background process limit to "no background processes" in the developer settings of my device, but I want to be able to have an automated test that proves that my activity can be recreated.
My activity has a fragment with id R.id.content_frame.
So for I have this:
public class MainActivityInstrumentationTest extends ActivityInstrumentationTestCase2<MainActivity> {
public void testKillCreateLifeCycleWithProfileFragment() throws Throwable {
final Activity activity = getActivity();
navigateToProfile(activity);
Thread.sleep(5000);
runTestOnUiThread(new Runnable() {
#Override
public void run() {
activity.recreate();
}
});
getInstrumentation().waitForIdleSync();
Thread.sleep(5000);
assertProfileFragmentIsVisible((FragmentActivity) activity);
}
private void assertProfileFragmentIsVisible(FragmentActivity activity) {
FragmentManager supportFragmentManager = activity.getSupportFragmentManager();
Fragment currentFragment = supportFragmentManager.findFragmentById(R.id.content_frame);
assertEquals(ProfileFragment.class.getName(), currentFragment.getClass().getName());
}
}
activity.recreate goes through all the live cycle callback methods and ultimalty calls onCreate with the saved bundle but the fragmentManager in my assertProfileFragmentIsVisible method does not contain any fragments.
Also I'm not sure whether to use activity.recreate is a right way to go. I tried many other ways like calling each life cycle method manually with getInstrumentation().callActivityOn...but then ultimately found no way of creating the activity with the saved bundle..
Any ideas on how I can create such an instrumentation test would be appreciated!
Regards
Frank
Just in case anybody is interested in my final solution:
The problem was that I put the reference to the old activity to assertProfileFragmentIsVisible. But activity.recreate() creates a new activity instance.
The problem remains how to get this reference.
I managed to obtain a reference to the new activity by using the ActivityMonitor.
So my complete test now looks as follows:
public void testKillCreateLifeCycle() throws Throwable {
Instrumentation.ActivityMonitor mainActivityMonitor = new Instrumentation.ActivityMonitor(MainActivity.class.getName(), null, false);
getInstrumentation().addMonitor(mainActivityMonitor);
final Activity activity = getActivity();
mainActivityMonitor.waitForActivityWithTimeout(5000);
navigateToFragment(activity);
runTestOnUiThread(new Runnable() {
#Override
public void run() {
activity.recreate();
}
});
getInstrumentation().waitForIdleSync();
Activity newActivity = mainActivityMonitor.getLastActivity();
assertFragmentIsVisible((FragmentActivity) newActivity, getExpectedFragment());
}

Finish Activity() from a separate myJavaClass.java

I have tried almost all the solutions from SO but no success :(.
I have a simple myJavaClass.java with a couple of functions.
One of the functions in myJavaClass : startActivity() starts MyCustomActivity
public startActivity(Context context)
{
Intent intent = new Intent(context, MyCustomActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
This launches MyCustomActivity() as expected.
Now I have another function in myJavaClass.java to close/finish MyCustomActivity but it is not able to do so!
I have tried
Making MyCustomActivity SingleTop in manifest and creating the activity via an intent as above
Passing an activity instance to "this" in onCreate() of MyCustomActivity and calling MyCustomActivity.activity.finish() from myJava.class but that doesnt work as well
Please help me. I have been stuck here for hours now. I know the solution is very simple and conceptual but I am a newbie. Just building Java/Android concepts!
EDIT
MyCustomActivity
public Activity activity;
OnCreate()
{
...
this = activity;
}
MyJavaClass
public closeActivity(Context context)
{
Activity customActivity = MyCustomActivity.activity;
customActivity.finish();
}
I think that what you are trying to do is fundamentally bad. For a start, outside of the Activity code, there are no guarantees that the activity still exists - the memory manager may have cleaned it up, the user may have pressed Back etc. Think of Activities as independent entities - you can start them, and you can optionally get a result back when they finish what they're doing, but that's it.
Think about whether you really have to programmatically close the activity from outside it - I'd say this is an unusual design, but there are circumstances where it may be appropriate.
If so, what I think you want is a publish/subscribe system whereby MyCustomActivity can register a listener with MyJavaClass, and then receive a callback whereupon it can 'finish' itself.
public Activity activity implements FinishListener
{
public void onCreate(...)
{
//where does MyJavaClass come from? see in a minute
MyJavaClass myjava = getMyJavaclass();
myJava.addFinishListener( this );
}
public void onFinishCallback()
{
this.finish();
}
}
and
public class MyJavaClass
{
private List<FinishListener> finishListeners = ...;
public void addFinishListener( FinishListener fl )
{
this.finishListeners.add(fl);
}
public closeActivity(Context context)
{
for ( FinishListener fl : finishListeners )
{
fl.onFinishCallback();
}
}
}
and
public interface FinishListener
{
void onFinishCallback();
}
Now the only remaining issue is how to get MyJavaClass from the Activity. That's up to you - you may already know how, you may be able to put it in your Application implementation, it could be a singleton (bad), the listeners could be static (bad) or various other options.
Oh, and don't forget to remove the listener again in the Activity's onDestroy() method!
Just try this....
public closeActivity(Activity _activity)
{
_activity.finish();
}
you can't finish activity from other class until you have the reference of instance of Activity in that class, give the reference in that class and call finish() method to stop the activity.
activity.finish();

Categories

Resources