Michael Dodd
Michael Dodd

Reputation: 10270

Callback from registerForActivityResult not called when fragment is destroyed

Let's assume a fragment has this ActivityResultLauncher:

class MyFragment : Fragment(R.layout.my_fragment_layout) {

    companion object {
        private const val EXTRA_ID = "ExtraId"

        fun newInstance(id: String) = MyFragment().apply {
            arguments = putString(EXTRA_ID, id)
        }
    }

    private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
        if (it.resultCode == Activity.RESULT_OK) {
            Timber.i("Callback successful")
        }
    }

...

This Fragment a wrapped in an Activity for temporary architectural reasons, it will eventually be moved into an existing coordinator pattern.

class FragmentWrapperActivity : AppCompatActivity() {
    
    private lateinit var fragment: MyFragment
    private lateinit var binding: ActivityFragmentWrapperBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityFragmentWrapperBinding.inflate(this)
        setContentView(binding.root)

        fragment = MyFragment.newInstance("blah")

        supportFragmentManager.transact {
            replace(R.id.fragment_container, fragment)
        }
    }
}

And we use that launcher to start an Activity, expecting a result:

fun launchMe() {
    val intent = Intent(requireContext(), MyResultActivity::class.java)
    launcher.launch(intent)
}

On a normal device with plenty of available memory, this works fine. MyResultActivity finishes with RESULT_OK, the callback is called and I see the log line.

However, where memory is an issue and the calling fragment is destroyed, the launcher (and its callback) is destroyed along with it. Therefore when MyResultActivity finishes, a new instance of my fragment is created which is completely unaware of what's just happened. This can be reproduced by destroying activities as soon as they no longer have focus (System -> Developer options -> Don't keep activities).

My question is, if my fragment is reliant on the status of a launched activity in order to process some information, if that fragment is destroyed then how will the new instance of this fragment know where to pick up where the old fragment left off?

Upvotes: 0

Views: 1268

Answers (1)

ianhanniballake
ianhanniballake

Reputation: 199880

Your minimal fragment is unconditionally replacing the existing fragment with a brand new fragment everytime it is created, thus causing the previous fragment, which has had its state restored, to be removed.

As per the Create a Fragment guide, you always need to wrap your code to create a fragment in onCreate in a check for if (savedInstanceState == null):

In the previous example, note that the fragment transaction is only created when savedInstanceState is null. This is to ensure that the fragment is added only once, when the activity is first created. When a configuration change occurs and the activity is recreated, savedInstanceState is no longer null, and the fragment does not need to be added a second time, as the fragment is automatically restored from the savedInstanceState.

So your code should actually look like:

fragment = if (savedInstanceState == null) {
    // Create a new Fragment and add it to
    // the FragmentManager
    MyFragment.newInstance("blah").also { newFragment ->
        supportFragmentManager.transact {
            replace(R.id.fragment_container, newFragment)
        }
    }
} else {
    // The fragment already exists, so
    // get it from the FragmentManager
    supportFragmentManager.findFragmentById(R.id.fragment_container) as MyFragment
}

Upvotes: 2

Related Questions