24

I have a MainActivity and inside it, I am loading a fragment A. From FragmentA , I am calling google placepicker activity using startActivityforResult as follows.

PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
Intent intent = builder.build(getActivity());
getActivity().startActivityForResult(intent,PLACE_PICKER_REQUEST);

But when I select place, onActivityResult(either in FragmentA or MainActivity) is not getting called. In fact,my application is getting destroyed after startActivityForResult call.

As per my understanding, android should recreate the calling activity if it is not available in memory.But it is not happening.Even onCreate is not getting called inside MainActivity.

Could anyone tell me the reason behind this kind of behavior or am I missing anything?

Now instead of PlacePicker Activity, I have tried using another activity in same application.

Let's say I have MainActivity with FragmentA loaded.I am calling SubActivity with startActivityForResult from FragmentA.Now while returning from SubActivity ,the application exits. I have enabled Dont keep activities in my device to test this specific scenario. I can see MainActivity getting destroyed when I move to SubActivity.But on returning from SubActivity, android is not recreating MainActivity(even onCreate is not getting called.The application just exits).

Renjith
  • 3,274
  • 19
  • 39
  • *"my application is getting destroyed after startActivityForResult call."* do you mean it is crashing? – codeMagic Jun 15 '15 at 18:32
  • Have you provided an intent filter in your manifest file? This is needed that your activity can be recreated – Mann Jun 15 '15 at 18:34
  • @codeMagic It's not crashing.After calling startActivityForResult,I can see onDestory method in MainActivity getting called .And when I select a place from the launched activity, it returns.But my application is not recreating. if I select the place quick enough ( before my MainActivity) getting destroyed), onActivityResult is getting called correctly. – Renjith Jun 15 '15 at 18:50
  • Are you calling `finish()` on that activity somewhere? Maybe in `onActivityResult()` or somewhere else? – codeMagic Jun 15 '15 at 18:52
  • Not at all. I have checked all those possibilities . – Renjith Jun 15 '15 at 18:53
  • At this point of tough debugging, I would suggest looking at the Logcat error messages carefully for errors/exceptions. And...placing a try/catch block around the code may help too. – The Original Android Jun 15 '15 at 22:26
  • do Not use getActivity.startActivityForResult, use only startActivityForResult; are you using nested fragments? its only one level of fragments? what fragment manager are you using? – xanexpt Jun 16 '15 at 11:45
  • I tried startActivityForResult as well.There is only one level of fragment and I am using support fragment manager – Renjith Jun 16 '15 at 11:53
  • Without calling finish manually MainActivity not be destroyed. – Haresh Chhelana Jun 18 '15 at 06:54
  • 1
    I have the same scenario in one of my apps and with "Dont keep activities" enabled my Activity is destroyed and recreated once the started Activity returns. That's the expected behavior so there's something out of the ordinary with you app or device. Without more code (including manifest) and specifics about your device/os there's imo no way this question can be answered. – Emanuel Moecklin Jun 18 '15 at 14:01

2 Answers2

19

This can happen due to many reasons, but hopefully it's a rare occurrence. The OS will destroy the Activity when backgrounded if it needs to reclaim resources, which is more likely to happen on devices with less memory and processing power.

Using the Do not keep Activities setting is a good way to test this scenario, and in this case there are other problems even if the Activity/Fragment do get re-created. With this setting enabled, the Activity and Fragment do get destroyed when the PlacePicker is shown, and then when onActivityResult() comes in, there is no valid Context because the Activity and Fragment are still in the process of being re-created.

I found this out by performing a controlled test with the setting disabled, and then with the setting enabled, and then looking at the results

I put logging in each lifecycle callback for the Activity and the Fragment in order to get an idea of what is going on during these calls.

Here is the full class I used that includes both the Activity and the Fragment:

public class MainActivity extends AppCompatActivity {

    MyFragment myFrag;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d("PlacePickerTest", "Activity onCreate");

        myFrag = new MyFragment();

        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, myFrag)
                    .commit();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        Log.d("PlacePickerTest", "Activity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d("PlacePickerTest", "Activity onPause");
    }

    @Override
    protected void onDestroy() {
        Log.d("PlacePickerTest", "Activity onDestroy");
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public void onActivityResult (int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d("PlacePickerTest", "Activity onActivityResult requestCode:" + requestCode);

        if (requestCode == 199){
            //process result of PlacePicker in the Fragment
            myFrag.processActivityResult(data);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            //open PlacePicker from menu item
            myFrag.startPlacePicker();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Fragment containing a map and PlacePicker functionality
     */
    public static class MyFragment extends Fragment {

        private GoogleMap mMap;
        Marker marker;

        public MyFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            Log.d("PlacePickerTest", "Fragment onCreateView");

            return rootView;
        }


        @Override
        public void onResume() {
            super.onResume();

            Log.d("PlacePickerTest", "Fragment onResume");

            setUpMapIfNeeded();
        }

        @Override
          public void onPause() {
            super.onPause();

            Log.d("PlacePickerTest", "Fragment onPause");
        }

        @Override
        public void onDestroy() {
            Log.d("PlacePickerTest", "Fragment onDestroy");
            super.onDestroy();
        }

        private void setUpMapIfNeeded() {
            // Do a null check to confirm that we have not already instantiated the map.
            if (mMap == null) {
                // Try to obtain the map from the SupportMapFragment.
                mMap = ((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map))
                        .getMap();
                // Check if we were successful in obtaining the map.
                if (mMap != null) {
                    setUpMap();
                }
            }
        }

        private void setUpMap() {

            // Enable MyLocation Layer of Google Map
            mMap.setMyLocationEnabled(true);
            mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
            mMap.getUiSettings().setZoomControlsEnabled(true);
            mMap.getUiSettings().setMyLocationButtonEnabled(true);
            mMap.getUiSettings().setCompassEnabled(true);
            mMap.getUiSettings().setRotateGesturesEnabled(true);
            mMap.getUiSettings().setZoomGesturesEnabled(true);

        }

        public void startPlacePicker(){
            int PLACE_PICKER_REQUEST = 199;
            PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
            //Context context = getActivity();
            try {
                Log.d("PlacePickerTest", "Fragment startActivityForResult");
                getActivity().startActivityForResult(builder.build(getActivity()), PLACE_PICKER_REQUEST);
            } catch (GooglePlayServicesRepairableException e) {
                e.printStackTrace();
            } catch (GooglePlayServicesNotAvailableException e) {
                e.printStackTrace();
            }
        }

        public void processActivityResult ( Intent data) {

            if (getActivity() == null) return;

            Log.d("PlacePickerTest", "Fragment processActivityResult");


            //process Intent......
            Place place = PlacePicker.getPlace(data, getActivity());
            String placeName = String.format("Place: %s", place.getName());
            String placeAddress =  String.format("Address: %s", place.getAddress());

            LatLng toLatLng = place.getLatLng();

            // Show the place location in Google Map
            mMap.moveCamera(CameraUpdateFactory.newLatLng(toLatLng));
            mMap.animateCamera(CameraUpdateFactory.zoomTo(15));

            if (marker != null) {
                marker.remove();
            }
            marker = mMap.addMarker(new MarkerOptions().position(toLatLng)
                    .title(placeName).snippet(placeAddress)
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_MAGENTA)));

        }
    }
}

Here are the resulting logs that give insight into what lifecycle callbacks are called during the process under normal circumstances:

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Fragment processActivityResult
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

So, as you can see onDestroy() is never called, and onPause() and onResume() are called on both the Activity and the Fragment.

Here is the result visually:

PlacePicker

Then after picking a place:

place shown on map

Then, I enabled Do not keep Activities under Developer Options in Settings, and ran the same test.

These are the resulting logs:

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onDestroy
 D/PlacePickerTest﹕ Fragment onDestroy
 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

So, you can see both the Activity and Fragment are destroyed when the PlacePicker is shown, and after the place is picked in the PlacePicker, the code execution never got to the Fragment processActivityResult log entry, and the app never showed the picked place on the map.

That is because of the null Context check:

 if (getActivity() == null) return;

 Log.d("PlacePickerTest", "Fragment processActivityResult");

 //process Intent......
 Place place = PlacePicker.getPlace(data, getActivity());

So, the call to onActivityResult() does come in, but it does so at the same time that the Activity and Fragment are getting re-created, and you need a valid Context to make the call to PlacePicker.getPlace(data, getActivity());.

The good news is that most end-users will not have the Do not keep Activities setting enabled, and most of the time your Activity won't be destroyed by the OS.

Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
  • This can happen if the OS deletes the activity calling startActivityForResult. –  Mar 12 '16 at 06:44
  • @JawadLeWywadi You're right, reading this again I realized this answer had some issues. Just edited, I think it's better now. Thanks! – Daniel Nugent Mar 12 '16 at 07:14
  • 1
    this should be the correct answer, especially for the hint to the settings "Do not keep Activities alive".... – Opiatefuchs Jul 27 '16 at 12:20
7

It seems quite unusual for Android to clean up an activity in the way you described, but if that was the case then your activity should still be restored. Android should not destroy the activity, unless you specifically call finish() or something causes the activity to end prematurely.

If you refer to the activity lifecycle diagram:

In the scenario you described the first activity should call onStop, but not onDestroy, then when you return from the second activity it should call onStart again.

I created a very simple app to test the scenario you described, which contained the following:

  • There are 2 activities, FirstActivity and SecondActivity
  • FirstActivity has a button, when the button is clicked it starts SecondActivity with startActivityForResult()
  • Activity lifecycle events are logged using ActivityLifecycleCallbacks in a custom application class
  • In FirstActivity onActivityResult additionally outputs to the log when it gets called

Here is what is output:

Application is started (FirstActivity is created and started and visible):

FirstActivity onCreate
FirstActivity onStart
FirstActivity onResume

I press the button to start SecondActivity:

FirstActivity onPause
SecondActivity onCreate
SecondActivity onStart
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop

Note, onDestroy does not get called.

Now I press the back button and return to the first activity:

SecondActivity onPause
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
SecondActivity onStop
SecondActivity onDestroy

The back button calls finish on SecondActivity so it's destroyed

Now if I press back again FirstActivity will also be finished, causing onDestroy to be called.

FirstActivity onPause
FirstActivity onStop
FirstActivity onDestroy

You can see that this example has adhered to the lifecycle diagram exactly. The activities are only destroyed once the back button is pressed, which causes the activity to call finish().

You mentioned that you tried turning on "Don't keep activities" in the developer options, we can repeat the above experiment which this option enabled and see what happens. I have just added the relevant lifecycle events to save repeating everything that is above:

After pressing the button in the first activity to start the second activity:

...
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
FirstActivity onDestroy

As expected, the activity has been destroyed this time. This is what happens when you navigate back to the first activity again:

SecondActivity onPause
FirstActivity onCreate
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
...

This time onCreate was called again as the system didn't have a stopped version of the first activity to restart. Also onActivityResult() was still called, regardless of the fact that the activity had to be recreated.

This further supports that something in your first activity must be calling finish() or causing it to crash. However, without seeing your actual code this is conjecture.

Finally, to maintain state if your activity does for some reason need to get recreated, you can override onSaveInstanceState() and add any state information to the bundle:

protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString(MY_STRING_KEY, "my string value");
}

When the activity is recreated, you'll get a bundle back in onCreate which should contain everything you saved:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    if (savedInstanceState != null) {
        // Restore previous state
    }
}
Community
  • 1
  • 1
Philio
  • 3,675
  • 1
  • 23
  • 33
  • I am exactly testing the same case of recreating the activity(see my edit).The problem is even onCreate method is not getting called while returning. – Renjith Jun 16 '15 at 11:49
  • 2
    Why is this the accepted answer? It doesn't answer the question imo. – Emanuel Moecklin Jun 23 '15 at 13:52
  • This is not correct -- the OS can reclaim an activity at any time if it is not in a visible state. I suggest ALWAYS running with DONT KEEP ACTIVITIES turned on to uncover these sorts of issues. The whole lifecycle of Activities was done for low-memory situations. File pickers, for example, can cause big memory squeezes and cause these sorts of issues. – DustinB May 25 '23 at 01:58