Android Activity Result API with a ViewModel: how to reference ActivityResultRegistry

176 Views Asked by At

I am using Jetpack Compose and Dagger-Hilt in making an app with Bluetooth functionality, one of my objectives is to be able to make the device discoverable from inside the app.

I am seriously surprised of the fact that there is no mention of this problem on SO or elsewhere, even though this problem seems to be easily encounterable: to launch a system activity, like picking a photo, opening settings or prompting a user to enable Bluetooth discoverability, ActivityResultLauncher should be registered in the ActivityResultRegistry and then launched.

Since I have a single ComponentActivity, no Fragments, am using Jetpack Compose, and certainly separating UI from any logic, it only makes sense to call such logic from a ViewModel, that is why I am surprised I haven't managed to find info about it anywhere. Not talking about the fact that even with Fragments a ViewModel should be used.

In order to achieve this I've tested the code below in a separate app with success (upon a button press and a call to becomeDiscoverable() a native Android dialog pops up requesting the discovery, at least on Android 8, on Android 10 there is no dialog, but it still works), but I am still unsure of stability of such code:

class MainViewModel @AssistedInject constructor(
    private val context: Application,
    @Assisted private val registry: ActivityResultRegistry,
) : ViewModel() {

var enableDiscoverableLauncher: ActivityResultLauncher<Intent>? = null
        private set

fun becomeDiscoverable(duration: Int = 20) {
            enableDiscoverableLauncher = registry.register(
                "DISCOVERABLE_ENABLER", ActivityResultContracts.StartActivityForResult()
            ) { }
            val requestCode = 1;
            val discoverableIntent: Intent =
                Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE).apply {
                    putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, duration)
                }
            enableDiscoverableLauncher?.launch(discoverableIntent, ActivityOptionsCompat.makeBasic())
        }

    //@AssistedFactory is below...
}

The thing is, since ViewModel outlives Activity, I am assuming during a configuration change the main ComponentActivity, which is the ActivityResultRegistryOwner , would recreate, and if during that window the becomeDiscoverable() is called, the app would crash. I wasn't able to recreate this behavior, but I assume it to be the case.

After the activity's recreation the reference to activityResultRegistry in the viewModel still holds and the becomeDiscoverable() still can be successfully called.

According to the accepted answer to the question "How to get ActivityResultRegistry in Jetpack Compose?" and my assumptions, I could obtain and remember LocalActivityResultRegistryOwner.current!!.activityResultRegistry at a certain point in composition to then use Dagger-Hilt's Assisted injection to obtain a viewmodel that would hold a reference to the activityResultRegistry.

But the last part of the answer really confused me:

I am assuming that you are using Compose inside a ComponentActivity anyway, so you will never receive a null reference when accessing ActivityResultRegistryOwner.current, so if you do not want to deal with a nullable type, you can use the !! operator.

How can it be the case? Why is it related specifically to ComponentActivity? And there is certainly no mention of a ViewModel in the linked question/answer, how is the OP not using vm at all?

And most importantly,

what is the correct way of implementing in Jetpack Compose interactions with such references of objects that are originally created by Android and belong to the main Activity?

0

There are 0 best solutions below