Search code examples
androidkotlinandroid-intentandroid-activity

I want my library to start an activity without the main code explictly calling it


I'm working on a library that has a couple of ready-made activities.

So far i have my activities in the library, and in the main app, i call it normally with registerForActivityResult to start it.

this means whoever is using my library would be able to see the whole activity.

what i would like to do, is to have the developer call a method in the library class and ask it to do an action, and in the library that method would on its own start the activity, register it for result, and return the result to the calling class through an interface.

the below is what i tried but it gives me error LifecycleOwner is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED

private fun launchScannerActivity(activity: FragmentActivity, callback: ScannerCallback) {
        val scanResult =
            activity.registerForActivityResult(
                ActivityResultContracts.StartActivityForResult()
            ) {
                if (it.resultCode == Activity.RESULT_OK) {
                    callback.onResult(it.data?.getStringExtra("Some Key") ?: "")
                } else {
                    callback.onFail()
                }
            }

        val intent = Intent(activity, ScannerActivity::class.java)
        scanResult.launch(intent);
    }

why do i need this: This library would be an SDK for a SAAS product, so we would like to abstract and obfuscate as much of the implementation as possible from our clients.


Solution

  • You can't really communicate between Activities using interfaces, at least not in a way that is somewhat concise and isn't very prone to leaking. What you can do is expose your own Activity result contract. Then your API could be as simple as some of the ones in ActivityResultContracts. You can look at the source code there to see how to implement it.

    Maybe something like this:

    class ScannerResultContract : ActivityResultContract<Unit, String?>() {
        override fun createIntent(context: Context, input: Unit?): Intent {
            return Intent(context, ScannerActivity::class.java)
        }
    
        override fun parseResult(resultCode: Int, intent: Intent?): String? {
            return if (resultCode == Activity.RESULT_OK) {
                intent?.getStringExtra("Some Key")
            } else {
                null
            }
        }
    }
    

    Client usage:

    // In activity or fragment:
    val getScannerResult = registerForActivityResult(ScannerResultContract()) { resultString ->
        if (resultString != null) {
            // use it
        } else {
            // log no result returned
        }
    }
    
    //elsewhere:
    someListener.setOnClickListener {
        getScannerResult.launch()
    }