1

The following code gives me error as registering occurs after onResume:

class TempActivity: AppCompatActivity(){
      private lateinit var binding: ActivityTempBinding
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTempBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.tempBtn.setOnClickListener {
            val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                //SomeCode
            }
            a.launch(
                //SomeIntent
            )
        }

    }

However, if I use activityResultRegistry, I am not getting any errors. The code is

class TempActivity: AppCompatActivity(){
      private lateinit var binding: ActivityTempBinding
      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTempBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.tempBtn.setOnClickListener {
            val a = activityResultRegistry.register("key", ActivityResultContracts.StartActivityForResult()){
            // SomeCode
            }
            a.launch(
               //Some Intent
            )
        }

    }

The latter code run without any problem and launches the corresponding intent. I just want to know how safe is latter one and is there any unwanted behaviors I should be aware of?

Diken Mhrz
  • 327
  • 2
  • 14

1 Answers1

0

It gives you an error because you are registering the contract conditionally after the Activity is well into its lifecycle.

The guide says:

You must always call registerForActivityResult() in the same order for each creation of your fragment or activity to ensure that the inflight results are delivered to the correct callback.

It's clear that if you register something after the Activity is created and it only happens when a condition (click event in this case) is met, the order of registration cannot be ensured.

A better solution would be to register the contract before the Activity is created and just call launch() when you need it. The guide, once again, says it is completely safe:

registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.

So in your case, the Activity would look like this:

class TempActivity: AppCompatActivity() {
    private lateinit var binding: ActivityTempBinding
    
    // registering the contract here
    private val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        //SomeCode
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTempBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.tempBtn.setOnClickListener {
            // launching the registered contract
            a.launch(
                //SomeIntent
            )
        }
    }
}

Further explanation:

The registerForActivityResult() is a convenience method that internally calls the registry's register method with an automatically created key. The key is derived from an internal AtomicInteger that is retrieved and incremented every time you call registerForActivityResult(). Since this key is used to look up the callback that will handle the result, every call to the registerForActivityResult must be in the same order, otherwise it might happen that you once call it in the order of A (key=0), B (key=1) but then you call it B (key=0), A (key=1), or not even call the register method for one of the contracts (this is exactly what happens when you register in the OnClickListener).

In your specific case if the Activity gets recreated while you're waiting for the launched contract to return (for example, configuration change happens or the system simply kills the app), the callback will be removed from the registry (the key remains there though), meaning that it will not be called with the results.

So, to summarize the whole thing: you can (should) safely register any contract as a member field in your Activity or in the onCreate(...), and you should never register a contract on-the-fly (a.k.a. conditionally). Registering the contract will do nothing special, the real deal happens when you launch it.

Gergely Kőrössy
  • 5,620
  • 3
  • 28
  • 44
  • I know the cause of the error and your mentioned way of doing it. I was asking whats wrong with the code using activityResultRegistry as it enables us to register the contract only after the onClick. Since, activityResultRegistry is allowing me to register contracts on resumed state of the activity, I am assuming that I am missing something. I am wondering is there any problem with using activityResultRegistry as I have done in the latter part. – Diken Mhrz May 03 '22 at 10:38
  • Could you also clarify the "the order of registration cannot be ensured" part? – Diken Mhrz May 03 '22 at 10:42
  • @DikenMhrz See the further explanation at the end of my answer. – Gergely Kőrössy May 03 '22 at 11:38
  • Thanks for the explanation. However, new problems arose for me. I will try my best to explain the situation. So basically I am trying to create a helper class that will ask run-time permissions to the user. I needed activity inside the helper class to create a RequestMultiplePermissions() contract and was passing the activity to the helper class through constructor and was instantiating the helper class before onCreate as it will register the contract before any lifecycle methods of the activity. It was working fine till I had to request permission from fragment. Continued...... – Diken Mhrz May 04 '22 at 09:07
  • So as you may have guessed, activity can be in resumed state during the transaction of the fragment. Resulting in: 'cannot register the contract in resumed state'. It was working after I used activityResultRegistry but you mentioned the problem with activityResultRegistry. So is there a way to create a helper class that can register contracts?? I hope you understand my question. – Diken Mhrz May 04 '22 at 09:10
  • I'm not entirely sure I understand your problem, but I'll still try to give you an answer. I can see 2 possibilities here: you either make the helper class Activity-scoped so any fragment would work on the same instance which have the registered contracts, or make the helper class take `ActivityResultCaller` instead of Activity as the parameter and pass in the Fragment (which implements it by default). `ActivityResultCaller` has the `registerForActivityResult(...)` methods you can use to register contracts. – Gergely Kőrössy May 05 '22 at 01:34
  • @ Gergely Korossy didn't know about `ActivityResultCaller`.. It worked.. Thanks – Diken Mhrz May 05 '22 at 09:24