ViewModel中的NavController:如果没有@Inject构造函数或@Provides-annotated方法,则无法提供androidx.navigation.NavController

cotxawn7  于 2023-04-18  发布在  Android
关注(0)|答案(3)|浏览(144)

bounty已结束,回答此问题可获得+50声望奖励,奖励宽限期10小时后结束,krtkush希望引起更多关注此问题:我的目标是在我的android代码中实现Clean Architecture,从而将导航隔离在自己的层中。因此,我希望从ViewModel而不是从UI调用导航调用。

我正在尝试将navController注入到ViewModel中-
视图模型-

@HiltViewModel
class DeviceHolderListViewModelImpl @Inject constructor(
    private val fetchUsersUseCase: FetchUsersUseCase,
    private val navigationUtil: NavigationUtil
    ) : DeviceHolderListViewModel, ViewModel() {
      

      // Trying to access navigationUtil here

}

导航实用程序

class NavigationUtil @Inject constructor(private val navController: NavController) {

    fun navigateTo(destination: String, bundle: Bundle) {

        when(destination) {
            DEVICE_HOLDER_LIST -> navController.navigate(R.id.action_global_goto_deviceHolderListFragment, bundle)
            DEVICE_HOLDER_DETAILS -> navController.navigate(R.id.action_global_goto_deviceHolderDetailsFragment, bundle)
        }
    }

    fun navigateBack() {
        navController.popBackStack()
    }
}

导航模块-

@Module
@InstallIn(ActivityComponent::class)
object NavigationModule {

    @Provides
    fun provideNavController(activity: AppCompatActivity): NavController {
        return Navigation.findNavController(activity, R.id.nav_host_fragment)
    }

    @Provides
    fun provideNavigationUtil(navController: NavController): NavigationUtil {
        return NavigationUtil(navController)
    }
}

在尝试构建代码时,我得到以下错误-
error:[Dagger/MissingBinding] androidx.navigation.NavController无法在没有@Inject构造函数或@Provides-annotated方法的情况下提供。
是因为我试图从ViewModel访问navController,而它应该从片段或Activity访问吗?
我的目标是从ViewModel开始导航。我应该怎样做呢?

41zrol4v

41zrol4v1#

你不能用Hilt直接注入NavController,因为你只能从Activity/Fragment调用Navigation.findNavController,但是你正在使用的NavigationUtil方法是一个很好的方法,可以从ViewModel访问导航。
你只需要做一些改变:

  • 创建两个方法setNavControllerclearNavController,而不是将NavController作为NavigationUtil的参数传递。
  • NavigationUtil的作用域设置为ViewModel,并使用@ViewModelScoped注解provide方法。
  • NavigationUtil注入Activity,并在onCreate方法中调用setNavController,在onDestroy方法中调用clearNavController

现在,您的NavigationUtil可以访问NavController,当Activity被重新创建时,它会被更新(当Activity被销毁时,它会被清除),您可以从ViewModel访问它。
你可以在这里查看我对一个非常相似的问题的答案:如何使用hilt将jetpack compose中的rememberNavController注入到Activity中?

zzwlnbp8

zzwlnbp82#

es,您看到的错误消息表明Dagger无法向NavigationUtil类提供NavController的示例。这可能是因为NavController通常与片段或Activity相关联,并且Dagger不清楚如何在视图模型的上下文中提供它。您可以采取的一种方法是在视图模型中定义一个表示导航事件的接口,然后让你的片段/Activity实现该接口并处理导航事件。例如:

interface NavigationHandler {
    fun navigateTo(destination: String, bundle: Bundle)
    fun navigateBack()
}

class DeviceHolderListViewModelImpl @Inject constructor(
    private val fetchUsersUseCase: FetchUsersUseCase,
    private val navigationHandler: NavigationHandler
) : DeviceHolderListViewModel, ViewModel() {

    fun onSomeButtonClick() {
        // Do some work...
        navigationHandler.navigateTo("destination", bundle)
    }

}

class MyFragment : Fragment(), NavigationHandler {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // Inflate your view...
        // Initialize your view model...
        viewModel.navigationHandler = this
        // Return your view...
    }

    override fun navigateTo(destination: String, bundle: Bundle) {
        findNavController().navigate(destination, bundle)
    }

    override fun navigateBack() {
        findNavController().popBackStack()
    }

}
vwkv1x7d

vwkv1x7d3#

我会建议一个不同的方法。
我通常创建一个Navigatior类,它同时注入到ActivityViewModel中。ViewModel调用navigator方法,Activity订阅它们。
免责声明:以下代码是根据我的记忆写的,语法可能不正确。请随时改进。

sealed interface NavEvent {
   data class Navigate(val directions: NavDirections): NavEvent
   data class PopWithResult(val args: Bundle): NavEvent
   object Pop(): NavEvent
}
@Singleton
class Navigator @Inject constructor() {

   private val _navigateFlow = MutableSharedFlow<NavEvent>
   val navigateFlow: SharedFlow = _navigateFlow

   suspend fun navigate(nav: NavDirections) {
       _navigateFlow.emit(Navigate(directions))
   }
   
   suspend fun pop() {
       _navigateFlow.emit(Pop)
   }
}
@HiltViewModel
class MyVm @Inject constructor(
    private val navigator: Navigator
) : ViewModel() {

    fun onClickSmth() {
        viewModelScope.launch {
            navigator.navigate(MyFragmentDirections.actionToSomewhere())
        }
    }
}
@AndroidEntryPoint
class MainActivity : Activity {
    
    @Inject lateinit var navigator: Navigator

    private lateinit var navController: NavController = TODO()

    fun onCreate() {
        lifecycleScope.launch {
            navigator.navigateFlow.collect(this::onNavEvent)
        }
    }
    
    private fun onNavEvent(event: NavEvent) {
        when (navEvent) {
            Navigate -> navController.navigate(navEvent.directions)
            Pop -> navController.popBackStack()
            else -> TODO()
        }
    }
}

相关问题