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