I want to avoid multiple function call when LaunchEffect key triggers.
LaunchedEffect(key1 = isEnableState, key2 = viewModel.uiState) {
viewModel.scanState(bluetoothAdapter)
}
when first composition isEnableState and viewModel.uiState both will trigger twice and call viewModel.scanState(bluetoothAdapter).
isEnableState is a Boolean type and viewModel.uiState is sealed class of UI types.
var uiState by mutableStateOf<UIState>(UIState.Initial)
private set
var isEnableState by mutableStateOf(false)
private set
So how can we handle idiomatic way to avoid duplicate calls?
Thanks
UPDATE
ContentStateful
#Composable
fun ContentStateful(
context: Context = LocalContext.current,
viewModel: ContentViewModel = koinViewModel(),
) {
LaunchedEffect(key1 = viewModel.isEnableState, key2 = viewModel.uiState) {
viewModel.scanState(bluetoothAdapter)
}
LaunchedEffect(viewModel.previous) {
viewModel.changeDeviceSate()
}
ContentStateLess{
viewModel.isEnableState = false
}
}
ContentStateLess
#Composable
fun ContentStateLess(changeAction: () -> Unit) {
Button(onClick = { changeAction() }) {
Text(text = "Click On me")
}
}
ContentViewModel
class ContentViewModel : BaseViewModel() {
var uiState by mutableStateOf<UIState>(UIState.Initial)
var isEnableState by mutableStateOf(false)
fun scanState(bluetoothAdapter: BluetoothAdapter) {
if (isEnableState && isInitialOrScanningUiState()) {
// start scanning
} else {
// stop scanning
}
}
private fun isInitialOrScanningUiState(): Boolean {
return (uiState == UIState.Initial || uiState == UIState.ScanningDevice)
}
fun changeDeviceSate() {
if (previous == BOND_NONE && newState == BONDING) {
uiState = UIState.LoadingState
} else if (previous == BONDING && newState == BONDED) {
uiState = UIState.ConnectedState(it)
} else {
uiState = UIState.ConnectionFailedState
}
}
}
scanState function is start and stop scanning of devices.
I guess the answer below would work or might require some modification to work but logic for preventing double clicks can be used only if you wish to prevent actions happen initially within time frame of small interval. To prevent double clicks you you set current time and check again if the time is above threshold to invoke click callback. In your situation also adding states with delay might solve the issue.
IDLE, BUSY, READY
var launchState by remember {mutableStateOf(IDLE)}
LaunchedEffect(key1 = isEnableState, key2 = viewModel.uiState) {
if(launchState != BUSY){
viewModel.scanState(bluetoothAdapter)
if(launchState == IDLE){ launchState = BUSY)
}
}
LaunchedEffect(launchState) {
if(launchState == BUSY){
delay(50)
launchState = READY
}
}
I have a table with fifteen rows. Each row have three columns and a total column. I want to get the total per row, the grand total, and the overall average.
The user may not enter data for all rows, and the user may skip a row.
So the code checks if the user have entered data in one of three fields of each row.
If the row is blank, ignore it.
If some of the fields are filled-up, tell the user to fill up the rest of the row.
If all the fields in a row is filled up, sum all its fields and increment the divider.
I have only pasted the codes for Rows 1 & 2 for brevity, but it shows the gist of what I'm trying to achieve:
The code:
var a1 = 0
var a2 = 0
var total = 0
var divider = 0
// Row 1
if (b1p1.text.isNotEmpty() or b2p1.text.isNotEmpty() or b3p1.text.isNotEmpty()) {
var y = 0
listOf(b1p1, b2p1, b3p1).forEach {
if (it.text.isEmpty()) {
it.error = "Fill up empty fields!"
y = 1
}
}
if (y == 0) {
divider++
listOf(b1p1, b2p1, b3p1).forEach {
a1 += it.text.toString().toInt()
}
total1.text = a1.toString()
total += a1
e2 = 1
} else {
Toast.makeText(activity, "Error", Toast.LENGTH_SHORT).show()
}
}
// Row 2
if (b1p2.text.isNotEmpty() or b2p2.text.isNotEmpty() or b3p2.text.isNotEmpty()) {
var y = 0
listOf(b1p2, b2p2, b3p2).forEach {
if (it.text.isEmpty()) {
it.error = "Fill up empty fields!"
y = 1
}
}
if (y == 0) {
divider++
listOf(b1p2, b2p2, b3p2).forEach {
a2 += it.text.toString().toInt()
}
total2.text = a2.toString()
total += a2
} else {
Toast.makeText(activity, "Error", Toast.LENGTH_SHORT).show()
}
}
if (e2 == 1) {
grandTotalTextView.text = total.toString()
average = total.toDouble()/divider
val decimalFormatter = DecimalFormat("#,###.##")
averageTextView.text = decimalFormatter.format(average).toString()
cyeSingleton.anct3b = decimalFormatter.format(average).toString()
} else {
Toast.makeText(activity, "Error 2", Toast.LENGTH_SHORT).show()
}
The table:
This is the best I could come up with. Should there be no other suggestion, I will settle for this.
Thanks in advance!
**EDIT: Thanks to ** https://stackoverflow.com/users/3736955/jemshit-iskenderov
data class TotalResult(val divider:Int, val allTotal:Int, val showError:Boolean)
private fun calculateTotalResult(allTextViews:List<List<TextView>>, totalTextViews:List<TextView>): TotalResult {
var divider = 0
var allTotal = 0
var showError=false
allTextViews.forEachIndexed{index, rowTextViews->
val rowResult = calculateRowResult(rowTextViews as List<EditText>, totalTextViews[index])
if(!rowResult.ignoreRow){
if(rowResult.allFieldsFilled){
divider+=1
allTotal+=rowResult.rowTotal
}else{
showError = true
}
}
}
Toast.makeText(
activity,
"$divider, $allTotal, $showError", Toast.LENGTH_SHORT)
.show()
return TotalResult(divider, allTotal, showError)
}
data class RowResult(val ignoreRow:Boolean, val allFieldsFilled:Boolean, val rowTotal:Int)
private fun calculateRowResult(rowTextViews:List<EditText>, totalTextView:TextView): RowResult {
val ignore = rowTextViews.filter{it.text.isBlank()}.count() == rowTextViews.size
if(ignore)
return RowResult(true, false, 0)
var emptyFieldCount = 0
var total = 0
rowTextViews.forEach {textView ->
if (textView.text.isEmpty()) {
textView.error = "Fill up empty fields!"
emptyFieldCount +=1
}else{
val fieldValue:Int? = textView.text.toString().toIntOrNull() // or toIntOrElse{0}
if(fieldValue!=null) total+=fieldValue
}
}
if(emptyFieldCount==0)
totalTextView.text = total.toString()
return RowResult(false, emptyFieldCount==0, total)
}
fun main(){
val totalResult = calculateTotalResult(
allTextViews = listOf(
listOf(t11,t12,t13),
listOf(t21,t22,t23)
),
totalTextViews = listOf(totalView1, totalView2)
)
// single Toast error
if(totalResult.showError){
// showToast(error)
}
// use totalResult.divider, totalResult.allTotal
}
data class TotalResult(val divider:Int, val allTotal:Int, val showError:Boolean)
fun calculateTotalResult(allTextViews:List<List<TextView>>, totalTextViews:List<TextView>){
var divider = 0
var allTotal = 0
var showError=false
allTextViews.forEachIndexed{index, rowTextViews->
val rowResult = calculateRowResult(rowTextViews, totalTextViews[index])
if(!rowResult.ignore){
if(rowResult.allFieldsFilled){
divider+=1
allTotal+=rowResult.rowTotal
}else{
showError = true
}
}
}
return TotalResult(divider, allTotal, showError)
}
data class RowResult(val ignoreRow:Boolean, val allFieldsFilled:Boolean, val rowTotal:Int)
fun calculateRowResult(rowTextViews:List<TextView>, totalTextView:TextView): RowResult {
val ignore = rowTextViews.filter{it.isBlank()}.count() == rowTextViews.size
if(ignore)
return RowResult(true, false, 0)
var emptyFieldCount = 0
var total = 0
rowTextViews.forEach {textView ->
if (textView.text.isEmpty()) {
textView.error = "Fill up empty fields!"
emptyFieldCount +=1
}else{
val fieldValue:Int? = textView.text.toString().toIntOrNull() // or toIntOrElse{0}
if(fieldValue!=null) total+=fieldValue
}
}
if(emptyFieldCount==0)
totalTextView.text = total.toString()
return RowResult(false, emptyFieldCount==0, total)
}
Extracted calculateTotalResult() and calculateRowResult() so multiple rows and columns do not need to repeat same code.
calculateRowResult() processes singlet row of TextViews. I had to iterate rowTextViews twice, one to calculate ignore, the other to show error on TextView if not ignore. We don't show Toast Error here yet.
calculateTotalResult() iterates through all rows and gets total result. We show only one Toast Error (if required) after this step.
Code is pseudo-code, not tested.
The problem here return#Foreach not getting executed it always return false
on the if statement
fun check() {
var x: Int? = null
val numbers = 1..100
numbers.forEach {
x = it
if (it == 2) {
return#forEach
}
}
showingText("$x Hello World")
}
Actually, the it==2 condition is true when it is 2, and the statement return#forEach executed then. You can see it with simple logs:
fun check() {
var x: Int? = null
val numbers = 1..100
numbers.forEach {
println(it)
x = it
if (it == 2) {
println("FINISH")
return#forEach
}
}
println("$x Hello World")
}
So, the return is executed and stops the forEach method. Then the sequence of range moves to the next item (3, 4, and so on) and action is run for it consequently.
If you want to stop actions after 2 found, use simple loop:
fun check2() {
var x: Int? = null
val numbers = 1..100
for(it in numbers) {
println(it)
x = it
if (it == 2) {
println("FINISH")
break
}
}
println("$x Hello World")
}
Im followed a tutorial to create a maze with Recursive Backtracking and it works great.
Im trying to create a game where people get on the same maze, and if someone wins, it creates a new maze and everyones current maze gets updated.
So what i was thinking is to have a seed to create the same maze and pass that seed to all the players so they can have the same maze.
Is there a way to modify it so i can give the maze a seed and it creates always the same maze?
This is what i have now:
It uses a Cell class (posx,posy)
class Cell(var col:Int = 0, var row: Int = 0){
var topWall = true
var leftWall = true
var bottomWall = true
var rightWall = true
var visited = false
}
fun createMaze(){
var stack = Stack<Cell>()
var current:Cell
var next:Cell?
for(x in 0 until COLS){
for(y in 0 until ROWS){
cells[x][y] = Cell(x,y)
}
}
player = cells[0][0]
exit = cells [COLS-1][ROWS-1]
current = cells[0][0]
current.visited = true
do{
next = getNeighbour(current)
if(next != null) {
removeWall(current, next)
stack.push(current)
current = next
current.visited = true
}else{
current = stack.pop()
}
}while (!stack.empty())
}
fun getNeighbour(cell:Cell): Cell? {
var vecinos: ArrayList<Cell> = ArrayList()
//vecino izquierda
if(cell.col > 0) {
if (!cells[cell.col - 1][cell.row].visited) {
vecinos.add(cells[cell.col - 1][cell.row])
}
}
//vecino derecha
if(cell.col < COLS - 1) {
if (!cells[cell.col + 1][cell.row].visited) {
vecinos.add(cells[cell.col + 1][cell.row])
}
}
//vecino arriba
if(cell.row > 0) {
if (!cells[cell.col][cell.row - 1].visited) {
vecinos.add(cells[cell.col ][cell.row - 1])
}
}
//vecino abajo
if(cell.row < ROWS - 1) {
if (!cells[cell.col][cell.row + 1].visited) {
vecinos.add(cells[cell.col][cell.row + 1])
}
}
if (vecinos.size > 0) {
var index = random.nextInt(vecinos.size)
return vecinos[index]
}else {
return null
}
}
fun removeWall(current:Cell,next:Cell){
if (current.col == next.col && current.row == next.row +1){
current.topWall = false
next.bottomWall = false
}
if (current.col == next.col && current.row == next.row -1){
current.bottomWall = false
next.topWall = false
}
if (current.col == next.col + 1 && current.row == next.row){
current.leftWall = false
next.rightWall = false
}
if (current.col == next.col - 1 && current.row == next.row){
current.rightWall = false
next.leftWall = false
}
}
If you want to pass a seed to create the maze, then you have to make sure that all of the players are using the same random number generator. Which means you have to supply your own random number generator implementation.
The application would seed the random number generator with the value you pass, and then it should deterministically generate the same sequence of random numbers for each client.
Note also that you can't ever change the random number generator implementation unless you can prove that the new implementation will generate exactly the same sequence of numbers that the original did.
class MainActivity : AppCompatActivity() {
enum class PLAYINGPLAYER {
FIRST_PLAYER,
SECOND_PLAYER
}
var playingplayer: PLAYINGPLAYER? = null
var buttonClicked:ArrayList<Int> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
playingplayer = PLAYINGPLAYER.FIRST_PLAYER
}
fun imgButtonClicked(v: View) {
val btnselected = v as Button
var optionNumber = 0
when (btnselected.id) {
R.id.btn1 -> optionNumber = 1
R.id.btn2 -> optionNumber = 2
R.id.btn3 -> optionNumber = 3
R.id.btn4 -> optionNumber = 4
R.id.btn5 -> optionNumber = 5
R.id.btn6 -> optionNumber = 6
}
action(optionNumber, btnselected)
}
var button1Click = ArrayList<Button>()
var button2Click = ArrayList<Button>()
fun action(optionNumber: Int, btnselected: Button) {
if (playingplayer == PLAYINGPLAYER.FIRST_PLAYER) {
btnselected.setBackgroundColor(Color.GREEN)
buttonClicked.add(optionNumber)
btnselected.isEnabled = false
button1Click.add(btnselected)
playingplayer = PLAYINGPLAYER.SECOND_PLAYER
}
else if (playingplayer == PLAYINGPLAYER.SECOND_PLAYER) {
btnselected.setBackgroundColor(Color.RED)
buttonClicked.add(optionNumber)
btnselected.isEnabled = false
button2Click.add(btnselected)
playingplayer = PLAYINGPLAYER.FIRST_PLAYER
}
imagebtnState()
}
fun imagebtnState() {
val btnLast = buttonClicked.get(buttonClicked.size - 1)
if (buttonClicked.contains(1) && buttonClicked.contains(2)) {
if ( btnLast == ??? player1 ) {
imgbBtnBackground.setImageResource(R.drawable.rca)
}
}
else if (buttonClicked.contains(1) && buttonClicked.contains(2)) {
if ( btnLast == ??? player2 ) {
imgbBtnBackground.setImageResource(R.drawable.wac)
}
}
}
}
I have two players and several buttons. Player1 clicks on a button and player2 clicks on another button. When the players press several specific ?, I want to change the background of imageButton.
In (btnLast = ???) what I have put to get last one click in (buttonClicked.contains(1) && buttonClicked.contains(2)) is player1 or player2.
Assuming you are only ever going to have two players create a boolean called var playerOneLastClicked = true and if player one clicks set true, else if player two clicks set false
if (buttonClicked.contains(1) && buttonClicked.contains(2)) {
if (playerOneLastClicked) {
imgbBtnBackground.setImageResource(R.drawable.rca)
} else {
imgbBtnBackground.setImageResource(R.drawable.wac)
}
}
Since imagebtnState() is called after each click it doesn't really matter about checking who clicked the last in the arrayList as the method does the check to see whether or not the specified buttons have been clicked anyway
I think that's that what you were asking for?