Photo by Hal Gatewood on Unsplash

The Heisenbug that had us baffled for days: The strange tale of tasks and launch intents

Pixplicity
Pixplicity
Published in
3 min readJun 4, 2018

--

A few weeks ago, an exceptional QA tester raised this issue:

The app forgets its state when started from the app drawer.

This had the dev team baffled for days, because it was a software bug that seemed to disappear or alter its behavior when one attempted to study it. Was it a crash? Is the launch mode incorrect? Surely it’s not a platform bug?

Once upon a time…

When Android was in its infancy, a strange phenomenon with activities was observed when launching an app from the IDE:

Activity stack behaves incorrectly during the first run of an app
issue report from 2009

A few years pass, and where this issue is first dismissed, a new one pops up:

Activity stack confused when launching app from browser
issue report from 2014

Again the issue is left for obsolete, but user reports continue to come in left and right that there really is a problem with application states when they’re launched in different ways.

The way launch intents should work

This is how a simple app with main and detail activities should be resumed from the launcher.

The way launch intents falter

Unfortunately, this is the reality of how tasks are resumed from the launcher: a new instance of the launch activity is created.

Now, I know what you’re thinking: just set launchMode to singleTop. Here’s the kicker: it doesn’t matter, because upon each launch, MainActivity will appear as another new instance on the back stack. And things get even messier with singleInstance, where all other activities will be mercilessly finished.

Upon each launch, MainActivity will appear as another new instance on the back stack.

Why should I care?

Many users will launch your app from Google Play after installing it. As long as the app is still running a task (i.e. the user presses the home button), this issue will result in your user being brought to a new instance of the launcher activity, regardless of where they left off.

Imagine user Alice installing the fictional SuperMsg messaging app:

  1. SuperMsg is opened through Google Play
  2. Alice composes a new chat message to Bob
  3. She switches out out of app to check something
  4. Alice locates the app on her home screen to resume typing her message
  5. SuperMsg launches to its start screen instead of the composed message

At this point, Alice will perceive that her draft was lost.
And since this was her first experience in using the app, she will no doubt be very frustrated and inclined to uninstall it.

What can I do?

The good news is that this broken behavior can be detected and mitigated. The gist of it is this:

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Check that the activity was launched correctly
if (!isTaskRoot
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& intent.action == Intent.ACTION_MAIN) {
// This activity doesn't belong
finish()
return
}
}

More details

We’ve set up a repository to illustrate this problem. Check out Poor Intentions on GitHub or Google Play.

--

--