Reputation: 7
I want to go to another screen only when the api call is finished. I'm using the below code and it works fine in splash screen (i can see the api call happening in log cat) but when i navigate and try to collect the flow in that screen nothing is showing (like the bottomsheets based on result) what is the problem here ? is there a way to achieve this? i would appreciate any help and solution.
my viewmodel:
@HiltViewModel
class MainViewModel @Inject constructor(
private val mainRepo: MainRepository,
@ApplicationContext private val context:Context
) : ViewModel() {
val statusFlow = MutableStateFlow<NetworkResult<Status>>(NetworkResult.Loading())
var status by mutableStateOf(Status())
fun getStatus(){
viewModelScope.launch {
statusFlow.emit(mainRepo.getStatus())
}
}
}
... rest of the viewmodel
splash screen:
@Composable
fun SplashScreen(navController: NavController,mainViewModel: MainViewModel = hiltViewModel()) {
Splash()
LaunchedEffect(true) {
getAllApiCalls(mainViewModel)
mainViewModel.statusFlow.collectLatest {
when(it){
is NetworkResult.Success -> {
navController.navigate(Screens.MainScreen.route) {
popUpTo(Screens.SplashScreen.route) {
inclusive = true
}
}
}
is NetworkResult.Loading -> {...}
is NetworkResult.Error -> {...}
}
}
}
}
private fun getAllApiCalls(mainViewModel: MainViewModel) {
mainViewModel.getStatus()
}
my main screen:
@Composable
fun MainScreen(
navController: NavController,
mainViewModel: MainViewModel = hiltViewModel(),
) {
Main(mainViewModel = mainViewModel)
}
@SuppressLint("CoroutineCreationDuringComposition")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Main(mainViewModel: MainViewModel) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val sheetState = rememberBottomSheetScaffoldState(
bottomSheetState = SheetState(
initialValue = SheetValue.Hidden,
skipPartiallyExpanded = false,
)
)
val drawerState = rememberDrawerState(
initialValue = DrawerValue.Closed
)
val statusResult by mainViewModel.statusFlow.collectAsState()
Log.e("TAG",statusResult.toString())
when (statusResult) {
is NetworkResult.Success -> {
mainViewModel.status = statusResult.data ?: Status()
Log.e("TAG"," hi ${mainViewModel.status.url}")
scope.launch {
if (mainViewModel.status.minVersion > VersionHelper.getVersionCode(context)!!) {
if (!sheetState.bottomSheetState.isVisible) {
drawerState.close()
mainViewModel.bottomSheetContents.value = BottomSheetContents.UPDATE
sheetState.bottomSheetState.expand()
}
return@launch
}
if (!mainViewModel.status.functional) {
if (!sheetState.bottomSheetState.isVisible) {
drawerState.close()
mainViewModel.bottomSheetContents.value =
BottomSheetContents.UNDER_CONSTRUCTION
sheetState.bottomSheetState.expand()
}
return@launch
}
if (!mainViewModel.status.reject) {
if (!sheetState.bottomSheetState.isVisible) {
drawerState.close()
mainViewModel.bottomSheetContents.value = BottomSheetContents.DISABLED_USER
sheetState.bottomSheetState.expand()
}
return@launch
}
}
}
is NetworkResult.Error -> {
Log.e("TAG", "error api statusResult : ${statusResult.message}")
LaunchedEffect(Unit) {
if (!sheetState.bottomSheetState.isVisible) {
drawerState.close()
mainViewModel.bottomSheetContents.value =
BottomSheetContents.UNDER_CONSTRUCTION
sheetState.bottomSheetState.expand()
}
}
}
is NetworkResult.Loading -> {}
}
... rest of the code
If i call the getAllApiCalls()
function in the mainscreen the problem goes away but in this way i call the apis 2 times and that would defeat the whole purpose of calling in in the SplashScreen()
Upvotes: 0
Views: 102
Reputation: 10857
The problem here lies in how hiltViewModel
scopes a ViewModel. By default, it will be scoped to the current NavGraphEntry
. That means if your NavHost
navigates to a new destination, the new destination will create its own ViewModel instance.
In your case, the SplashScreen
holds the ViewModel instance with the updated statusFlow
, but then your MainScreen
receives a completely new ViewModel instance with initial state.
You can however obtain the ViewModel from a certain NavGraphEntry
and pass the ViewModel into each Composable in your NavHost
, as described in this stackoverflow answer:
@Composable
fun MyApp() {
NavHost(navController, startDestination = startRoute) {
// ...
composable("exampleWithRoute") { backStackEntry ->
val parentEntry = remember {
navController.getBackStackEntry(Screens.SplashScreen.route) // get ViewModel instance from SplashScreen
}
val mainViewModel = hiltViewModel<MainViewModel>(
parentEntry
)
MainScreen(
navController,
mainViewModel
)
}
}
}
To make that work, you however will have to remove the
popUpTo(Screens.SplashScreen.route) {
nclusive = true
}
otherwise your ViewModel is very likely going to be lost.
Alternatively, you could consider to call hiltViewModel
in the very root Composable of your app and then pass the instance to the NavHost
and then further to each Composable.
Please let me know if the approaches described here worked in your case!
Upvotes: 0