Home > Enterprise >  Why doesnt the index plus one on the first click of the Iconbutton
Why doesnt the index plus one on the first click of the Iconbutton

Time:04-17

So i have made a calendar. I am now trying to make two arrow buttons jump through the months. The only problem is that everytime i click the Iconbutton the doesnt do anything the first time but does something on the second... why is this can someone please help

package com.jens.svensson.jenson_calendar

import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.*
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.hilt.navigation.compose.hiltViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarColors
import com.jens.svensson.jenson_calendar.ui.CalendarViewModel
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch


@Composable
fun Calendar(
    calendarViewModel: CalendarViewModel = hiltViewModel(),
    isRoundedCorners: Boolean = false,
    startMonth: Int,
    calendarColor: CalendarColors,
    textStyle: TextStyle,
    onDissmissDialog: () -> Unit,
    size: Configuration
){
    val calendarYears = calendarViewModel.calendarYears
    val showMenuState = calendarViewModel.dropDownMenuState.value.showDropDown
    var dropDownPickedState = calendarViewModel.dropDownMenuState.value.pickedItem
    val sizeHeight = size.screenHeightDp
    val sizeWidth = size.screenWidthDp
    val width = sizeWidth * 0.95
    val height = sizeHeight * 0.95
    val coroutineScope = rememberCoroutineScope()



    Dialog(onDismissRequest = onDissmissDialog, properties = DialogProperties(dismissOnClickOutside = true)) {
        Column(modifier = Modifier
            .size(width = width.dp, height = height.dp)
            .padding(15.dp)
            .background(
                calendarColor.calendarColor,
                shape = if (isRoundedCorners) RoundedCornerShape(10) else RectangleShape
            )) {


            Header(isRoundedCorners = isRoundedCorners, color = calendarColor.headerColor)
            Row(
                modifier = Modifier
                    .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Box(
                    modifier = Modifier
                        .align(Alignment.CenterVertically)
                        .padding(start = 22.dp)
                ) {
                    Row(Modifier.clickable { calendarViewModel.onEvent(CalendarEvent.ShowDropDown(!showMenuState)) }) {
                        Text(
                            text = calendarViewModel.standardMonths[calendarViewModel.dropDownMenuState.value.pickedItem],
                            style = MaterialTheme.typography.h6,
                            color = calendarColor.mainCalendarTextColor
                        )
                        Icon(
                            imageVector = Icons.Default.ArrowDropDown,
                            contentDescription = "Month dropdown arrow",
                            tint = calendarColor.mainCalendarTextColor
                        )
                    }
                    DropdownMenu(
                        expanded = showMenuState,
                        onDismissRequest = { calendarViewModel.onEvent(CalendarEvent.ShowDropDown(!showMenuState)) }) {
                        calendarViewModel.standardMonths.forEachIndexed { index, month ->
                            DropdownMenuItem(onClick = { calendarViewModel.onEvent(CalendarEvent.ClickedMenuItem(index))
                            }) {
                                Row() {
                                    Text(text = month, style = MaterialTheme.typography.h6)
                                }
                            }
                        }
                    }
                }
                IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {}) {
                    Icon(
                        imageVector = Icons.Default.ArrowBack,
                        contentDescription = "Go back one month arrow",
                        tint = calendarColor.mainCalendarTextColor
                    )
                }
                IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {
                    calendarViewModel.onEvent(CalendarEvent.ClickedMenuItem(dropDownPickedState  ))
                }) {
                    Icon(
                        imageVector = Icons.Default.ArrowForward,
                        contentDescription = "Go forward one month arrow",
                        tint = calendarColor.mainCalendarTextColor
                    )
                }


            }
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceEvenly
            ) {
                calendarViewModel.datesList.forEach {
                    Text(text = it, color = calendarColor.mainCalendarTextColor)
                }
            }
            LazyRow(state = calendarViewModel.listState, modifier = Modifier.fillMaxWidth()) {
                calendarYears.forEach {
                    items(it.months.count()) { index ->
                        CalendarRowItem(
                            modifier = Modifier.fillParentMaxWidth(),
                            calendarSize = it.months[index].ammountOfDays,
                            initWeekday = it.months[index].startDayOfMonth.ordinal,
                            textColor = MaterialTheme.colors.secondaryVariant,
                            clickedColor = MaterialTheme.colors.primary,
                            textStyle = MaterialTheme.typography.body1
                        )
                    }
                }

            }



            DisposableEffect(Unit) {
                coroutineScope.launch {
                    calendarViewModel.listState.scrollToItem(calendarViewModel.currentMonth)


                }
                onDispose {  }
            }
            
            
            CalendarButtonSection()






        }
    }

}

calendarViewModel

package com.jens.svensson.jenson_calendar.ui


import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarMonth
import com.jens.svensson.jenson_calendar.data.model.CalendarYear
import com.jens.svensson.jenson_calendar.data.model.YearMonths
import com.jens.svensson.jenson_calendar.domain.repository.CalendarInterface
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import com.jens.svensson.jenson_calendar.ui.state.DropDownMenuState
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Month
import java.util.*
import javax.inject.Inject

@HiltViewModel
class CalendarViewModel @Inject constructor(private val repository: CalendarInterface): ViewModel() {
    val datesList: List<String> = repository.getShortenedWeekDays()
    val calendarYears: List<CalendarYear> = repository.createAndReturnYears()
    val standardMonths: List<String> = repository.getStandardMonths()
    val listState = LazyListState()
    val currentMonth: Int = Calendar.getInstance().get(Calendar.MONTH)



    private val _dropdownMenuState = mutableStateOf(DropDownMenuState())
    val dropDownMenuState: State<DropDownMenuState> = _dropdownMenuState

    init {
        _dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = currentMonth)
    }


    fun onEvent(event: CalendarEvent){
        when(event){
            is CalendarEvent.ShowDropDown -> _dropdownMenuState.value = dropDownMenuState.value.copy(showDropDown =  event.value)
            is CalendarEvent.ClickedMenuItem -> {
                _dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem = event.value)
            }

        }
    }







    fun getCalendarMonths(yearIndex: Int): List<CalendarMonth>{
        return calendarYears[yearIndex].months
    }
}

New viewmodel

package com.jens.svensson.jenson_calendar.ui


import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.jens.svensson.jenson_calendar.data.model.CalendarMonth
import com.jens.svensson.jenson_calendar.data.model.CalendarYear
import com.jens.svensson.jenson_calendar.data.model.YearMonths
import com.jens.svensson.jenson_calendar.domain.repository.CalendarInterface
import com.jens.svensson.jenson_calendar.ui.events.CalendarEvent
import com.jens.svensson.jenson_calendar.ui.state.DropDownMenuState
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Month
import java.util.*
import javax.inject.Inject

@HiltViewModel
class CalendarViewModel @Inject constructor(private val repository: CalendarInterface): ViewModel() {
    val datesList: List<String> = repository.getShortenedWeekDays()
    val calendarYears: List<CalendarYear> = repository.createAndReturnYears()
    val standardMonths: List<String> = repository.getStandardMonths()
    val listState = LazyListState()
    val currentMonth: Int = Calendar.getInstance().get(Calendar.MONTH)
    private var datePickedState: Int = 0


    private val _dropdownMenuState = mutableStateOf(DropDownMenuState())
    val dropDownMenuState: State<DropDownMenuState> = _dropdownMenuState

    init {
        _dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = currentMonth)
    }


    fun onEvent(event: CalendarEvent){
        when(event){
            is CalendarEvent.ShowDropDown -> _dropdownMenuState.value = dropDownMenuState.value.copy(showDropDown =  event.value)
            is CalendarEvent.ClickedMenuItem -> {
                _dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem = event.value)
            }
            is CalendarEvent.NextMonth -> nextMonth()
            is CalendarEvent.PreviousMonth -> previousMonth()

        }
    }

    fun nextMonth(){
        datePickedState = _dropdownMenuState.value.pickedItem
        if(datePickedState == 11){
            datePickedState = 0
        }else{
            _dropdownMenuState.value = _dropdownMenuState.value.copy(pickedItem =   datePickedState)
        }


    }
    fun previousMonth(){
        datePickedState = _dropdownMenuState.value.pickedItem
        if(datePickedState == 0){
            datePickedState == 11
        }else{
            _dropdownMenuState.value = dropDownMenuState.value.copy(pickedItem = --datePickedState)
        }


    }





    fun getCalendarMonths(yearIndex: Int): List<CalendarMonth>{
        return calendarYears[yearIndex].months
    }
}

The buttons

IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {calendarViewModel.onEvent(CalendarEvent.PreviousMonth)}) {
    Icon(
        imageVector = Icons.Default.ArrowBack,
        contentDescription = "Go back one month arrow",
        tint = calendarColor.mainCalendarTextColor
    )
}
IconButton(modifier = Modifier.align(Alignment.CenterVertically), onClick = {
    calendarViewModel.onEvent(CalendarEvent.NextMonth)
}) {
    Icon(
        imageVector = Icons.Default.ArrowForward,
        contentDescription = "Go forward one month arrow",
        tint = calendarColor.mainCalendarTextColor
    )
}

CodePudding user response:

One problem is that i passes the current i value to the calculation before increasing, so you pass the same old value to onEvent. You can find more details in this answer - it's about C, but inc/dec operators work the same in all languages they exists. You could've used i, which will increase the value before using it in the calculations.

But here comes the second problem. This line:

var dropDownPickedState = calendarViewModel.dropDownMenuState.value.pickedItem

creates a local variable, and those are not saved between recompositions. So the first time you click, you're increasing the local value, but pass the old value to onEvent, which doesn't cause recomposition.

The second time you click - previously increased value gets passed to your view model, which triggers recomposition, and resets your dropDownPickedState.

To prevent such errors don't use var in Compose views, unless you're using it with state delegation, e.g.:

var item by viewModel.stateValue  // is ok
var item = viewModel.stateValue.value // is not ok
  • Related