Hilt 导航撰写和 ViewModel

问题描述 投票:0回答:1

我尝试遵循 android 文档中的示例,但无法使用 Navigation Compose、ViewModel 和 Hilt 实现导航。不会引发任何错误,并且在 NavigationScreen LaunchedERffect 中不会在 ViewModel 发出时触发。

可组合项:

@HiltAndroidApp
class MyApplication : Application() {}

@Serializable
object NavScreen

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            val navController = rememberNavController()

            PhotosSyncTheme {
                NavHost(
                    navController = navController,
                    startDestination = NavScreen
                ) {
                    composable<NavScreen> { NavigationScreen(navController) }
                    composable<NavigationEvent.NavigateToHome> { HomeScreen() }
                    composable<NavigationEvent.NavigateToSettings> { Settings() }
                }
            }
        }
    }
}

@Composable
fun NavigationScreen(navController: NavController) {
    val navViewModel = hiltViewModel<NavigationViewModel>()

    val navigationEvent by navViewModel.navigationEvents.collectAsState(initial = NavigationEvent.NavigateToHome)

    LaunchedEffect(navigationEvent) {
        Log.d("Navigation Screen", navigationEvent.toString())
        navigationEvent.let { event ->
            Log.d("Navigation Screen", event.toString())
            when (event) {
                is NavigationEvent.NavigateBack -> navController.popBackStack()
                NavigationEvent.NavigateToHome -> navController.navigate(NavigationEvent.NavigateToHome)
                NavigationEvent.NavigateToSettings -> navController.navigate(NavigationEvent.NavigateToSettings)
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen() {
    val navViewModel = hiltViewModel<NavigationViewModel>()
    var selectedItem by remember { mutableIntStateOf(0) }

    Scaffold(
        modifier = Modifier.fillMaxSize(),
        topBar = {
            TopAppBar(title = { Text("Photos Sync") }, actions = {
                IconButton(onClick = {
                    navViewModel.navigateToSettings()
                }) {
                    Icon(
                        imageVector = Icons.Filled.Settings,
                        contentDescription = "Settings icon"
                    )
                }
            })
        }
    )
    { }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Settings() {
    val viewModel = hiltViewModel<NavigationViewModel>()

    Scaffold(
        modifier = Modifier.fillMaxSize(),
        topBar = {
            TopAppBarWithBack(title = "Definições")
        }
    )
    { }
}

视图模型:

@HiltViewModel
class NavigationViewModel @Inject constructor() : ViewModel() {
    private val _navigationEvents = MutableSharedFlow<NavigationEvent>()
    val navigationEvents: SharedFlow<NavigationEvent> = _navigationEvents.asSharedFlow()

    fun navigateBack() {
        viewModelScope.launch { _navigationEvents.emit(NavigationEvent.NavigateBack) }
    }

    fun navigateToHome() {
        viewModelScope.launch { _navigationEvents.emit(NavigationEvent.NavigateToHome) }
    }

    fun navigateToSettings() {
        viewModelScope.launch {
            Log.d("Navigation ViewModel", "Emit Navigate To Settings")
            try {
                _navigationEvents.emit(NavigationEvent.NavigateToSettings)

            } catch (e: Exception) {
                Log.e("Navigation ViewModel", "Error emitting navigation event")
            } finally {
                Log.d("Navigation ViewModel", "done Emit Navigate To Settings")
            }
        }
    }

    fun navigateTo(event: NavigationEvent) {
        viewModelScope.launch { _navigationEvents.emit(event) }
    }

    sealed class NavigationEvent {
        @Serializable
        object NavigateBack : NavigationEvent()

        @Serializable
        object NavigateToHome : NavigationEvent()

        @Serializable
        object NavigateToSettings : NavigationEvent()
    }
}

我期望当我单击设置按钮时,使用

navController.navigate

将设置可组合项推送到可组合项堆栈的顶部
android kotlin android-viewmodel dagger-hilt android-jetpack-compose-navigation
1个回答
0
投票

视图模型的范围仅限于当前导航目的地(无论您是否使用 Hilt)。您对不同的屏幕使用相同的视图模型,因此每个屏幕都会获得其“自己的”视图模型实例。修改其中一个的状态不会影响其他的状态。 您可以将视图模型范围限制为其他内容,以便您的目的地将共享

相同

视图模型实例(请参阅我可以在不同的 Compose 导航路线中使用 hiltViewModel() 共享 ViewModel 吗?),但我不明白为什么您甚至想首先涉及视图模型,因为导航与 UI 状态无关。您的视图模型当前所做的就是接收导航事件并将其传递回可组合项。您甚至不需要保留当前路线或其参数(可以使用SavedStateHandle

来完成),但这对于专用于特定屏幕的视图模型也最有用,而不是
导航视图模型 我建议完全删除

NavigationViewModel

。您可以保留您的

NavigationEvent
密封层次结构,尽管我发现这个名称很有误导性,而且您甚至不需要继承。最好用这样的东西替换它(在顶层声明,没有封闭的接口):
@Serializable data object HomeRoute
@Serializable data object SettingsRoute

由于您不再将它们用作事件,因此它们现在仅用于导航图中的路线,因此得名。由于 
NavigateBack

不是一条路线,因此可以将其删除(只需在需要时调用

navController.popBackStack()
)。
您的 NavHost 将如下所示:

NavHost( navController = navController, startDestination = HomeRoute, ) { composable<HomeRoute> { HomeScreen( navigateToSettings = { navController.navigate(SettingsRoute) }, ) } composable<SettingsRoute> { Settings() } }

请注意,我创建了新参数 
HomeScreen

来传递必要的导航逻辑,而不是

navigateToSettings
依赖视图模型来访问导航。你不应该传递 navController 。
HomeScreen
然后使用新参数,如下所示:
@Composable
fun HomeScreen(
    navigateToSettings: () -> Unit,
) {
    // ...
    IconButton(onClick = navigateToSettings) {
        //...
    }
}

这也使得 HomeScreen 具有更好的可测试性和可重用性,因为它不再依赖于视图模型。

整个导航逻辑现在包含在 NavHost 中。

NavigationScreen

带有 LaunchedEffect 的可以删除。

Google 提供了功能齐全的示例项目 

Now in Android

,除其他外,还展示了如何使用更复杂的导航。如果您想窥探,只需使用以下链接将其导入 Android Studio(“从版本控制获取...”):https://github.com/android/nowinandroid

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.