【问题标题】:Proper usage of navigation component with view pager2 and nested navigation使用 view pager2 和嵌套导航正确使用导航组件
【发布时间】:2020-08-21 14:40:24
【问题描述】:

我正在尝试使用 ViewPager2 + FragmentStateAdapter + 导航组件构建以下视图结构/导航。

前提条件:单一活动架构,带有一个导航图

1.片段 A 包含一个视图寻呼机。 View pager 使用 FragmentStateAdapter。

2。 Fragment B 是通过 FragmentStateAdapter 实例化的(“生活”在视图寻呼机中)。

3。片段 C - 应该从片段 B 导航到。 --> 这就是问题所在。


方法 1:ViewPager2 + FragmentStateAdapter + 从 Fragment B 声明的导航

    <fragment
        android:id="@+id/fragmentA"
        android:name="com.abc.FragmentA"
        android:label="FragmentA" />

    <fragment
        android:id="@+id/fragmentB"
        android:name="com.abc.FragmentB"
        android:label="FragmentB">
        <action
            android:id="@+id/to_fragmentC"
            app:destination="@id/fragmentC" />
    </fragment>

    <fragment
        android:id="@+id/fragmentC"
        android:name="com.abc.FragmentC"
        android:label="FragmentC" />

FragmentB 执行:

 FragmentBDirections
            .toFragmentC()
            .let { findNavController().navigate(it) }

结果:

App crash
java.lang.IllegalArgumentException: navigation destination com.abc:id/to_fragmentC is unknown to this NavController

方法 2:ViewPager2 + FragmentStateAdapter + 从 Fragment A 声明的导航

    <fragment
        android:id="@+id/fragmentA"
        android:name="com.abc.FragmentA"
        android:label="FragmentA" >
        <action
            android:id="@+id/to_fragmentC"
            app:destination="@id/fragmentC" />
    </fragment>

    <fragment
        android:id="@+id/fragmentB"
        android:name="com.abc.FragmentB"
        android:label="FragmentB">
    </fragment>

    <fragment
        android:id="@+id/fragmentC"
        android:name="com.abc.FragmentC"
        android:label="FragmentC" />

FragmentB 执行:

 FragmentADirections
            .toFragmentC()
            .let { findNavController().navigate(it) }

结果:

App navigates to FragmentC, but when i hit the back button , it crashes with :
java.lang.IllegalArgumentException
        at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
        at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)
        at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1161)
        at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:461)
        at com.abc.FragmentA.viewCreated(FragmentA.kt:69)


方法 3:ViewPager + FragmentStatePagerAdapter(已弃用)+ 从 Fragment B 声明的导航

与方法一的结果相同。


方法 4:ViewPager + FragmentStatePagerAdapter(已弃用)+ 从 Fragment A 声明的导航

这个确实有效。此外,导航返回工作正常。

这里的问题是:

  • 必须为 FragmentB 的每个父片段定义导航 -> 不可扩展
  • 已弃用的适配器的使用情况

如果有人知道这个问题的一些优雅的解决方案,我会很高兴任何提示。

谢谢

【问题讨论】:

  • 我有同样的问题,我有 NavGraph -> Fragment A 有(View pager)我必须从 View pager(Fragment 1 | Fragment 2)导航到 Fragment B 如果你发布解决方案得到任何我会做同样的事情。
  • 方法 2 在我的情况下运行良好。问题可能出在 viewpager 而不是导航
  • 你是如何初始化 FragmentStateAdapter 的?您是否从 Dagger 获取 FragmentStateAdapter 实例?用匕首初始化时出现问题
  • @nabeel 我的崩溃日志与方法 2 相同。知道如何解决吗?
  • @Karthik 请发布代码

标签: android androidx android-architecture-navigation


【解决方案1】:

我不知道您是否找到了问题的解决方案,但这就是我在我的应用中的做法(这与您为解决方案 4 描述的类似)

我有一个应用程序,它有一个主片段 (HomeFragment),其中包含一个底部选项卡栏,显示四个不同的片段。

在使用应用程序的任何时候,用户都可以登录/注销(这是一个包含 4 个片段的流程)回答问卷(2 个片段)显示设置(一个片段)或拍照(1 个片段) )

我已经这样声明了我的主视图模型:

class HomeViewModel(app:Application):AndroidViewModel(app), HomeParent {
}


intefrace HomeParent { //this contains all the actions that can be executed, like showLogin, showQuestions etc etc
    fun ShowLogin()
 
}

在 HomeFragment 中,我像这样初始化寻呼机:

class HomeFragment: Fragment(), KoinComponent {
    private val vm by sharedViewModel<HomeViewModel>()

    private val pager by lazy { binding.pager }  //I just use view bindings you can do it with findviewbyid
    private val adapter by lazy { HomePageAdapter(this) }

    
    override fun initView(state:Bundle?) { //this is run before oncreate finishes
         pager.setPageTransformer(this::pageAnimation) //this is just to animate transitions
    }

    override fun setUpListeners() {  //this is run after oncreate finishes
        bottomNav.setOnNavigationItemSelectedListener(this::navigationItemSelected) //this does stuff and sends it to vm
        pager.registerOnPageChangeCallback(pagerChangeCallback)
    }

    override fun onResume() {
        super.onResume()
        pager.adapter = adapter
        (vm.state.value as? HomeState.Default)?.let { //I'm using mvi, this basically holds the current position in the pager
            pager.setCurrentItem(it.position.position, false)
        }
    }

    override fun onPause() {
        super.onPause()
        pager.adapter = null //I don't remember why I did this, I gues
    }

    override fun onDestroy() {
        super.onDestroy()
        pager.unregisterOnPageChangeCallback(pagerChangeCallback)
    }

}

实际的适配器如下所示:

class HomePageAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount() = 4

    override fun createFragment(position: Int): Fragment {
        return when (position) {
            0 -> Fragment1()
            1 -> Fragment2()
            2 -> Fragment3()
            3 -> Fragment4()
            else -> throw Throwable("Invalid position $position")
        }
    }
}

每个片段都是这样创建的:

class Fragment1 :Fragment(),
    private val home : HomeParent by sharedViewModel<HomeViewModel>()
    override val vm: Fragment1ViewModel by viewModel { parametersOf(home) }

如您所见,我将主页视图模型作为参数传递给主页内的每个选项卡

那么每个片段都可以做类似的事情

home.ShowLogin()

这会将消息发送到 HomeViewModel,然后 HomeViewModel 会将消息发送到 HomeFragment,它会调用类似

findNavController().navigate(HomeFragmentDirections.logIn())

如您所见,Fragment1/2/3/4 在整个导航过程之外,而 HomeFragment 包含将要执行的所有操作

希望我解释正确,如果有人有任何问题或需要澄清,请告诉我

【讨论】:

    【解决方案2】:

    总体思路

    您可以在 ViewModel 中定义 Channel 来处理 UI 事件。

    让我们看看我们如何做到这一点:

    1. 定义你的viewModel:
    @HiltViewModel
    class OrderViewModel @Inject constructor(
        private val repository: Repository
    ) : ViewModel() {
        
        private val orderEventChannel = Channel<OrderEvent>()
        val orderEvent = orderEventChannel.receiveAsFlow()
    
        ...
    
        fun onInvoiceClicked(invoice: Invoice) = viewModelScope.launch {
            orderEventChannel.send(OrderEvent.NavigateToOrderDetailsFragment(invoice))
        }
    
        sealed class OrderEvent{
            data class NavigateToOrderDetailsFragment(val invoice: Invoice) : OrderEvent()
        }
    }
    
    1. activityViewModels 的初始 viewModel 并在包含 viewPager2 视图的 Fragment 中侦听 Channel
    @AndroidEntryPoint
    class OrdersFragment : Fragment(R.layout.fragment_orders) {
        private val viewModel: OrderViewModel by activityViewModels()
        
            override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            
            ...
            
            viewLifecycleOwner.lifecycleScope.launchWhenStarted {
                viewModel.orderEvent.collect { event ->
                    when(event){
                        is OrderViewModel.OrderEvent.NavigateToOrderDetailsFragment -> {
                            findNavController().navigate(
                                OrdersFragmentDirections.actionOrdersFragmentToOrderDetailsFragment(event.invoice)
                            )
                        }
                    }
                }
            }
        }
    
    1. 现在在所有Fragment 中绑定viewPager 初始视图模型activityViewModels 并调用onInvoiceClicked() 函数:
    class InProgressViewPagerFragment : Fragment(R.layout.fragment_in_progress_view_pager){
        private val viewModel: OrderViewModel by activityViewModels()
        
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            ...
            binding.invoice.setOnClickListener{
               viewModel.onInvoiceClicked(invoice)
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      您不需要将 Fragment B 声明为图形中的目的地,因为您从不使用 NavController 导航到它。除了使用操作,您可以只使用 destination id 在 Fragment B 中实现导航,例如 findNavController().navigate(R.id.fragmentC)findNavController方法会找到父fragment的navController来执行导航。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-12-10
        • 1970-01-01
        • 2018-03-26
        • 2023-01-25
        • 2018-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多