Search code examples
androidandroid-lifecycle

ActivityResultLauncher not re-registered after Fragment recreated


I have a fragment where I am using DefaultLifecycleObserver to handle launching a file chooser and getting content (via a uri). The first time my fragment is created in the activity lifecycle, the behavior works fine. If I navigate to a different fragment and come back, the fragment gets recreated, but the ActivityResultLauncher is not re-registered.

I was able to get around the issue by creating a cleanup() function that manually unregisters the Activity result launcher. I call this cleanup method in the Fragment's onDestroy() and it fixes the issue. However, this approach feels very hacky. What is the idiomatic way of handling re-registering the ActivityResultLauncher?

class FileSelectorLifecycleObserver(private val registry : ActivityResultRegistry, private val callback: (Uri) -> Unit):
    DefaultLifecycleObserver {
    private lateinit var getContent: ActivityResultLauncher<String>

    override fun onCreate(owner: LifecycleOwner) {
        getContent = registry.register("com.histogramo.app.FILE_LOAD", owner, ActivityResultContracts.GetContent()) { uri ->
            uri?.let { callback(it) }
        }
    }

    fun cleanup() { getContent.unregister() }

    fun selectFile() { getContent.launch("application/octet-stream")  }
}

class MyFragment : Fragment() {
    private var _binding: MyFragmentBinding? = null
    private val binding
        get() = _binding

    private lateinit var observer : FileSelectorLifecycleObserver

    override fun onDestroyView() {
        _binding = null
        super.onDestroyView()
        observer.cleanup()
        lifecycle.removeObserver(observer)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = MyFragmentBinding.inflate(inflater, container, false)
        observer = FileSelectorLifecycleObserver(requireActivity().activityResultRegistry) { uri ->
            // DO STUFF WITH THE FILE
        }
        lifecycle.addObserver(observer)
        return binding?.root ?: View(context)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding?.loadFileButton?.setOnClickListener {
            activity?.let { observer.selectFile() }
        }
    }
}

Solution

  • You're using

    lifecycle.addObserver(observer)
    

    Which is registering the observer with the Fragment's lifecycle - not with the Fragment's view lifecycle (the one that is created in onCreateView and destroyed in onDestroyView). This means it will only get a single onCreate call when the Fragment's onCreate is called, not a call every time the fragment's view is created (e.g., onCreateView is called).

    If you want your ActivityResultLauncher to be tied to the Fragment View lifecycle, then you need to use:

    viewLifecycleOwner.lifecycle.addObserver(observer)
    

    That will ensure your FileSelectorLifecycleObserver is called every time the Fragment's view is created, which would be appropriate if you are creating a new Observer in every onCreateView.