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

> Source: <https://dev.to/ashwar_sadh_f7abe6f82c8e9/a-share-target-activity-that-must-not-run-twice-why-it-finishes-itself-when-android-restores-it-1g88>
> Published: 2026-11-04 06:18:03+00:00

*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](https://play.google.com/store/apps/details?id=com.ashwar.autoupiqr&referrer=utm_source%3Ddevto%26utm_medium%3Darticle) 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.

``` php
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](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.
