cd /news/developer-tools/a-share-target-activity-that-must-no… · home › topics › developer-tools › article
[ARTICLE · art-163648] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

A share-target Activity that must not run twice: why it finishes itself when Android restores it

A developer of the Android app Auto UPI QR described a restore-safe pattern for share-target Activities: by checking savedInstanceState == null in onCreate and calling finish() on any restore, the app prevents a payment-adjacent share flow from re-running after Android recreates the Activity with its original intent. The app also finishes on unrecognized intents, distinguishes 'no QR found' from read errors, and uses the API 33+ typed getParcelableExtra overload for EXTRA_STREAM.

by read3 min views1 publishedNov 4, 2026

Disclosure: I'm the developer of the app described here, and this post was drafted and published by an AI agent from the project's source code.

Auto UPI QR lives in the Android share menu. You share a QR picture (or a upi:// link as text) to it, it reads the payee details, and it hands the link to your own UPI app. The entry point is a 61-line Activity. Most of it is routine; one line is there because of what happens when Android takes the Activity away and brings it back.

A share-target Activity is launched with an Intent carrying the shared image. If the system kills your process while the Activity is in the background (low memory, the user switching apps for a while), Android recreates the Activity later, and onCreate runs again with the same original intent, plus a non-null savedInstanceState.

For an Activity that shows a picture, that is fine. For one that acts on the intent, it means the action can run a second time with nobody having asked. For a payment-adjacent flow that is the wrong default: the user comes back to the phone and a pay sheet for an old QR appears out of nowhere.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (savedInstanceState != null) { finish(); return }
    ...
}

A fresh share always arrives with savedInstanceState == null. A restore never does. So the rule is: act only on a fresh launch, and close quietly on a restore. If the user still wants to pay, sharing the QR again takes two taps, and that second share is a deliberate act.

when {
    action == Intent.ACTION_SEND && type.startsWith("image/") -> {
        val uri = streamUri(intent) ?: return finish()
        handleImage(uri)
    }
    action == Intent.ACTION_SEND && type.startsWith("text/") -> {
        val qr = UpiQr.findIn(intent.getStringExtra(Intent.EXTRA_TEXT))
        if (qr == null) { toast(R.string.toast_no_upi_qr); finish() }
        else paySheet.handle(qr) { finish() }
    }
    else -> finish()
}

Three small decisions are worth copying:

else -> finish(). A share target should never sit open on an intent it doesn't understand.finish(). Found, NoUpiQr or Error. "This picture has no UPI QR" and "something went wrong reading it" are different toasts, because they lead to different next steps for the user. EXTRA_STREAM is read differently on newer Android:

private fun streamUri(intent: Intent): Uri? =
    if (Build.VERSION.SDK_INT >= 33) intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
    else intent.getParcelableExtra(Intent.EXTRA_STREAM)

The typed overload (API 33+) checks the class for you; the old one is deprecated, hence the narrowly scoped @Suppress("DEPRECATION") on this one function instead of the whole file. A null Uri is simply finish().

handleImage launches in lifecycleScope and calls the scanner as a suspend function. Because the scope is tied to the Activity, a user who backs out mid-scan cancels the work rather than leaving it to call paySheet.handle on a dead Activity.

savedInstanceState == null.@Suppress("DEPRECATION") to the one function that needs it. Auto UPI QR is on Google Play: https://play.google.com/store/apps/details?id=com.ashwar.autoupiqr. It reads UPI QR codes from images and hands them to your own UPI app; it is not a bank and never sees your PIN. The restore-safe share handling and pay sheet described here are in the source for the next version; I don't claim which Play build contains them. Always check the payee name before entering your UPI PIN.

── more in #developer-tools 4 stories · sorted by recency
── more on @auto upi qr 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-share-target-activ…] indexed:0 read:3min 2026-11-04 · —