Jetpack Compose - Issues by passing down States - kotlin

I am new to Kotlin and jetpack compose. So while I get startig, I got my first problem and I can't solve it on my own.
The Problem appears, when I started to put the States up in the MainContent-function and pass the States as Arguments to TipCalcSurface.
Now I got the following problems:
Type mismatch: inferred type is Int but MutableState<Int> was expected
The other problems are consequential problems of the wrong Type, but why?
If I change the type to an Int, the whole function doesn't work.
Below my practiceing code.
package com.example.customtippcalc
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.KeyboardArrowDown
import androidx.compose.material.icons.sharp.KeyboardArrowUp
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.customtippcalc.ui.theme.CustomTippCalcTheme
import com.example.customtippcalc.util.calculateTipPrice
import com.example.customtippcalc.util.calculateTotalPricePerPerson
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CustomTippCalcTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainContent()
}
}
}
}
}
#Composable
fun MainContent() {
var totalPricePerPerson by remember { mutableStateOf(0.0)}
var personCount by remember { mutableStateOf(1)} // Problem starts here
Column() {
TipCalcHeader(totalPrice = totalPricePerPerson)
TipCalcSurface(
personCount = personCount,
onPriceCalculate = { totalPricePerPerson = it }
)
}
}
#Composable
fun TipCalcHeader(totalPrice: Double = 0.0) {
Column {
Text(text = "Price per person:")
Text(
modifier = Modifier.fillMaxWidth().padding(top = 28.dp, bottom = 28.dp),
textAlign = TextAlign.Center,
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
text = "$totalPrice €"
)
}
}
#Preview(showBackground = true)
#Composable
fun TipCalcSurface(
personCount: MutableState<Int>, // And problem here
onPriceCalculate: (Double) -> Unit = {}
) {
var totalPrice by remember { mutableStateOf(0.0) }
var invoiceAmount by remember { mutableStateOf("")}
var totalTip by remember { mutableStateOf(0.0)}
var sliderAmount by remember { mutableStateOf(0f) }
var sliderAmountAtPercent by remember { mutableStateOf(0)}
Column(modifier = Modifier
.fillMaxWidth()
.padding(13.dp)) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = invoiceAmount,
onValueChange = {invoiceAmount = it},
label = { Text( text = "Totalprice") }
)
Spacer(modifier = Modifier.height(20.dp))
// Person Count Row
Row(modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text( text = "Person:")
Row(verticalAlignment = Alignment.CenterVertically) {
Button(onClick = {
personCount ++
totalPrice = calculateTotalPricePerPerson(
personAmount = personCount,
invoiceAmount = invoiceAmount.toDouble(),
tipAmount = totalTip
)
onPriceCalculate(totalPrice)
} ) {
Icon(imageVector = Icons.Sharp.KeyboardArrowUp, contentDescription = "+")
}
Text( modifier = Modifier
.padding(start = 13.dp, end = 13.dp),
text = "$personCount")
Button(
onClick = {
if (personCount > 1) personCount --
totalPrice = calculateTotalPricePerPerson(
personAmount = personCount,
invoiceAmount = invoiceAmount.toDouble(),
tipAmount = totalTip
)
onPriceCalculate(totalPrice)
}) {
Icon(imageVector = Icons.Sharp.KeyboardArrowDown, contentDescription = "-")
}
}
}
Spacer(modifier = Modifier.height(20.dp))
// Tip Amount Row
Row(modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween) {
Text( text = "Tip in €:")
Text( text = "$totalTip €")
}
Spacer(modifier= Modifier.height(20.dp))
// Tip Percentage Slider
Row(modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text( text = "Tip in %:")
Text( text = "$sliderAmountAtPercent")
}
Slider(
value = sliderAmount,
onValueChange = {
sliderAmount = it //(it * 100).toInt()
sliderAmountAtPercent = (sliderAmount*100).toInt()
totalTip = calculateTipPrice(
totalPrice = invoiceAmount.toDouble(), // Fehler bei kovertierung zu Double wenn Wert leer
tipAtPercent = sliderAmountAtPercent)
totalPrice = calculateTotalPricePerPerson(
personAmount = personCount,
invoiceAmount = invoiceAmount.toDouble(),
tipAmount = totalTip
)
onPriceCalculate(totalPrice)
})
}
}
Thanks for help!

When you do var personCount by remember { mutableStateOf(1)}, then the type of personCount is Int. You are not assigning the MutableState<Int> to personCount, but delegating to it.
In order to make it work, you should pass the personCount as an Int to TipCalcSurface, as well as adding a updatePersonCount: (Int) -> Unit function to it. You can then call that function when you want to update personCount.
Like this:
fun TipCalcSurface(
personCount: Int,
updatePersonCount: (Int) -> Unit = {},
onPriceCalculate: (Double) -> Unit = {}
) {
// Fix the code mutating personCount to instead call updatePersonCount.
}
#Composable
fun MainContent() {
var totalPricePerPerson by remember { mutableStateOf(0.0) }
var personCount by remember { mutableStateOf(1) }
Column() {
TipCalcHeader(totalPrice = totalPricePerPerson)
TipCalcSurface(
personCount = personCount,
updatePersonCount = { personCount = it }
onPriceCalculate = { totalPricePerPerson = it }
)
}
}

Related

How to load bitmap Images in a List faster, Using jetpack compose Android

I am trying to load a list of object in a column using jetpack compose.
The problem is the image is making screen lag.
Here's my code - single list item composable function.
fun SoulProfilePreviewSingleItem(
soul: Souls,
soulWinningViewModel: SoulWinningViewModel,
onClick: () -> Unit
) {
val lazyListState = rememberLazyListState()
/* val localImage = if (soul.localImage.isNotBlank())
rememberImagePainter(getBase64StringToImage(soul.localImage)) else null*/
LaunchedEffect(key1 = soulWinningViewModel.nurtureVsFollowUpTabIndex.value) {
lazyListState.animateScrollToItem(0)
// if (soul.localImage.isNotBlank()){
// }
}
val isManaging = soulWinningViewModel.isManagingList.value
var isDeleted by remember {
mutableStateOf(false)
}
val setVisibility: (Boolean) -> Unit = {
isDeleted = it
}
AnimatedVisibility(
visible = isDeleted.not() &&
if (soulWinningViewModel.userIsSearchingFor.value.isNotEmpty() && isManaging.not())
soul.name.contains(soulWinningViewModel.userIsSearchingFor.value, true) ||
soul.followUpActions.toString()
.contains(soulWinningViewModel.userIsSearchingFor.value, true) else
true
) {
with(soul) {
Column {
Box(
modifier = Modifier
.height(60.dp)
.background(MaterialTheme.colorScheme.background)
.fillMaxWidth(),
contentAlignment = Alignment.BottomEnd
) {
Row(
Modifier
.fillMaxSize(),//.clickable { onClick() },
verticalAlignment = Alignment.CenterVertically
) {
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier
.padding(start = brc_DP_small)
.padding(bottom = 6.dp, top = 6.dp)
) {
/*if (soul.localImage.isNotBlank()) {
GlideImage(
model = ,
// Crop, Fit, Inside, FillHeight, FillWidth, None
contentScale = ContentScale.Crop,
// shows an image with a circular revealed animation.
)
Log.e("BITMAP","onResourceReady test")
val image = loadPicture(url = soul.localImage, defaultImage = R.drawable.ic_brc_logo).value
image?.let { img->
Image(
// Can crash this
bitmap =img.asImageBitmap(),
contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.size(30.dp),
contentScale = ContentScale.Crop
)
}
}*/
if (soul.localImage.isNotBlank()) {
// val imageLoader = rememberImagePainter(getBase64StringToImage(soul.localImage))
Image(
// painter = imageLoader
// Can crash this
bitmap = (
getBase64StringToImage(soul.localImage).asImageBitmap()
),
contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.size(30.dp),
contentScale = ContentScale.Crop
)
} else {
Image(
// Can crash this
painterResource(id = R.drawable.ic_brc_logo),
contentDescription = null,
modifier = Modifier
.clip(RoundedCornerShape(50))
.size(30.dp),
contentScale = ContentScale.Crop
)
}
}
Column(
modifier = Modifier
.padding(top = brc_DP_small)
.weight(1f)
.fillMaxWidth()
.fillMaxHeight()
.padding(start = brc_DP_smaller),
verticalArrangement = Arrangement.SpaceBetween
) {
Column(
modifier = Modifier
.weight(1f)
.fillMaxSize()
) {
// This is the place where signle soul is getting showed......
Text(
text = name,
style = if (isManaging.not()) MaterialTheme.typography.labelSmall.copy(
fontWeight = FontWeight(500),
letterSpacing = 0.7.sp
)
else MaterialTheme.typography.labelSmall
.copy(
fontSize = 10.sp
),
modifier = Modifier,
maxLines = 1,
)
if (isManaging.not()) {
Text(
text = SimpleDateFormat(
"dd-MMM-yyyy",
Locale.UK
).format(
createdOn
),
style = MaterialTheme.typography.bodySmall.copy(fontSize = 9.sp),
color = Color.Gray,
modifier = Modifier
)
}
}
if (isBrcMember) {
Text(
text = "BRC Member",
style = MaterialTheme.typography.labelSmall.copy(
fontSize = 7.sp,
fontStyle = FontStyle.Italic
),
color = brc_blue_color,
modifier = Modifier.padding(bottom = brc_DP_smallest)
)
}
}
}
var externClick by remember {
mutableStateOf(false)
}
Column(modifier = Modifier.padding(brc_DP_small)) {
AddSoulFollowUpActionButton(
soul = soul,
soulWinningViewModel = soulWinningViewModel,
visible = soulWinningViewModel.nurtureVsFollowUpTabIndex.value == 1,
externClick = externClick,
onFinished = { externClick = false }
) {
setVisibility(it)
}
}
//TODO:Check Click area ...
Row(modifier = Modifier.fillMaxSize()) {
Spacer(
modifier = Modifier
.fillMaxSize()
.weight(1.4f)
.clickable(
interactionSource = MutableInteractionSource(),
indication = null
) { onClick() })
if (soulWinningViewModel.nurtureVsFollowUpTabIndex.value == 1 || isManaging) {
Spacer(
modifier = Modifier
.fillMaxSize()
.weight(0.6f)
.clickable(
interactionSource = MutableInteractionSource(),
indication = null
) { externClick = !externClick })
}
}
}
Spacer(
modifier = Modifier
.padding(start = 28.dp)
.fillMaxWidth() //.then(if (isManaging.not()) Modifier.width(150.dp) else Modifier.fillMaxWidth())
.height(0.3.dp)
.background(Color.LightGray)
)
}
}
}
}
caller of list composable function.
forEach {
SoulProfilePreviewSingleItem(
soul = it,
soulWinningViewModel
) {
soulWinningViewModel.currentSoul.value = it
navController.navigate(route = SoulWinningRoutes.PROFILE)
}
}
Whenever I am trying to show Image() of my soul object I am getting glitches and my screens takes more time to load itself.
I am storing image as string in local room database and trying to retrieve it back converting it to bitmapImage.
Code for conversation.
fun getBase64StringToImage(base64String: String): Bitmap {
val decodedString: ByteArray = Base64.decode(base64String, Base64.DEFAULT)
val decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
return decodedByte
// this is taking a lot of time and making my Ui performance dull.
}
Is there any way I can achieve this functionality with good performance. Like loading Image in separate scope or anything else.
I have already tried using rememberImagePainter and Glide Coil. rememberImagePainter making my performance worst.
Please feel free to give me suggestions, I am still in learning stage.
Thanks in advance.

Kotlin - Calculate Age according to a choice from Drop Down Menu

I'm starting to learn Kotlin and trying to make a simple project that calculate your age in (Years, Months, Weeks, Days) which is choices in a drop down menu
and you select the date of birth from datePickerDialog
both the drop down menu and the date dialog are working fine
but I can't find the way to get the difference between the current date and the date of birth!
API: 26
Sample of the Project
package com.example.agecalculator
import android.annotation.SuppressLint
import android.app.DatePickerDialog
import android.os.Bundle
import android.widget.DatePicker
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.agecalculator.ui.theme.AgeCalculatorTheme
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AgeCalculatorTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainUI(this)
}
}
}
}
}
#Composable
fun MainUI(context: MainActivity) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(color = colorResource(id = R.color.MinaBackground)),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Calculate Your Age",
fontSize = 24.sp,
color = colorResource(id = R.color.MinaFont),
modifier = Modifier
.align(Alignment.Start)
.padding(16.dp, 8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
MyUI(context = context)
Spacer(modifier = Modifier.height(8.dp))
}
}
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun MyUI(context: MainActivity) {
val contextForToast = LocalContext.current.applicationContext
val listItems = arrayOf("Years", "Months", "Weeks", "Days")
var selectedItem by remember {
mutableStateOf(listItems[0])
}
var expanded by remember {
mutableStateOf(false)
}
// the box
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
// text field
TextField(
value = selectedItem,
onValueChange = {},
readOnly = true,
label = { Text(text = "in", color = colorResource(id = R.color.MinaFont)) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors(
textColor = colorResource(id = R.color.MinaFont),
backgroundColor = colorResource(id = R.color.MinaForeground),
trailingIconColor = colorResource(id = R.color.MinaFont),
)
)
// menu
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
Modifier.background(colorResource(id = R.color.MinaForeground))
) {
listItems.forEach { selectedOption ->
// menu item
DropdownMenuItem(onClick = {
selectedItem = selectedOption
Toast.makeText(contextForToast, selectedOption, Toast.LENGTH_SHORT).show()
expanded = false
}) {
Text(text = selectedOption, color = colorResource(id = R.color.MinaFont))
}
}
}
}
MyCal(context = context, dropDownSelection = selectedItem)
}
#SuppressLint("WeekBasedYear")
#Composable
fun MyCal(context: MainActivity, dropDownSelection: String) {
val calendar = Calendar.getInstance()
val year = calendar.get(Calendar.YEAR)
val month = calendar.get(Calendar.MONTH)
val day = calendar.get(Calendar.DAY_OF_MONTH)
calendar.time = Date()
val formatter = DateTimeFormatter.ofPattern("dd/MM/YYYY")
val current = LocalDate.now().format(formatter)
val date = remember { mutableStateOf("") }
val datePickerDialog = DatePickerDialog(
context,
{ _: DatePicker, year: Int, month: Int, dayOfMonth: Int ->
date.value = "$dayOfMonth/${month + 1}/$year"
},
year,
month,
day
)
Button(onClick = { datePickerDialog.show() }) {
if (date.value == "") {
Text(text = current)
} else Text(text = date.value)
}
}

Jetpack compose scrollable table

I need to have a component that scrolls vertically and horizontally.
Here is what I did so far:
#Composable
fun Screen() {
val scope = rememberCoroutineScope()
val scrollOffset = remember {
mutableStateOf(value = 0F)
}
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
) {
items(10) {
SimpleRow(
onScrollOffsetChange = {
scrollOffset.value = it
}
)
}
}
}
#Composable
private fun SimpleRow(
onScrollOffsetChange: (Float) -> Unit,
) {
val lazyRowState = rememberLazyListState()
LazyRow(
modifier = Modifier.fillMaxWidth(),
state = lazyRowState
) {
item {
Text(
text = "firstText"
)
}
for (i in 1..30) {
item {
Text(text = "qwerty")
}
}
}
}
When anyone of these SimpleRow is scrolled I want to scroll all of the SimpleRows together.
And I do not know how many SimpleRows I have because they came from our server.
Is there any kind of composable or trick that can do it for me?
You can use multiple rows inside a column that scrolls horizontally and vertically, like this:
#Composable
fun MainContent() {
val scrollStateHorizontal = rememberScrollState()
val scrollStateVertical = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(state = scrollStateVertical)
.horizontalScroll(state = scrollStateHorizontal)
) {
repeat(40){ c ->
Row {
repeat(40){ r ->
Item("col: $c | row: $r")
}
}
}
}
}
#Composable
fun Item(text: String) {
Text(
modifier = Modifier
.padding(5.dp)
.background(Color.Gray),
text = text,
color = Color.White
)
}

Why doesnt the index plus one on the first click of the Iconbutton

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].amountOfDays,
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
)
}
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

Integrating Braintree dropin UI into Kotlin Jetpack Compose

I'm trying to convert the android/java dropin UI code from here https://developer.paypal.com/braintree/docs/guides/drop-in/setup-and-integration#starting-drop-in into a jetpack compose app. So far I have
#Composable
fun Account(user: FinUser) {
val context = LocalContext.current
val customerToken = user.userData["customerToken"] as String
val dropInRequest = DropInRequest()
.clientToken(customerToken)
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult()
) {
print("pause here")
}
val dropInIntent = dropInRequest.getIntent(context)
val dropInPendingIntent = PendingIntent.getBroadcast(
context, 200, dropInIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
Column(){
Column(
Modifier
.padding(top = 0.dp)
.clickable { launchDropInUi(
dropInHintLauncher=dropInHintLauncher,
dropInPendingIntent=dropInPendingIntent) }) {
Divider(color = Color.LightGray, thickness = 1.dp)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp, 10.dp)
) {
Column() {
Text("Payment", color = Color.Gray)
Text("*********9999", color = Color.Black)
}
Spacer(modifier = Modifier.fillMaxWidth())
}
Divider(color = Color.LightGray, thickness = 1.dp)
}
}
}
fun launchDropInUi(dropInHintLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>, dropInPendingIntent: PendingIntent){
dropInHintLauncher.launch(
IntentSenderRequest.Builder(dropInPendingIntent).build()
)
}
When I click on my row there's no dropin UI popup but it does register the click and runs over the launchDropInUi function.
I found the issue. I needed to use
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
)
Giving
#Composable
fun Account(user: FinUser) {
val context = LocalContext.current
val customerToken = user.userData["customerToken"] as String
val dropInRequest = DropInRequest()
.clientToken(customerToken)
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
){ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
// you will get result here in result.data
val data: DropInResult? = result.data?.getParcelableExtra(DropInResult.EXTRA_DROP_IN_RESULT)
}else{
print("throw error popup")
}
}
val dropInIntent = dropInRequest.getIntent(context)
Column(){
Column(
Modifier
.padding(top = 0.dp)
.clickable { launchDropInUi(
dropInHintLauncher =dropInHintLauncher,
dropInIntent =dropInIntent) }) {
Divider(color = Color.LightGray, thickness = 1.dp)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp, 10.dp)
) {
Column() {
Text("Payment", color = Color.Gray)
Text("*********9999", color = Color.Black)
}
Spacer(modifier = Modifier.fillMaxWidth())
}
Divider(color = Color.LightGray, thickness = 1.dp)
}
}
}
fun launchDropInUi(dropInHintLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>, dropInIntent: Intent){
dropInHintLauncher.launch(dropInIntent)
}