I wrote my first app. It displays a simple screen with an addition to complete. If the given answer is correct, I would like to refresh the screen to display a new calculation.
Currently, the old calculation remains, and the new one is inserted on the top.
The screen I would like:
What I see on screen after several given answers:
Here is my code:
package com.example.calcul
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
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.calcul.ui.theme.CalculTheme
import kotlin.random.Random
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
CalculTheme {
MainContent()
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainContent() {
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text("Addition")
}
)
},
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.border(2.dp, color = Color.Red)
// For text to be displayed under the bar
.padding(innerPadding)
) {
//Add()
InitializeNumbers()
}
}
}
@Composable
fun Add(fist: String, second: String) {
var haveToReset by remember {
mutableStateOf(false)
}
if (haveToReset) {
InitializeNumbers()
}
val focusRequester = remember { FocusRequester() }
var fist_number by remember { mutableStateOf(fist) }
var second_number by remember { mutableStateOf(second) }
var resultInput by remember { mutableStateOf("") }
var result by remember { mutableIntStateOf(0) }
result = fist_number.toInt() + second_number.toInt()
val maxChar: Int = 2
Spacer(modifier = Modifier.height(50.dp))
Text(
text = "$fist_number + $second_number = ",
fontSize = 50.sp
)
TextField(
value = resultInput,
singleLine = true,
// /questions/67136058/textfield-maxlength-android-jetpack-compose
onValueChange = { if (it.length <= maxChar) resultInput = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
keyboardActions = KeyboardActions(
onDone = {
if (resultInput.toInt() == result) {
println("OK")
haveToReset = !haveToReset
}
}
),
modifier = Modifier.focusRequester(focusRequester)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
@Composable
fun InitializeNumbers() {
var fist = Random.nextInt(11).toString()
var second = Random.nextInt(11).toString()
Add(fist, second)
}
@Preview(showBackground = true)
@Composable
fun AdditionPreview() {
CalculTheme {
MainContent()
}
}
Thanks for you help
You have a conditional recursive call in your code:
@Composable
fun Add(fist: String, second: String) {
//...
if (haveToReset) {
InitializeNumbers() // HERE IS THE RECURSIVE CALL
}
//...
}
This will result in a call stack like this:
MainContent
-> InitializeNumbers
-> Add
-> InitializeNumbers
-> Add
-> InitializeNumbers
-> ...
Instead, your app should follow the unidirectional data flow pattern. That means that state should flow down the Composable hierarchy and events flow upwards using callback functions. In your case, delegate the state management to the parent Composable and use a callback to initialize new numbers.
First, change the signature of the Add
Composable and only use the following variables:
@Composable
fun Add(first: Int, second: Int, onReset: () -> Unit) {
val focusRequester = remember { FocusRequester() }
val maxChar: Int = remember { 2 }
val result = first + second
var resultInput by remember { mutableStateOf("") }
LaunchedEffect(first, second) { // reset input with new calculation
resultInput = ""
}
Spacer(modifier = Modifier.height(50.dp))
Text(
text = "$first + $second = ",
fontSize = 50.sp
)
//...
}
Then, after completing and validating the input, call the onReset()
function:
keyboardActions = KeyboardActions(
onDone = {
if (resultInput.toInt() == result) {
println("OK")
onReset()
}
}
),
And directly call the Add
Composable in your MainContent
Composable like this:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainContent() {
Scaffold(
//...
) { innerPadding ->
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.border(2.dp, color = Color.Red)
.padding(innerPadding)
) {
var firstNumber by remember {
mutableIntStateOf(Random.nextInt(11))
}
var secondNumber by remember {
mutableIntStateOf(Random.nextInt(11))
}
Add(
first = firstNumber,
second = secondNumber,
onReset = {
firstNumber = Random.nextInt(11)
secondNumber = Random.nextInt(11)
}
)
}
}
}
2