How can i make this animation stop after completion in Jetpack Compose or not be infinite - kotlin

#Composable
fun Gojo(
maxWidth:Dp,
maxHeight:Dp,)
{
val resource: Painter
val modifier: Modifier
val gojoSize = 200.dp
val infiniteTransition = rememberInfiniteTransition()
val posistionState = infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = LinearEasing
),
repeatMode=RepeatMode.Reverse
)
)
if(posistionState.value<=0.1f) {
modifier = Modifier.offset(
x = -140.dp,
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else if(posistionState.value<=0.2f){
modifier = Modifier.offset(
x = -100.dp,
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else if(posistionState.value<=0.25f){
modifier = Modifier.offset(
x = -100.dp,
y = maxHeight * 0.75f,
).rotate(0.0F)
}
else if(posistionState.value<=0.3f){
modifier = Modifier.offset(
x = -100.dp+(maxWidth*posistionState.value),
y = maxHeight * 0.75f,
).rotate(50.0F)
}
else{
modifier = Modifier.offset(
x = maxWidth * 0.25f,
y = maxHeight * 0.75f,
)
}
resource=painterResource(R.drawable.gojo)
Image(modifier=modifier.width(gojoSize).height(gojoSize),painter = resource, contentDescription ="gojo sama")
}

you are asking for not to be infinite and using infinite transition? just use animateDpAsState and change size of the image by using the dp values. If you want to start the transition when the item has composed just call a LaunchedEffect (true) {startTransition = true} by remembering the startTransition value as false in the beginning:
var startTransition by remember {
mutableStateOf(false)
}
LaunchedEffect(true){
startTransition = true
}
val size = animateDpAsState(
targetValue = if(startTransition == false)
0.dp
else
/*Your Desired Value*/
)
// And use size in Image. You can create different animateDpAsState values for width and height.

Related

Compose onValueChange behaviour isn't consistent

I'm making a sudoku game and solver. For my user interface I used LazyVerticalGrid to create a 9x9 grid. I successfully made it so when you click on a cell it will only accept digits [1-9] via an OutLinedTextField. I then added a conditional that only empty cells would have the text field applied. That worked and only those cells could be altered but when I do that the logic that only accepts digits doesn't work and the program crashes. If I comment out the conditional statement and the OutLinedTextField is applied to all cells it works again. I get the following error.
Also if I add conditionals for backgroundColor or Content Color the same thing happens and the program crashes if a non digit is pressed. I'm not sure why the conditionals affect the onValueChange logic. Why is this and how do I fix it?
fun displayPuzzle(answer: Array<Array<IntArray>>) {
var list: SnapshotStateList<String> = mutableStateListOf()
for (x in answer[0]) list.addAll(x.map { it.toString() })
var columnHeighty by remember { mutableStateOf(0F) }
var columnWidthx by remember { mutableStateOf(0f) }
var pad = 20.dp
LazyVerticalGrid(
columns = GridCells.Fixed(9),
contentPadding = PaddingValues(
start = pad,
top = pad,
end = pad,
bottom = pad
)
) {
items(list.size) { index ->
Card(
shape = RectangleShape,
backgroundColor = Color.Red,
modifier = Modifier
.requiredWidth(83.dp)
.fillMaxWidth()
.fillMaxHeight()
.onGloballyPositioned { coordinates ->
columnWidthx = coordinates.size.width.toFloat()
columnHeighty = coordinates.size.height.toFloat()
},
//backgroundColor = if (list[index].toInt() == 0) Color.Yellow else Color.White ,
//contentColor = if (list[index].toInt() == 0) Color.Blue else Color.Black ,
border = BorderStroke(width = 1.dp, color = Color.Black)
) {
Text(
text = list[index],
fontWeight = FontWeight.Bold,
fontSize = 30.sp,
color = Color(0xFF000000),
textAlign = TextAlign.Center,
modifier = Modifier
.padding(23.dp)
.clickable { }
)
}
// When the if statement is included the program crashes on a non digit entry
//if (list[index].toInt() == 0) {
val pattern = remember { Regex("[1-9]") }
var value by remember { mutableStateOf("") }
OutlinedTextField(
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
colors = TextFieldDefaults.outlinedTextFieldColors(cursorColor = Color.Transparent),
textStyle = TextStyle(color = Color.Red),
modifier = Modifier
.fillMaxHeight()
.padding(vertical = 10.dp, horizontal = 10.dp),
value = value,
onValueChange = { if (it.isEmpty() || (it.matches(pattern) && (it.length == 1)))
value = it
list[index] = value}
)
//}
}
}
Your game crashed because you trying to convert for example 'a' to Int value and runtime throws NumberFormatException.
You need to use:
if (list[index].toIntOrNull() == null)
This condition will be triggered if a non-decimical number is obtained from your SnapshotStateList
Explanation: toIntOrNull() returns Int from String (example: "4".toIntOrNull() - returns 4) otherwise it returns null

Jetpack Compose interface is never updated with new api data

I'm making my first app with Kotlin and Jetpack Compose, I’m still studying. The app, It is a meteorological application that takes the data from an API. The MainScreen starts with the data of the city of Madrid by default, and has a searchBar where you can search for any other city. My problem is that when a new city is introduced in the searchbar, this name travels to the backend and makes the calls to the API, the API responds with the new data but these never come back to the interface. The MainScreen is never updated with new data, but I can see the data on my console.
Any help in welcome, thanks in advance
MainScreen
#RequiresApi(Build.VERSION_CODES.O)
#SuppressLint("UnusedMaterialScaffoldPaddingParameter", "SimpleDateFormat")
#Composable
fun MainScreen(
MainViewModel: MainViewModel
) {
val acceso = Accesso_API()
acceso.coger_tiempo_nombre("Madrid")
while (!acceso.datos_adquiridos) {}
Log.println(Log.ASSERT, "Inicio MainScreen", acceso.resultado.toString())
Surface(
color = MaterialTheme.colors.background,
modifier = Modifier
.fillMaxHeight()
.fillMaxSize()
) {
val searchWidgetState by MainViewModel.searchWidgetState
val searchTextState by MainViewModel.searchTextState
Scaffold(
modifier = Modifier
.fillMaxSize(),
topBar = {
MainAppBar(
searchWidgetState = searchWidgetState,
searchTextState = searchTextState,
onTextChange = {
MainViewModel.updateSearchTextState(newValue = it)
},
onCloseClicked = {
MainViewModel.updateSearchWidgetState(newValue = SearchWidgetState.CLOSED)
},
onSearchClicked = {
Log.d("Searched Text", it)
MainViewModel.searchTextState.value
Log.d("searchTextState", searchTextState)
acceso.coger_tiempo_nombre(searchTextState)
},
onSearchTriggered = {
MainViewModel.updateSearchWidgetState(newValue = SearchWidgetState.OPENED)
}
)
}
) {
Column(
modifier = Modifier
.background(Color.White)
.fillMaxWidth()
.padding(20.dp)
.verticalScroll(rememberScrollState())
) {
//TARJETA PRICIPAL
Card(
modifier = Modifier
.fillMaxWidth(),
backgroundColor = GreyCard,
elevation = 0.dp,
shape = RoundedCornerShape(10.dp)
) {
WeatherDisplayDailyCard(acceso.resultado)
}
//DETALLES
Row(
modifier = Modifier
.padding(
top = 5.dp,
bottom = 0.dp
)
.align(Alignment.CenterHorizontally),
horizontalArrangement = Arrangement.SpaceBetween,
) {
DetailsItem(
details = WeatherDetails(
units = "Pressure",
data = "${acceso.resultado.presion.toString()} mb",
R.drawable.pressure,
color = GreyCard
)
)
DetailsItem(
details = WeatherDetails(
units = "Wind",
data = "${acceso.resultado.viento.toString()} km/h",
R.drawable.wind2,
color = GreyCard
)
)
}
Row(
modifier = Modifier
.padding(
top = 0.dp,
bottom = 0.dp
)
.align(Alignment.CenterHorizontally),
horizontalArrangement = Arrangement.SpaceBetween,
) {
DetailsItem(
details = WeatherDetails(
units = "UV Index",
data = acceso.resultado.uv_text.toString(),
R.drawable.rays,
color = GreyCard
)
)
DetailsItem(
details = WeatherDetails(
units = "Humidity",
data = "${acceso.resultado.humedad_relativa.toString()} %",
R.drawable.water,
color = GreyCard
)
)
}
Spacer(modifier = Modifier.height(16.dp))
//12 HORAS
var hour_1 = acceso.resultado.hora_1;
var hour_2 = acceso.resultado.hora_2;
var hour_3 = acceso.resultado.hora_3;
var hour_4 = acceso.resultado.hora_4;
var hour_5 = acceso.resultado.hora_5;
var hour_6 = acceso.resultado.hora_6;
var hour_7 = acceso.resultado.hora_7;
var hour_8 = acceso.resultado.hora_8;
var hour_9 = acceso.resultado.hora_9;
var hour_10 = acceso.resultado.hora_10;
var hour_11 = acceso.resultado.hora_11;
var hour_12 = acceso.resultado.hora_12;
val formatterHour = DateTimeFormatter.ISO_DATE_TIME
val time1: LocalTime? = LocalTime.parse(hour_1, formatterHour)
val time2: LocalTime? = LocalTime.parse(hour_2, formatterHour)
val time3: LocalTime? = LocalTime.parse(hour_3, formatterHour)
val time4: LocalTime? = LocalTime.parse(hour_4, formatterHour)
val time5: LocalTime? = LocalTime.parse(hour_5, formatterHour)
val time6: LocalTime? = LocalTime.parse(hour_6, formatterHour)
val time7: LocalTime? = LocalTime.parse(hour_7, formatterHour)
val time8: LocalTime? = LocalTime.parse(hour_8, formatterHour)
val time9: LocalTime? = LocalTime.parse(hour_9, formatterHour)
val time10: LocalTime? = LocalTime.parse(hour_10, formatterHour)
val time11: LocalTime? = LocalTime.parse(hour_11, formatterHour)
val time12: LocalTime? = LocalTime.parse(hour_12, formatterHour)
HourlyForecastSheet(
hourlyForecast = listOf(
HourlyDetails(
hour = time1.toString(),
icon = acceso.resultado.icono_hora_1,
temperature = acceso.resultado.temperatura_hora_1
),
HourlyDetails(
hour = time2.toString(),
icon = acceso.resultado.icono_hora_2,
temperature = acceso.resultado.temperatura_hora_2
),
HourlyDetails(
hour = time3.toString(),
icon = acceso.resultado.icono_hora_3,
temperature = acceso.resultado.temperatura_hora_3
),
HourlyDetails(
hour = time4.toString(),
icon = acceso.resultado.icono_hora_4,
temperature = acceso.resultado.temperatura_hora_4
),
HourlyDetails(
hour = time5.toString(),
icon = acceso.resultado.icono_hora_5,
temperature = acceso.resultado.temperatura_hora_5
),
HourlyDetails(
hour = time6.toString(),
icon = acceso.resultado.icono_hora_6,
temperature = acceso.resultado.temperatura_hora_6
),
HourlyDetails(
hour = time7.toString(),
icon = acceso.resultado.icono_hora_7,
temperature = acceso.resultado.temperatura_hora_7
),
HourlyDetails(
hour = time8.toString(),
icon = acceso.resultado.icono_hora_8,
temperature = acceso.resultado.temperatura_hora_8
),
HourlyDetails(
hour = time9.toString(),
icon = acceso.resultado.icono_hora_9,
temperature = acceso.resultado.temperatura_hora_9
),
HourlyDetails(
hour = time10.toString(),
icon = acceso.resultado.icono_hora_10,
temperature = acceso.resultado.temperatura_hora_10
),
HourlyDetails(
hour = time11.toString(),
icon = acceso.resultado.icono_hora_11,
temperature = acceso.resultado.temperatura_hora_11
),
HourlyDetails(
hour = time12.toString(),
icon = acceso.resultado.icono_hora_12,
temperature = acceso.resultado.temperatura_hora_12
),
)
)
Spacer(modifier = Modifier.height(8.dp))
//5 DIAS
Column()
{
val date_1 = acceso.resultado.fecha_dia_1
val date_2 = acceso.resultado.fecha_dia_2
val date_3 = acceso.resultado.fecha_dia_3
val date_4 = acceso.resultado.fecha_dia_4
val date_5 = acceso.resultado.fecha_dia_5
val formatterDate = DateTimeFormatter.ISO_DATE_TIME
val day_1: OffsetDateTime = OffsetDateTime.parse(date_1, formatterDate)
val day_2: OffsetDateTime = OffsetDateTime.parse(date_2, formatterDate)
val day_3: OffsetDateTime = OffsetDateTime.parse(date_3, formatterDate)
val day_4: OffsetDateTime = OffsetDateTime.parse(date_4, formatterDate)
val day_5: OffsetDateTime = OffsetDateTime.parse(date_5, formatterDate)
val date1: DayOfWeek? = LocalDate.from(day_1).dayOfWeek
val date2: DayOfWeek? = LocalDate.from(day_2).dayOfWeek
val date3: DayOfWeek? = LocalDate.from(day_3).dayOfWeek
val date4: DayOfWeek? = LocalDate.from(day_4).dayOfWeek
val date5: DayOfWeek? = LocalDate.from(day_5).dayOfWeek
Next5daysForecastItem(
Next5daysForecast(
date_5days = date1.toString(),
icon_5days = acceso.resultado.icono_dia_1,
temperature_min_5days = acceso.resultado.Temp_min_dia_1,
temperature_max_5days = acceso.resultado.Temp_max_dia_1,
icon_frase_5days = acceso.resultado.frase_dia_1,
)
)
Next5daysForecastItem(
Next5daysForecast(
date_5days = date2.toString(),
icon_5days = acceso.resultado.icono_dia_2,
temperature_min_5days = acceso.resultado.Temp_min_dia_2,
temperature_max_5days = acceso.resultado.Temp_max_dia_2,
icon_frase_5days = acceso.resultado.frase_dia_2,
)
)
Next5daysForecastItem(
Next5daysForecast(
date_5days = date3.toString(),
icon_5days = acceso.resultado.icono_dia_3,
temperature_min_5days = acceso.resultado.Temp_min_dia_3,
temperature_max_5days = acceso.resultado.Temp_max_dia_3,
icon_frase_5days = acceso.resultado.frase_dia_3,
)
)
Next5daysForecastItem(
Next5daysForecast(
date_5days = date4.toString(),
icon_5days = acceso.resultado.icono_dia_4,
temperature_min_5days = acceso.resultado.Temp_min_dia_4,
temperature_max_5days = acceso.resultado.Temp_max_dia_4,
icon_frase_5days = acceso.resultado.frase_dia_4,
)
)
Next5daysForecastItem(
Next5daysForecast(
date_5days = date5.toString(),
icon_5days = acceso.resultado.icono_dia_5,
temperature_min_5days = acceso.resultado.Temp_min_dia_5,
temperature_max_5days = acceso.resultado.Temp_max_dia_5,
icon_frase_5days = acceso.resultado.frase_dia_5,
)
)
Log.println(Log.ASSERT, "Final MainScreen", acceso.resultado.toString())
}
}
}
}
}
MainViewModel
#RequiresApi(Build.VERSION_CODES.O)
class MainViewModel : ViewModel() {
private val _searchWidgetState: MutableState<SearchWidgetState> =
mutableStateOf(value = SearchWidgetState.CLOSED)
val searchWidgetState: State<SearchWidgetState> = _searchWidgetState
private val _searchTextState: MutableState<String> =
mutableStateOf(value = "")
val searchTextState: State<String> = _searchTextState
fun updateSearchWidgetState(newValue: SearchWidgetState) {
_searchWidgetState.value = newValue
}
fun updateSearchTextState(newValue: String) {
_searchTextState.value = newValue
}
}
API_Access
class Accesso_API {
val api_key ="87EYQdAb8jlViEELJJ6KsgJGfkcKG6qu"
#RequiresApi(Build.VERSION_CODES.O)
var resultado: Datos_Tiempo = Datos_Tiempo()
var datos_adquiridos: Boolean = false
#RequiresApi(Build.VERSION_CODES.O)
fun coger_tiempo_posicion_gps(longitud: Float, latitud: Float) {
coger_datos("", longitud,latitud,"")
}
#RequiresApi(Build.VERSION_CODES.O)
fun coger_tiempo_nombre(
nombre: String,
) {
coger_datos(nombre, 0.0f,0.0f,"")
}
#RequiresApi(Build.VERSION_CODES.O)
fun coger_tiempo_key(key: String) {
coger_datos("", 0.0f,0.0f,key)
}
#RequiresApi(Build.VERSION_CODES.O)
private fun coger_datos(nombre: String, longitud: Float, latitud: Float, p_key: String) {
datos_adquiridos = false
val quotesApi = RetrofitHelper.getInstance().create(QuotesApi_Tiempo::class.java)
// launching a new coroutine
GlobalScope.launch {
var key = "308526"
var ciudad_texto = "Sevilla"
Log.println(Log.ASSERT, "", "Ciudad entregada:" + nombre)
if (nombre != "") {
Log.println(Log.ASSERT, "", "Ciudad entregada por nombre:" + nombre)
// Coger la respuesta del rest API de la consulta de localidad por nombre
var respuesta = quotesApi.get_localidad_nombre(nombre, apikey = api_key)
Log.println(Log.ASSERT, "respuesta", respuesta.toString())
// Convertir la respuesta al objeto: List<Localidad>
var ciudades = respuesta.body()
key = ciudades?.get(0)?.Key.toString()
ciudad_texto = ciudades?.get(0)?.EnglishName.toString()
Log.println(Log.ASSERT, "", key)
Log.println(Log.ASSERT, "", ciudad_texto)
}
else {
if (p_key == "") {
// Coger la respuesta del rest API de la consulta de localidad por geoposición (longitud y latitud)
var respuesta = quotesApi.get_localidad_geoposition(
q = longitud.toString() + "," + latitud.toString(),
apikey = api_key
)
// Convertir la respuesta al objeto: Localidad
var ciudad = respuesta.body()
key = ciudad?.Key.toString()
ciudad_texto = ciudad?.localizedName.toString()
} else {
key = p_key
}
}
// Coger la respuesta del rest API de la consulta de las condiciones actuales
val respuesta_cond_act = quotesApi.get_condiciones_actuales(localidad = key, apikey = api_key, details = true, metric = true)
// Convertir la respuesta al objeto: List<Condiciones>
val cond_act = respuesta_cond_act.body()
// Coger la respuesta del rest API de la consulta de la previción de 1 dia
val respuesta_prev_1day = quotesApi.get_prevision_1_dia(localidad = key, apikey = api_key, details = true, metric = true)
// Convertir la respuesta al objeto: Previccion
val prev_1day = respuesta_prev_1day.body()
// Coger la respuesta del rest API de la consulta de la previcción de 5 dias
val respuesta_prev_5d = quotesApi.get_prevision_5_dias(localidad = key, apikey = api_key, details = true, metric = true)
// Convertir la respuesta al objeto: Previccion_5dias
val prev_5d = respuesta_prev_5d.body()
// Coger la respuesta del rest API de la consulta de la previcción de 12 horas
val respuesta_prev_12h = quotesApi.get_prevision_12_horas(localidad = key, apikey = api_key, details = true, metric = true)
// Convertir la respuesta al objeto: List<Previccion_12Horas>
val prev_12h = respuesta_prev_12h.body()
resultado.ciudad = ciudad_texto
// CARTA PRICIPAL
if (cond_act?.get(0) != null) {
resultado.temperatura_actual = cond_act?.get(0)?.Temperature?.Metric?.Value
resultado.unidades_temperatura = cond_act?.get(0)?.Temperature?.Metric?.UnitType.toString()
resultado.icono = cond_act?.get(0)?.WeatherIcon
resultado.icono_frase = cond_act?.get(0)?.WeatherText
}
if (prev_1day != null) {
resultado.temperatura_maxima = prev_1day?.DailyForecasts?.get(0)?.Temperature?.Maximum?.Value
resultado.temperatura_minima = prev_1day?.DailyForecasts?.get(0)?.Temperature?.Minimum?.Value
}
// DETALLES
if (cond_act?.get(0) != null) {
resultado.presion = cond_act?.get(0)?.Pressure?.Metric?.Value
resultado.uv = cond_act?.get(0)?.UVIndex
resultado.uv_text = cond_act?.get(0)?.UVIndexText
resultado.humedad_relativa = cond_act?.get(0)?.RelativeHumidity
resultado.viento = cond_act?.get(0)?.Wind?.Speed?.Metric?.Value
resultado.direccion_viento = cond_act?.get(0)?.Wind?.Direction?.Localized
}
// 12 HORAS
if (prev_12h?.get(0) != null) {
resultado.hora_1 = prev_12h?.get(0)?.DateTime?.toString().toString()
resultado.temperatura_hora_1 = prev_12h?.get(0)?.Temperature?.Value!!
resultado.icono_hora_1 = prev_12h.get(0).WeatherIcon!!
resultado.hora_2 = prev_12h.get(1).DateTime.toString()
resultado.temperatura_hora_2 = prev_12h.get(1).Temperature?.Value!!
resultado.icono_hora_2 = prev_12h.get(1).WeatherIcon!!
resultado.hora_3 = prev_12h.get(2).DateTime.toString()
resultado.temperatura_hora_3 = prev_12h.get(2).Temperature?.Value!!
resultado.icono_hora_3 = prev_12h.get(2).WeatherIcon!!
resultado.hora_4 = prev_12h?.get(3)?.DateTime.toString()
resultado.temperatura_hora_4 = prev_12h.get(3).Temperature?.Value!!
resultado.icono_hora_4 = prev_12h.get(3).WeatherIcon!!
resultado.hora_5 = prev_12h.get(4).DateTime.toString()
resultado.temperatura_hora_5 = prev_12h?.get(4)?.Temperature?.Value!!
resultado.icono_hora_5 = prev_12h?.get(4)?.WeatherIcon!!
resultado.hora_6 = prev_12h?.get(5)?.DateTime.toString()
resultado.temperatura_hora_6 = prev_12h?.get(5)?.Temperature?.Value!!
resultado.icono_hora_6 = prev_12h?.get(5)?.WeatherIcon!!
resultado.hora_7 = prev_12h?.get(6)?.DateTime.toString()
resultado.temperatura_hora_7 = prev_12h?.get(6)?.Temperature?.Value!!
resultado.icono_hora_7 = prev_12h?.get(6)?.WeatherIcon!!
resultado.hora_8 = prev_12h?.get(7)?.DateTime.toString()
resultado.temperatura_hora_8 = prev_12h?.get(7)?.Temperature?.Value!!
resultado.icono_hora_8 = prev_12h?.get(7)?.WeatherIcon!!
resultado.hora_9 = prev_12h?.get(8)?.DateTime.toString()
resultado.temperatura_hora_9 = prev_12h?.get(8)?.Temperature?.Value!!
resultado.icono_hora_9 = prev_12h?.get(8)?.WeatherIcon!!
resultado.hora_10 = prev_12h?.get(9)?.DateTime.toString()
resultado.temperatura_hora_10 = prev_12h?.get(9)?.Temperature?.Value!!
resultado.icono_hora_10 = prev_12h?.get(9)?.WeatherIcon!!
resultado.hora_11 = prev_12h?.get(10)?.DateTime.toString()
resultado.temperatura_hora_11 = prev_12h?.get(10)?.Temperature?.Value!!
resultado.icono_hora_11 = prev_12h?.get(10)?.WeatherIcon!!
resultado.hora_12 = prev_12h?.get(11)?.DateTime.toString()
resultado.temperatura_hora_12 = prev_12h?.get(11)?.Temperature?.Value!!
resultado.icono_hora_12 = prev_12h?.get(11)?.WeatherIcon!!
}
// 5 dias
if (prev_5d != null) {
resultado.fecha_dia_1 = prev_5d?.DailyForecasts?.get(0)?.Date.toString()
resultado.frase_dia_1 = prev_5d?.DailyForecasts?.get(0)?.Day?.ShortPhrase.toString()
resultado.icono_dia_1 = prev_5d?.DailyForecasts?.get(0)?.Day?.Icon!!
resultado.Temp_max_dia_1 = prev_5d?.DailyForecasts?.get(0)?.Temperature?.Maximum?.Value!!
resultado.Temp_min_dia_1 = prev_5d?.DailyForecasts?.get(0)?.Temperature?.Minimum?.Value!!
resultado.fecha_dia_2 = prev_5d?.DailyForecasts?.get(1)?.Date.toString()
resultado.frase_dia_2 = prev_5d?.DailyForecasts?.get(1)?.Day?.ShortPhrase.toString()
resultado.icono_dia_2 = prev_5d?.DailyForecasts?.get(1)?.Day?.Icon!!
resultado.Temp_max_dia_2 = prev_5d?.DailyForecasts?.get(1)?.Temperature?.Maximum?.Value!!
resultado.Temp_min_dia_2 = prev_5d?.DailyForecasts?.get(1)?.Temperature?.Minimum?.Value!!
resultado.fecha_dia_3 = prev_5d?.DailyForecasts?.get(2)?.Date.toString()
resultado.frase_dia_3 = prev_5d?.DailyForecasts?.get(2)?.Day?.ShortPhrase.toString()
resultado.icono_dia_3 = prev_5d?.DailyForecasts?.get(2)?.Day?.Icon!!
resultado.Temp_max_dia_3 =prev_5d?.DailyForecasts?.get(2)?.Temperature?.Maximum?.Value!!
resultado.Temp_min_dia_3 = prev_5d?.DailyForecasts?.get(2)?.Temperature?.Minimum?.Value!!
resultado.fecha_dia_4 = prev_5d?.DailyForecasts?.get(3)?.Date.toString()
resultado.frase_dia_4 = prev_5d?.DailyForecasts?.get(3)?.Day?.ShortPhrase.toString()
resultado.icono_dia_4 = prev_5d?.DailyForecasts?.get(3)?.Day?.Icon!!
resultado.Temp_max_dia_4 = prev_5d?.DailyForecasts?.get(3)?.Temperature?.Maximum?.Value!!
resultado.Temp_min_dia_4 = prev_5d?.DailyForecasts?.get(3)?.Temperature?.Minimum?.Value!!
resultado.fecha_dia_5 = prev_5d?.DailyForecasts?.get(4)?.Date.toString()
resultado.frase_dia_5 = prev_5d?.DailyForecasts?.get(4)?.Day?.ShortPhrase.toString()
resultado.icono_dia_5 = prev_5d?.DailyForecasts?.get(4)?.Day?.Icon!!
resultado.Temp_max_dia_5 = prev_5d?.DailyForecasts?.get(4)?.Temperature?.Maximum?.Value!!
resultado.Temp_min_dia_5 = prev_5d?.DailyForecasts?.get(4)?.Temperature?.Minimum?.Value!!
}
datos_adquiridos = true
Log.println(Log.ASSERT, "datos_tiempo", resultado.toString())
}
}
}
DataModel
data class Datos_Tiempo #RequiresApi(Build.VERSION_CODES.O) constructor(
// Card principal
var ciudad: String? = "Cordoba",
var temperatura_actual: Double? = 18.0,
var temperatura_maxima: Double? = 12.0,
var temperatura_minima: Double? = 6.0,
var unidades_temperatura: String? = "grados",
var icono: Int? = 1,
var icono_frase: String? = "Cloudy",
// Detalles
var presion: Double? = 1020.0,
var direccion_viento: String? = "Sur",
var viento: Double? = 30.0,
var uv: Int? = 4,
var uv_text: String? = "Low",
var humedad_relativa: Double? = 50.0,
#DrawableRes val icon: Int? = R.drawable.ic_1,
val data: String? = "45",
val units: String? = "km/h",
val color: Color? = GreyCard,
//12horas
var hora_1: String = "2022-12-07T12:00:00+01:00",
var icono_hora_1: Int = 6,
var temperatura_hora_1: Double = 13.5,
var temperatura_hora_2: Double = 12.3,
var hora_2: String = "2022-12-07T13:00:00+01:00",
var icono_hora_2: Int = 12,
var temperatura_hora_3: Double = 14.3,
var hora_3: String = "2022-12-07T14:00:00+01:00",
var icono_hora_3: Int = 13,
var temperatura_hora_4: Double = 15.3,
var hora_4: String = "2022-12-07T15:00:00+01:00",
var icono_hora_4: Int = 15,
var temperatura_hora_5: Double = 13.3,
var hora_5: String = "2022-12-07T16:00:00+01:00",
var icono_hora_5: Int = 13,
var temperatura_hora_6: Double = 12.3,
var hora_6: String = "2022-12-07T17:00:00+01:00",
var icono_hora_6: Int = 12,
var temperatura_hora_7: Double = 12.3,
var hora_7: String = "2022-12-07T18:00:00+01:00",
var icono_hora_7: Int = 6,
var temperatura_hora_8: Double = 13.3,
var hora_8: String = "2022-12-07T19:00:00+01:00",
var icono_hora_8: Int = 7,
var temperatura_hora_9: Double = 11.3,
var hora_9: String = "2022-12-07T20:00:00+01:00",
var icono_hora_9: Int = 6,
var temperatura_hora_10: Double = 14.3,
var hora_10: String = "2022-12-07T21:00:00+01:00",
var icono_hora_10: Int = 6,
var temperatura_hora_11: Double = 13.3,
var hora_11: String = "2022-12-07T22:00:00+01:00",
var icono_hora_11: Int = 26,
var temperatura_hora_12: Double = 12.3,
var hora_12: String = "2022-12-07T23:00:00+01:00",
var icono_hora_12: Int = 36,
//5dias
var fecha_dia_1: String = "2022-12-07T18:00:00+01:00",
var frase_dia_1: String = "Mostly Cloudy w/ T-Storms",
var icono_dia_1: Int = 17,
var Temp_max_dia_1: Double = 12.0,
var Temp_min_dia_1: Double = 2.0,
var fecha_dia_2: String = "2022-12-08T18:00:00+01:00",
var frase_dia_2: String = "Rain",
var icono_dia_2: Int = 18,
var Temp_max_dia_2: Double = 15.0,
var Temp_min_dia_2: Double = 4.0,
var fecha_dia_3: String = "2022-12-09T18:00:00+01:00",
var frase_dia_3: String = "Flurries",
var icono_dia_3: Int = 19,
var Temp_max_dia_3: Double = 16.0,
var Temp_min_dia_3: Double = 5.0,
var fecha_dia_4: String = "2022-12-10T18:00:00+01:00",
var frase_dia_4: String = "Mostly Cloudy w/ Flurries",
var icono_dia_4: Int = 20,
var Temp_max_dia_4: Double = 14.0,
var Temp_min_dia_4: Double = 4.0,
var fecha_dia_5: String = "2022-12-11T18:00:00+01:00",
var frase_dia_5: String = "Mostly Cloudy w/ T-Storms",
var icono_dia_5: Int = 16,
var Temp_max_dia_5: Double = 16.0,
var Temp_min_dia_5: Double = 5.0,
)
Two major problems.
First, you are creating your instance of Accesso_API in the Composable, so it is recreated every time things recompose, so you end up with many copies (probably an infinite loop that will eventually crash the device with out-of-memory error), and you are always looking at a new copy who doesn't have any fetched data yet. You need to wrap it in remember { } so it is initialized only once:
val acceso = remember {
Accesso_API()
.apply { coger_tiempo_nombre("Madrid") }
}
Second, you cannot do this kind of thing in UI code:
while (!acceso.datos_adquiridos) {}
This will freeze the whole device because it blocks the main thread.
Compose is meant to be reactive. Don't wait and repeatedly poll values. Create your data property as State that the Composable uses. When the data isn't ready yet, your composable should show a loading indicator or blank screen or something.
So, in your Accesso_API class, change resultado to be MutableState, and you can use null to represent the state when no data is acquired yet:
var resultado: Datos_Tiempo? by mutableStateOf(null)
And completely delete the datos_adquiridos property and any code where you use it.
In your composable, you can get a reference to this state, and you can determine whether it is received yet by checking if it is null.
val resultado = acceso.resultado
if (resultado == null) {
// show screen with loading indicator
return
}
// the rest of your code that builds the screen using non-null resultado.
There could be other problems to fix, but you have so much code I didn't go through it all in detail.
Side note, this kind of repetitive code should be avoided:
//12horas
var hora_1: String = "2022-12-07T12:00:00+01:00",
var icono_hora_1: Int = 6,
var temperatura_hora_1: Double = 13.5,
var temperatura_hora_2: Double = 12.3,
var hora_2: String = "2022-12-07T13:00:00+01:00",
var icono_hora_2: Int = 12,
//...
Use lists or arrays instead of many similar properties! Then you can iterate things and won't have super long functions with repetitive code wherever you work with this data class.

Jetpack Compose offset image vector in Canvas

I have a problem with vector image in Canvas. As shown below I can just call vector image but I can’t make any offset in Canvas. So I only can have it the way it is.
I don't know the reason why there is no Offset option like in drawCircle or drawRect, if someone has some ideas it would be great.
val vector = ImageVector.vectorResource(id = R.drawable.ic_test)
val painter = rememberVectorPainter(image = vector)
Box(contentAlignment = Alignment.Center) {
Canvas(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
with(painter) {
draw(
painter.intrinsicSize
)
}
}
}
I tried something like adding Offset into with(painter) but nothing changes:
with(painter) {
draw(
painter.intrinsicSize
)
Offset(x = 10f, y = 10f)
}
You can use DrawScope.translate:
translate(left = 10f, top = 10f) {
with(painter) {
draw(
painter.intrinsicSize
)
}
}

How to switch the position of two buttons randomly؟

I want to switch the positions of the buttons when we press the toggle button. Buttons are repositioned randomly. I tried a lot but was not successful. So if anyone knows how but randomly
val btnOne = findViewById<View>(R.id.buttonOne) as Button
val btnTwo = findViewById<View>(R.id.buttonTwo) as Button
val posOneX = btnOne.x
val posOneY = btnOne.y
val posTwoX = btnTwo.x
val posTwoY = btnTwo.y
btnOne.x = posTwoX
btnOne.y = posTwoY
btnTwo.x = posOneX
btnTwo.y = posOneY
Yes, I found the solution
b4.setOnClickListener {
var arswap = arrayListOf<Button>(b1, b2, b3);
var randomPos: Int = 0;
fun swap(b1: Button, b2: Button) {
var tempPosition = Point(b1.x.toInt(), b1.y.toInt())
b1.x = b2.x;
b1.y = b2.y;
b2.x = tempPosition.x.toFloat();
b2.y = tempPosition.y.toFloat();
}
var totalItems: Int = 3; // total number of items
for (i in 0..totalItems/3 ) {
randomPos = ((Math.random() * arswap.size).toInt());
var randomItem = ((Math.random() * ((totalItems / 2) - 1)) + ((totalItems / 2) + 1));
swap(arswap[randomPos], arswap[randomItem.toInt()])
}
}

Fragment BarChart in Modal is not updating - TornadoFX

I've created a Fragment to hold a BarChart and a ScrollPane - the end result will be a scrollable histogram.
I'm creating these new fragments in a seperate modal using the openModal method.
The problem that i'm having is that the BarChart doesn't seem to be updating when I call my loadData method, as shown below:
class Histogram : Fragment() {
override val root = vbox{
hgrow = Priority.ALWAYS
vgrow = Priority.ALWAYS
style{
minWidth = 1280.px
minHeight = 180.px
}
hbox{
hgrow = Priority.ALWAYS
}
}
private val bar = ScrollBar()
private var barChart = barchart("bar", CategoryAxis(), NumberAxis()){
barGap = 0.0
categoryGap = -1.0
hgrow = Priority.ALWAYS
vgrow = Priority.ALWAYS
style{
minWidth = 640.px
minHeight = 240.px
maxHeight = 480.px
}
isLegendVisible = false
}
private val s = XYChart.Series<String, Number>()
init{
root.add(barChart)
root.add(bar)
}
fun loadData(h: AlignmentHistogram){
s.data.add(XYChart.Data<String, Number>("asd", 2))
barChart.title = h.rname
/* for(i in 0..MAX_DATASET_SIZE){
if(i > h.histogram.size){
break
}
val data = XYChart.Data<String, Int>((h.firstPosition + i + 1).toString(), (h.histogram[i]))
println(data)
s.data.add(data)
}*/
s.data.add(XYChart.Data<String, Number>("asasdd", 5))
s.name = h.rname
barChart.data.add(s)
}
}
AlignmentHistogram is just a POJO with the data for the histogram and a few other details.
I'm calling the fragment with the following code:
val stage = Stage()
stage.title = "${pointer.rname} - ${selectedFile.file.name}"
val view = Histogram()
view.loadData(h)
val parent = StackPane()
stage.scene = Scene(parent, 1280.0, 360.0)
view.openModal(StageStyle.UTILITY, Modality.NONE, false, stage, false, true)
The result of this is just an empty histogram in a new modal, despite calling barChart.data.add(s)
Any ideas? Thanks in advance!
Nevermind, I solved it by passing the POJO in as the scope and putting the set-up code into the init block.
This article helped with the scope part:
Tornadofx - How to pass parameter to Fragment on every instance