使用导航组件时如何设置对话框的目标片段
How to set target fragment of a dialog when using navigation components
我在使用 childFragmentManager
的片段内或使用 supportFragmentManager
在 Activity 内显示对话框,在此过程中我想设置目标片段,如下所示:
val textSearchDialog = TextSearchDialogFragment.newInstance()
textSearchDialog.setTargetFragment(PlaceSearchFragment@this, 0)
但是当 运行 该代码时我得到错误:
java.lang.IllegalStateException: Fragment
TextSearchDialogFragment{b7fce67 #0 0} declared target fragment
PlaceSearchFragment{f87414 #0 id=0x7f080078} that does not belong to
this FragmentManager!
我不知道如何访问 FragmentManager
导航组件用来管理片段的显示,是否有解决方案?
更新:作为 Navigation 2.3.0 的一部分,Navigation 添加了对 returning a result with a specific section on returning a result from a Dialog destination 的明确支持,作为使用共享 ViewModel 的替代方法。
上一个回答:
片段与导航架构组件之间通信的推荐模式是通过 shared ViewModel
- ViewModel
位于 Activity 级别,通过检索 ViewModel
使用 ViewModelProvider(getActivity())
根据 the documentation,这提供了很多好处:
- The activity does not need to do anything, or know anything about this communication.
- Fragments don't need to know about each other besides the
SharedViewModel
contract. If one of the fragments disappears, the other one keeps working as usual.
- Each fragment has its own lifecycle, and is not affected by the lifecycle of the other one. If one fragment replaces the other one, the UI continues to work without any problems.
在比整个 activity 更小的范围内共享 ViewModel
详细说明已接受的答案:
(1) 创建一个共享视图模型,用于在 Activity 内的片段之间共享数据。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Double> aDouble = new MutableLiveData<>();
public void setDouble(Double aDouble) {
this.aDouble.setValue(aDouble);
}
public LiveData<Double> getDouble() {
return aDouble;
}
}
(2) 在视图模型中存储您要访问的数据。注意视图模型的范围(getActivity).
SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.setDouble(someDouble);
(3)让fragment实现dialog的回调接口,在不设置target fragment的情况下加载dialog
fragment.setOnDialogSubmitListener(this);
fragment.show(getActivity().getSupportFragmentManager(), TAG);
(4) 在对话框中检索数据。
SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.getDouble().observe(this, new Observer<Double>() {
@Override
public void onChanged(Double aDouble) {
// do what ever with aDouble
}
});
使用视图模型和片段 ktx,您可以在父片段和子片段之间托管共享视图模型,因此您的 activity 不必包含视图模型的实例并存储数据直到 activity完成后,你可以将视图模型存储在父片段中,这样做,当你弹出实例化视图模型的片段时,视图模型将被清除
进口
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
ParentFragment(SharedViewModel 宿主)
class ParentFragment:Fragment() {
private val model: SharedViewModel by viewModels()
}
子片段
class ChildFragment:Fragment(){
private val model: SharedViewModel by viewModels ({requireParentFragment()})
}
因此,这样做将在父片段中托管 sharedviewmodel,并且依赖于该父片段的子片段将可以访问 SharedViewModel
的同一实例,并且当您弹出(也称为销毁片段)时) ,您的 onCleared()
方法将在您的 viewmodel 上触发,shareviewmodel 及其所有数据将被清除。
这样,您的 MainActivity 就不会包含片段共享的所有数据,并且您不需要在每次离开使用 SharedViewModel
[= 的片段时都清除该数据17=]
现在在 alpha 中,您还可以使用将在导航之间保存数据的视图模型在导航之间传递数据,假设您想在 Fragment B 和 Fragment A 之间共享数据,现在只需两行即可完成
https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result
None 的现有答案实际上回答了您的问题 - 使用导航组件时我们如何设置对话框的目标片段?
事实证明我们不需要使用共享 ViewModel 的(坦率地说可怕的)模式。一旦您知道如何使用导航组件设置目标片段实际上非常容易。
我已经写了一整篇文章,你可以在这里阅读:
https://lukeneedham.medium.com/using-targetfragment-with-jetpack-navigation-component-9c4302e8c062
您也可以在这里查看要点:
https://gist.github.com/LukeNeedham/83f0bdaa8d56d03d11f727967eb327f2
他们的钥匙是自定义的 FragmentFactory
:
fun FragmentManager.autoTarget() {
fragmentFactory = ChildManagerFragmentFactory(this)
}
class ChildManagerFragmentFactory(
private val fragmentManager: FragmentManager
) : AutoTargetFragmentFactory() {
override fun getCurrentFragment() =
fragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.firstOrNull()
}
abstract class AutoTargetFragmentFactory : FragmentFactory() {
abstract fun getCurrentFragment(): Fragment?
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val fragment = super.instantiate(classLoader, className)
val currentFragment = getCurrentFragment()
fragment.setTargetFragment(currentFragment, REQUEST_CODE)
return fragment
}
companion object {
const val REQUEST_CODE = 0
}
}
然后像这样简单地使用:
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.autoTarget()
}
}
我在使用 childFragmentManager
的片段内或使用 supportFragmentManager
在 Activity 内显示对话框,在此过程中我想设置目标片段,如下所示:
val textSearchDialog = TextSearchDialogFragment.newInstance()
textSearchDialog.setTargetFragment(PlaceSearchFragment@this, 0)
但是当 运行 该代码时我得到错误:
java.lang.IllegalStateException: Fragment TextSearchDialogFragment{b7fce67 #0 0} declared target fragment PlaceSearchFragment{f87414 #0 id=0x7f080078} that does not belong to this FragmentManager!
我不知道如何访问 FragmentManager
导航组件用来管理片段的显示,是否有解决方案?
更新:作为 Navigation 2.3.0 的一部分,Navigation 添加了对 returning a result with a specific section on returning a result from a Dialog destination 的明确支持,作为使用共享 ViewModel 的替代方法。
上一个回答:
片段与导航架构组件之间通信的推荐模式是通过 shared ViewModel
- ViewModel
位于 Activity 级别,通过检索 ViewModel
使用 ViewModelProvider(getActivity())
根据 the documentation,这提供了很多好处:
在比整个 activity 更小的范围内共享 ViewModel
- The activity does not need to do anything, or know anything about this communication.
- Fragments don't need to know about each other besides the
SharedViewModel
contract. If one of the fragments disappears, the other one keeps working as usual.- Each fragment has its own lifecycle, and is not affected by the lifecycle of the other one. If one fragment replaces the other one, the UI continues to work without any problems.
详细说明已接受的答案:
(1) 创建一个共享视图模型,用于在 Activity 内的片段之间共享数据。
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Double> aDouble = new MutableLiveData<>();
public void setDouble(Double aDouble) {
this.aDouble.setValue(aDouble);
}
public LiveData<Double> getDouble() {
return aDouble;
}
}
(2) 在视图模型中存储您要访问的数据。注意视图模型的范围(getActivity).
SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.setDouble(someDouble);
(3)让fragment实现dialog的回调接口,在不设置target fragment的情况下加载dialog
fragment.setOnDialogSubmitListener(this);
fragment.show(getActivity().getSupportFragmentManager(), TAG);
(4) 在对话框中检索数据。
SharedViewModel svm =ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
svm.getDouble().observe(this, new Observer<Double>() {
@Override
public void onChanged(Double aDouble) {
// do what ever with aDouble
}
});
使用视图模型和片段 ktx,您可以在父片段和子片段之间托管共享视图模型,因此您的 activity 不必包含视图模型的实例并存储数据直到 activity完成后,你可以将视图模型存储在父片段中,这样做,当你弹出实例化视图模型的片段时,视图模型将被清除
进口
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
ParentFragment(SharedViewModel 宿主)
class ParentFragment:Fragment() {
private val model: SharedViewModel by viewModels()
}
子片段
class ChildFragment:Fragment(){
private val model: SharedViewModel by viewModels ({requireParentFragment()})
}
因此,这样做将在父片段中托管 sharedviewmodel,并且依赖于该父片段的子片段将可以访问 SharedViewModel
的同一实例,并且当您弹出(也称为销毁片段)时) ,您的 onCleared()
方法将在您的 viewmodel 上触发,shareviewmodel 及其所有数据将被清除。
这样,您的 MainActivity 就不会包含片段共享的所有数据,并且您不需要在每次离开使用 SharedViewModel
[= 的片段时都清除该数据17=]
现在在 alpha 中,您还可以使用将在导航之间保存数据的视图模型在导航之间传递数据,假设您想在 Fragment B 和 Fragment A 之间共享数据,现在只需两行即可完成
https://developer.android.com/guide/navigation/navigation-programmatic#returning_a_result
None 的现有答案实际上回答了您的问题 - 使用导航组件时我们如何设置对话框的目标片段?
事实证明我们不需要使用共享 ViewModel 的(坦率地说可怕的)模式。一旦您知道如何使用导航组件设置目标片段实际上非常容易。
我已经写了一整篇文章,你可以在这里阅读:
https://lukeneedham.medium.com/using-targetfragment-with-jetpack-navigation-component-9c4302e8c062
您也可以在这里查看要点:
https://gist.github.com/LukeNeedham/83f0bdaa8d56d03d11f727967eb327f2
他们的钥匙是自定义的 FragmentFactory
:
fun FragmentManager.autoTarget() {
fragmentFactory = ChildManagerFragmentFactory(this)
}
class ChildManagerFragmentFactory(
private val fragmentManager: FragmentManager
) : AutoTargetFragmentFactory() {
override fun getCurrentFragment() =
fragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.firstOrNull()
}
abstract class AutoTargetFragmentFactory : FragmentFactory() {
abstract fun getCurrentFragment(): Fragment?
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
val fragment = super.instantiate(classLoader, className)
val currentFragment = getCurrentFragment()
fragment.setTargetFragment(currentFragment, REQUEST_CODE)
return fragment
}
companion object {
const val REQUEST_CODE = 0
}
}
然后像这样简单地使用:
class MainActivity : AppCompatActivity(R.layout.activity_main) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.autoTarget()
}
}