Навигация с помощью View Model в Jetpack Compose

Вот мой файл MainActivity.kt

Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    NavigationHoster()
                }

вот функция навхостера

@Composable
fun NavigationHoster(){
    val navController = rememberNavController();
    
    NavHost(navController = navController, startDestination = "Home"){
        composable("Home"){ HomeScreen(navController = navController)}
        composable("Chat"){ ChatScreen()}
    }
}

вот функция домашнего экрана

@OptIn(ExperimentalMaterial3Api::class)
//@Preview(showBackground = true, showSystemUi = true)
@Composable
fun HomeScreen(
    homeViewModel: HomeViewModel = viewModel(),
    navController: NavController
){
    val HomeViewState by homeViewModel.uiState.collectAsState()
    Scaffold (
        topBar = {
            TopAppBar(title = {
                Text(text = "Chat Wallet", style = MaterialTheme.typography.titleMedium)
            }, colors = TopAppBarDefaults.mediumTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.background
            ))
        }
    ){
        paddingValues -> Box(modifier = Modifier.padding(paddingValues).fillMaxSize(), contentAlignment = Alignment.Center) {
            IconButton(onClick = { navController.navigate("Chat") }, colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.primary)) {
                Icon(painter = painterResource(id = R.drawable.baseline_chat_24), contentDescription = null, tint = MaterialTheme.colorScheme.background)
            }

        }
    }
}

Вот моя ViewModel

class HomeViewModel : ViewModel(){
    private var _uiState = MutableStateFlow(HomeData())
    var uiState : StateFlow<HomeData> = _uiState.asStateFlow()

    public fun ChangeName() {
        _uiState.value = HomeData(UserName = "hello there")
    }
}

Эти коды работают хорошо, и я проверил. эта модель просмотра и файл экрана могут быть не связаны. Потому что я делаю разные тесты, изменяя их. Могу ли я узнать, как перейти к экрану чата, вызвав функцию changeName из пользовательского интерфейса? понятия не имею как это реализовать

У меня не было возможности импортировать navcontroller в модель представления. поэтому я остановился на этом пункте. Я думал, что его нужно импортировать в viewmodel.

пожалуйста, дайте мне простой пример. Спасибо.

0
0
50
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете создать класс NavManager, в котором будут функции навигации, и вы можете вызывать их из ViewModel:

class NavManager(private val navController: NavController) {
    fun navigateToChatScreen() {
        navController.navigate("Chat")
    }
}

Теперь передайте NavManager в HomeViewModel во время навигации:

@Composable
fun NavigationHoster(){
    val navController = rememberNavController();
    val navManager = NavManager(navController)
    NavHost(navController = navController, startDestination = "Home"){
        composable("Home"){ 
             val viewModel = HomeViewModel(navManager)
             HomeScreen(viewModel)
        }
        composable("Chat"){ ChatScreen()}
    }
}

Теперь домашний экран должен выглядеть так:

@Composable
fun HomeScreen(
    homeViewModel: HomeViewModel
){
  ...
}

И HomeViewModel должен выглядеть так:

class HomeViewModel(private val navManager: NavManager) : ViewModel(){
    private var _uiState = MutableStateFlow(HomeData())
    var uiState : StateFlow<HomeData> = _uiState.asStateFlow()

    public fun ChangeName() {
        _uiState.value = HomeData(UserName = "hello there")
    }

    // Call this function from view whenever you want to navigate to chat screen
    fun navigateToChatScreen(){
        navManager.navigateToChatScreen()
    }
}

По-другому можно использовать hilt, чтобы не приходилось вот так передавать navManager через навигацию. Вместо этого вы можете просто внедрить его зависимость в HomeViewModel с помощью конструктора.

вроде нормально проверю

Gayantha 21.08.2023 12:29
Ответ принят как подходящий

Навигация необходима, когда вы работаете над приложением Single Activity Compose. Но теперь вы можете сделать это очень легко, как в примере ниже.

Во-первых, сделайте SealedClass, чтобы избежать опечатки в навигации.

sealed class Screens(val route: String) {
    object Login : Screens("login")
    object Register : Screens("register")
}

Тогда вот полный пример, которому вы можете следовать.

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.cb.jetpackcomposenestednavigation.ui.theme.JetpackComposeNestedNavigationTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            JetpackComposeNestedNavigationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    NestedNavigationExample()
                }
            }
        }
    }
}

@Composable
fun NestedNavigationExample() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = Screens.Login.route
    ) {
        composable(route = Screens.Login.route) {
            LoginScreen {
                navController.navigate(it)
            }
        }
        composable(route = Screens.Register.route) {
            RegisterScreen {
                navController.navigate(it)
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(navigateTo: (route: String) -> Unit) {
    Scaffold(topBar = {
        TopAppBar(
            title = { Text(text = "Login") },
            colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
        )
    }) {
        Column(
            Modifier
                .padding(it)
                .fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Button(onClick = {
                navigateTo(Screens.Register.route)
            }) {
                Text(text = "Login")
            }
        }
    }
}


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RegisterScreen(navigateTo: (route: String) -> Unit) {
    Scaffold(topBar = {
        TopAppBar(
            title = { Text(text = "Register") },
            colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary)
        )
    }) {
        Box(
            Modifier
                .padding(it)
                .fillMaxSize(), contentAlignment = Alignment.Center
        ) {
            
        }
    }
}

Удачного сочинения!! :)

как я могу переместить navigateTo(Screens.Register.route) в ViewModel. Я хочу перейти после некоторых проверок из модели представления

Gayantha 21.08.2023 07:27

О, дорогой, перемещение его в ViewModel - не лучшая практика, здесь я привел пример лучших практик навигации.

Chirag Thummar 21.08.2023 08:26

и почему вы хотите переместить его в viewModel

Chirag Thummar 21.08.2023 08:26

Я хочу сделать навигацию после вызова некоторой функции в модели представления

Gayantha 21.08.2023 12:27

Я привел пример передовой практики. Кстати, выбирать вам.

Chirag Thummar 21.08.2023 18:53

Лучше всего было бы добавить в viewModel переменную с именем navigationToChatScreen, а затем, когда эта переменная имеет значение true, вы можете перемещаться по компоновке следующим образом:

    class HomeViewModel : ViewModel(){
    private var _uiState = MutableStateFlow(HomeData())
    var uiState : StateFlow<HomeData> = _uiState.asStateFlow()

private var _navigateToChatScreen = MutableStateFlow(false)
    var navigateToChatScreen : StateFlow<Boolean> = _navigateToChatScreen.asStateFlow()

    public fun ChangeName() {
        _uiState.value = HomeData(UserName = "hello there")
        _navigateToChatScreen.value = true
    }
}

Затем вы делаете это так в своем составном:

    @Composable
fun HomeScreen(
    homeViewModel: HomeViewModel = viewModel(),
    navController: NavController
){
    val HomeViewState by homeViewModel.uiState.collectAsState()

    val navigateToChatScreen by homeViewModel.navigateToChatScreen.collectAsState()

Launchedeffect(key= navigateToChatScreen){
  If(navigateToChatScreen){
     navController.navigate("chat")
  }
}
    Scaffold (
        topBar = {
            TopAppBar(title = {
                Text(text = "Chat Wallet", style = MaterialTheme.typography.titleMedium)
            }, colors = TopAppBarDefaults.mediumTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.primary,
                titleContentColor = MaterialTheme.colorScheme.background
            ))
        }
    ){
        paddingValues -> Box(modifier = Modifier.padding(paddingValues).fillMaxSize(), contentAlignment = Alignment.Center) {
            IconButton(onClick = { homeViewModel.changeName()}, colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.primary)) {
                Icon(painter = painterResource(id = R.drawable.baseline_chat_24), contentDescription = null, tint = MaterialTheme.colorScheme.background)
            }

        }
    }
}

Другие вопросы по теме