Search code examples
androidkotlinandroid-jetpack-composeandroid-viewmodelmutablestateof

Boolean State in compose is changing before the variable I put after it get's assigned


So I have Two ViewModels in my Calculator App in which I all reference in my Compose NavGraph so I can use the same ViewModel instance. I set a Boolean State(historyCheck) in the first ViewModel and I set it too true to "Clear" the History I set which is a history of Calculations I am trying to retrieve from both ViewModels. The issue now is that the Boolean State "strCalcViewModel.historyCheck" changes before the variable above it get's assigned which then makes the 'if' statement I setup fail which in turns makes the whole Implementation also fail as it is always set to false.

This is my code Below... My Compose NavGraph.

@Composable
fun ComposeNavigation(
    navController: NavHostController,
) {
    /**
     *  Here We declare an Instance of our Two ViewModels, their states and History States. This is because we don't want to have the same States for the two Screens.
     */
    val strCalcViewModel = viewModel<CalculatorViewModel>()
    val sciCalcViewModel = viewModel<ScientificCalculatorViewModel>()

    val strCalcState = strCalcViewModel.strState
    val sciCalcState = sciCalcViewModel.sciState

    val strHistoryState = strCalcViewModel.historyState
    val sciHistoryState = sciCalcViewModel.historyState

    // This holds our current available 'HistoryState' based on where the Calculation was performed(Screens) by the USER.
    var currHistory by remember { mutableStateOf(CalculatorHistoryState()) }
    if(strCalcViewModel.historyCheck) {

        currHistory = strHistoryState 
        strCalcViewModel.historyCheck = false // this gets assigned before the 'currHistory' variable above thereBy making the the if to always be false

    } else {
        currHistory = sciHistoryState
    }

    NavHost(
        navController = navController,
        startDestination = "main_screen",
    ) {
    
        composable("main_screen") {
            MainScreen(
                navController = navController, state = strCalcState, viewModel = strCalcViewModel
            )
        }

        composable("first_screen") {
            FirstScreen(
                navController = navController, state = sciCalcState, viewModel = sciCalcViewModel
            )
        }

        composable("second_screen") {
            SecondScreen(
               navController = navController, historyState =  currHistory, viewModel = strCalcViewModel
            )
       }
   }
}

Then my ViewModel

private const val TAG = "CalculatorViewModel"

class CalculatorViewModel : ViewModel() {

    var strState by mutableStateOf(CalculatorState())
        // This makes our state accessible by outside classes but still readable
        private set

    var historyState by mutableStateOf(CalculatorHistoryState())
        private set

    private var leftBracket by mutableStateOf(true)
    private var check = 0

    var checkState by mutableStateOf(false)

    var historyCheck by mutableStateOf(false)

    // Function to Register our Click events
    fun onAction(action : CalculatorAction) {
        when(action) {
            is CalculatorAction.Number -> enterNumber(action.number)
            is CalculatorAction.Decimal -> enterDecimal()
            is CalculatorAction.Clear -> {
                strState = CalculatorState()
                check = 0
            }
            is CalculatorAction.ClearHistory -> checkState = true
            is CalculatorAction.Operation -> enterStandardOperations(action.operation)
            is CalculatorAction.Calculate -> performStandardCalculations()
            is CalculatorAction.Delete -> performDeletion()
            is CalculatorAction.Brackets -> enterBrackets()
        }
    }

    // We are Basically making the click events possible by modifying the 'state'
    private fun performStandardCalculations() {
        val primaryStateChar = strState.primaryTextState.last()
        val primaryState = strState.primaryTextState
        val secondaryState = strState.secondaryTextState

        if (!(primaryStateChar == '(' || primaryStateChar == '%')) {

            strState = strState.copy(
                primaryTextState = secondaryState
            )
           strState = strState.copy(secondaryTextState = "")

            // Below, we store our Calculated Values in the History Screen after it has been Calculated by the USER.
            historyState = historyState.copy(
                historySecondaryState = secondaryState
            )

            historyState = historyState.copy(
                historyPrimaryState = primaryState
            )

            historyCheck = true // this is where I assign it to true when I complete my Calculations and pass it to the history State
        } else {
            strState = strState.copy(
                secondaryTextState = "Format error"
            )

            strState = strState.copy(
                color = ferrari
            )
        }

    }
}

Solution

  • Based on Sadegh.t's Answer I got it working but didn't write it the exact same way and used a different Implementation which I will post now.

    I Still used a side-effect but instead of checking for a change in the "historyCheck", I checked for a change in the 'State' itself and also instead of using a Boolean variable, I used the State itself for the basis of the Condition. So here is my answer based on Sadegh.t's original answer.

    var currHistory by remember { mutableStateOf(CalculatorHistoryState()) }
    LaunchedEffect(key1 = strCalcState) {
        if(strCalcState.secondaryTextState.isEmpty()) {
            currHistory = strHistoryState
        }
    }
    
    LaunchedEffect(key1 = sciCalcState) {
        if(sciCalcState.secondaryTextState.isEmpty()) {
            currHistory = sciHistoryState
        }
    }