5

I have three activities, let's call them ONE, TWO and THREE. From activity ONE, a button press starts activity TWO. From activity TWO, a button press starts activity THREE.

Simple enough.

Now, activity THREE requires a bit of data accessible from the application (which may or may not be there). In THREE's onResume() method, a check is made for the data and the activity is finished if it doesn't exist, like so:

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

    /* ... get data from app ... */

    if (data == null) {
        Toast.makeText(this, "Data not found", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }

    /* ... other logic ... */
}

When data == null, THREE finishes, destroys and returns to TWO. All is well. Now in TWO, pressing the back button calls finish() on TWO, but TWO never calls onDestroy(). The user is returned to ONE just fine, but any subsequent intent to go back to TWO does not work and no errors are thrown. TWO is left in a state where it has been finished (and paused), but never destroyed, and as a result cannot resume.

So, why is THREE important in this case? If I remove the finish() call in the code block above, and rely on a "natural" finishing of THREE (by using the back button), when the user gets back to ONE, TWO has been destroyed correctly.

OK here's where it gets really confusing...

Leaving the finish() call in place, I can alleviate the hangup by launching THREE directly from ONE, then "naturally" finish it (back button). After THREE is destroyed (a second time), TWO opens as expected.

Everything I've read has said I should be ok calling finish() within onResume() for an activity. But in this case, it is leaving something in a bad state and prevents me from destroying the calling activity down the line.

Ideas? or did I turn your brains inside out?

EDIT:

Further exploration uncovered this gem...

Surrounding the finish() call in THREE with a postDelay() handler of around 500 millis will allow TWO to close as expected. Like this:

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

    /* ... get data from app ... */

    if (data == null) {
        Toast.makeText(this, "Data not found", Toast.LENGTH_SHORT).show();
        Handler h = new Handler();
        h.postDelayed(new Runnable() {
            @Override
            public void run() {
                finish();
            }
        }, 500);
        return;
    }

    /* ... other logic ... */
}

Not exactly my idea of a fix...

artex
  • 1,776
  • 2
  • 11
  • 16
nak5ive
  • 1,963
  • 4
  • 15
  • 21
  • What is this data? From the sound of things this data should exist (not be null) when THREE enters onCreate(). Is the outlying issue the fact that onResume() data is null? Or does data become out of date or invalidated or not useful? – Graham Smith Feb 03 '12 at 20:29
  • Something seems seriously wrong with your architecture if you are resorting to multithreaded waits... I have a possible answer but until you answer what "data" is and why it is null on resume I shall postpone answering as it may/may not not be the answer. – Graham Smith Feb 03 '12 at 21:06
  • @GrahamSmith If I can be honest, you are correct, data should not be null. But in testing a fringe case where it could be null, we stumbled across this problem. If we can't resolve it soon, we will ignore the fringe case. :) Data is populated based on an "id" that comes in on the intent. That "id" is then handed to a public method in the application to retrieve an object (data) which looks at data buffers or local database. – nak5ive Feb 03 '12 at 21:08
  • @GrahamSmith Let me clarify, that a multithreaded approach is NOT what I plan on doing. Just interesting that it somehow resolved the issue. – nak5ive Feb 03 '12 at 21:09
  • ok, so straight question - is data null only on onResume() or can it be null from the onCreate() of THREE? – Graham Smith Feb 03 '12 at 21:09
  • @GrahamSmith onResume(). THREE is called by the intent flag bring to front. ensuring there is only one instance of that activity, recycled for different "id"s. – nak5ive Feb 03 '12 at 21:15

3 Answers3

2

an activity is not finished / destroyed on back pressed.

Use

 @Override
    public void onBackPressed()
    {
        finish();
    }
Subhalaxmi
  • 5,687
  • 3
  • 26
  • 42
PC.
  • 6,870
  • 5
  • 36
  • 71
  • 1
    from what i can tell, a call to super.onBackPressed() will finish my activities. – nak5ive Feb 03 '12 at 20:56
  • 2
    android documentation clearly mentions that `onBackPressed()` calls only `onPause()` and it depends on the JVM state if it wants to call `onStop()` or `onDestroy()`. – PC. Feb 03 '12 at 21:20
  • i will also mention that i am calling finish from within onBackPressed(). Sorry this is a very large app. more than one developer, as well. – nak5ive Feb 03 '12 at 21:33
1

Since I can not comment I will write here.

I am not 100% sure if I followed you but right near the end you mention that

After THREE is destroyed (a second time), TWO opens as expected.

What do you mean by that since if I followed you correctly you said that you open TWO with a button in ONE and THREE with a button in TWO. So how can TWO open as expected, or you mean then it goes to onDestroy() when you exit it?

What I am aiming at is that perhaps you open more instances of the same activity, as is mentioned in the here if you take a look at Figure 3.

Dusko
  • 628
  • 2
  • 6
  • 24
  • basically, while in ONE, a user has buttons to go to TWO or THREE. After the hangup occurs and a user is back on ONE, a direct launch from ONE to THREE, followed by a finish of THREE (by way of back button) frees up whatever is preventing TWO from opening. – nak5ive Feb 03 '12 at 20:51
  • why does this "hangup" occur? – Graham Smith Feb 03 '12 at 20:59
  • @GrahamSmith that's the million dollar question. – nak5ive Feb 03 '12 at 21:16
0

Since this issue occurs only from onResume() [see comments] I would argue it sounds like an issue with state persistence rather than tinkering with the stack.

Take a look at this Stack Over Flow Question Saving Android Activity state using Save Instance State

If onPause() you save "data" (I do not know its object type as you have not said) into a Bundle then onResume() go get it.

Another example found here by Google, uses SharedPreferences instead of a Bundle to achieve the same result.

Overall this will mean you can handle/prevent "data" being null as you can restore it therefore saving the effort of destroying THREE and trying to fiddle with the stack, simply put in your terms keeping the workflow "natural".

Community
  • 1
  • 1
Graham Smith
  • 25,627
  • 10
  • 46
  • 69