“ActivityResultLauncher”在Fragment重新创建后未重新注册。

huangapple go评论52阅读模式
英文:

ActivityResultLauncher not re-registered after Fragment recreated

问题

我有一个片段,我正在使用DefaultLifecycleObserver来处理启动文件选择器并获取内容(通过uri)。在活动生命周期中创建我的片段的第一次时,行为正常。如果我导航到不同的片段然后返回,片段会被重新创建,但ActivityResultLauncher不会重新注册。

我通过创建一个cleanup()函数来解决这个问题,该函数手动取消注册ActivityResultLauncher。我在Fragment的onDestroy()中调用此清理方法,它可以解决问题。但是,这种方法感觉非常不规范。处理重新注册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 ->
            // 使用文件执行操作
        }
        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() }
        }
    }
}
英文:

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) -&gt; Unit):
    DefaultLifecycleObserver {
    private lateinit var getContent: ActivityResultLauncher&lt;String&gt;

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

    fun cleanup() { getContent.unregister() }

    fun selectFile() { getContent.launch(&quot;application/octet-stream&quot;)  }
}

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 -&gt;
            // 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() }
        }
    }
}

答案1

得分: 1

你尝试过将启动器直接注册为片段的私有属性,而不是将其包装在专用的LifecycleObserver中吗?这可能会避免手动注册/取消注册和/或清理LifecycleObserver的需要,这可能是此处错误的根源。

官方文档中有一个示例:
https://developer.android.com/training/basics/intents/result#launch

英文:

Have you tried directly registering the launcher as a fragment's private attribute instead of wrapping it in a dedicated LifecycleObserver?
This might avoid the need to deal with manually register/unregister and/or cleanup the LifecycleObserver, which might be the source of the bug here.

There is an example in the official documentation:
https://developer.android.com/training/basics/intents/result#launch

答案2

得分: 1

你正在使用

lifecycle.addObserver(observer)

这会将观察者注册到 Fragment 的 lifecycle,而不是注册到 Fragment 的 view 生命周期(在 onCreateView 中创建并在 onDestroyView 中销毁的生命周期)。这意味着它只会在调用 Fragment 的 onCreate 时得到一次 onCreate 调用,而不是在每次 fragment 的视图创建时(例如,调用 onCreateView)都会得到调用。

如果你希望你的 ActivityResultLauncher 与 Fragment 的视图生命周期关联,那么你需要使用:

viewLifecycleOwner.lifecycle.addObserver(observer)

这将确保你的 FileSelectorLifecycleObserver 在每次 Fragment 的视图创建时都会被调用,如果你在每个 onCreateView 中创建新的观察者,这将是合适的。

英文:

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.

huangapple
  • 本文由 发表于 2023年6月5日 01:55:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76401715.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定