I have a function SendInstruction which send instructions to the user to press a button with label x for 3 seconds. I have four buttons with labels y,z and w.
fun SendInstruction(buttonLabel:String):String{
return buttonLabel
}
I have a counter that either increments by 20 if correct sent instruction matches user clicked button and decrements if not correct.
But when correct for 3 seconds then another instruction can be sent for button y otherwise user must pass button x to go to y and also must pass y to go to z and must pass z to go to w
Here is what i tried so far
var counter:Int = 0
GlobalScope.launch{
SendInstruction("click button x")
if(userclicked)//always true for 3 seconds
delay(3000)//check in 3s if instructions matches user clicked
counter +=20
SendInstruction("click button y")
if(userclicked)//always true for 3 s
delay(3000)//check in 3s if instruction matches user clicked
else
counter = 0
//restart
}
But it just doesn't work, can anyone help me with this please.
You could use a timeout pattern, like a reverse debounce catch, to help here. Here is an example, where you set a click target and a coroutine that will reset the target in 3 seconds. If you click the target in that time the coroutine is cancelled and you move to the next target.
private var job: Job? = null
private var target = ""
private var score = 0
fun onClick(btn: String, next: String) {
if(btn == target) {
advance(next)
}
else {
// Handle wrong click by starting over
reset()
}
}
fun advance(next: String) {
// Handle a correct click by cancelling
// the coroutine, updating the score,
// and setting the next target
// Cancel the timeout
job?.cancel()
// Add to the score
score += 20
// Set the next/end case
if(next.isEmpty()) {
SendInstruction("you won")
}
else {
updateTarget(next)
}
}
fun updateTarget(next: String) {
// Update the target, notify the user,
// and start a coroutine to reset
// the target in 3 seconds
target = next
SendInstruction("Click on $next")
// Reset the target after 3 seconds
// unless the coroutine is cancelled
// by a correct button click first
job = lifecycleScope.launch {
delay(3000)
reset()
}
}
fun reset() {
// Start over on a wrong click
// or a timeout. Alternately could
// just show "game over" and make
// them click "Go" or something to start over
job?.cancel()
target = ""
score = 0
updateTarget("x")
}
fun start() {
// Call to start the game, probably from onResume or when the user clicks "Go" or something
updateTarget("x")
}
// Call these when the specific buttons are clicked
fun onClickX() {
onClick("x", "y")
}
fun onClickY() {
onClick("y", "z")
}
fun onClickZ() {
onClick("z", "w")
}
fun onClickW() {
onClick("w", "")
}
Related
Need to change channel by numpad of remote controller but don't know correct method. Numbers can be pressed multiple times and need to wait for all of them and get 1 String like "123".
Now I override onKey up like below
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return when(keyCode){
KeyEvent.KEYCODE_1->{
//I don't know how to wait here next key up and get correct full channel number
true
}
KeyEvent.KEYCODE_2->{
...
true
}
//EPG
KeyEvent.KEYCODE_3->{
...
true
}
...
...
else -> super.onKeyUp(keyCode, event)
}
}
To perform searching you need first to collect all digits of the number in range of some time, you can use StringBuilder to append one digit inside onKeyUp or OnKeyDown depend on your requirements
You need to delay performing search until the user write the full number, you can use CountDownTimer and reset the time every time the use write new digit (You can also create a progress bar represent the timer and update it) or you can use simple Timer
When the time is finish you should perform the search operation and clear the last number in StringBuilder and Update UI
val channelNumber = StringBuilder()
val numberSearchTimer : CountDownTimer?
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
return when(keyCode){
KeyEvent.KEYCODE_1 -> {
cannelNumber.append("1")
perfomNumberSearch()
true
}
KeyEvent.KEYCODE_2 -> {
cannelNumber.append("2")
perfomNumberSearch()
true
}
...
else -> super.onKeyUp(keyCode, event)
}
}
private fun perfomNumberSearch() {
// Update UI With the new number
binding.searchChannelNumber.text = channelNumber.toString()
// Cancel the current time of it exists
if (numberSearchTimer != null) numberSearchTimer.cancel()
numberSearchTimer = object : CountDownTimer(1000, 100) {
override fun onTick(millisUntilFinished: Long) {
// Update UI With a progress until it perform search
// or replace CountDownTimer with Timer
}
override fun onFinish() {
changeChannelByNumber(channelNumber.toString())
// Clear the last search number after performing it
channelNumber.clear()
binding.searchChannelNumber.text = channelNumber.toString()
}
}.start()
}
I have a button in my activity. When the user clicks on the button, progress is shown and a request is sent to the server, after receiving a response to the request, the progress is hided and the next activity is opened.
However, if the user has time to press the button several times, two or more activities will open.
For preventing double click I use DebouncedOnClickListener. Here is my code:
abstract class DebouncedOnClickListener protected constructor() : View.OnClickListener {
private val minimumIntervalMillis: Long = 1000
private val lastClickMap: MutableMap<View, Long>
abstract fun onDebouncedClick(v: View?)
override fun onClick(clickedView: View) {
val previousClickTimestamp = lastClickMap[clickedView]
val currentTimestamp = SystemClock.uptimeMillis()
lastClickMap[clickedView] = currentTimestamp
if (previousClickTimestamp == null || abs(currentTimestamp - previousClickTimestamp) > minimumIntervalMillis) {
onDebouncedClick(clickedView)
}
}
init {
lastClickMap = WeakHashMap()
}
}
This method works in many cases. However, the request can be processed for an unknown amount of time. And the user can click on the button while the request is completed, the progress will be closed and the process of opening the next activity will start. I do not know how long a new activity can be launched, and at this moment another click on the button can occur, which will subsequently lead to the opening of two activities.
How can you avoid double clicking until the activity opens, please help me.
P.S. Even when I try to disable the button, double-clicking can still happen during the launch of the activity
Just paste this on button click
button.setEnabled(false);
You can use a boolean flag like following.
private val onClicked: Boolean = false
override fun onClick(clickedView: View) {
if(!onClicked) {
onClicked = true
// Do something
onClicked = false
}
}
Add required functions in Utils and wherever it's required call it by passing view as parameter.
const val DIGIT_THOUSAND = 1000
fun preventMultipleTap(view: View) {
preventMultipleTap(view, DIGIT_THOUSAND.toLong())
}
fun preventMultipleTap(view: View, delayInMillis: Long) {
view.isEnabled = false
view.postDelayed({ view.isEnabled = true }, delayInMillis)
}
I have a ViewModel in android and I am trying to validate whether the person has entered his name and age before moving on to the next page.
Here is my code:
fun onContinueClick() {
val navigateNextPage: (Int) -> Unit = lambda#{ validation ->
if (validation < 2)
return#lambda
nextPageUseCase().onEach {
_navigationNotify.value = it // Moving on to the next page
}
}
viewModelScope.launch {
var valid = 0
getName().collect { name ->
if (name != null) navigateNextPage(++valid)
}
getAge().collect { age ->
if (age != null) navigateNextPage(++valid)
}
}
}
Although this is working as expected, is it efficient to perform this operation? I think that if both the ++valid happen at the same time, I wouldn't be able to go to the next page.
I want to know how to synchronize the code to avoid that situation.
I'm trying to loop through items in an array and log the index of each item every few seconds when a button is pressed.
Whenever the button is pressed, I can't press other buttons or interact with the screen in any way until it's done looping through all the items.
How do I make this task run in the background so I can interact with other elements on the screen?
for(wallpaperUUID in p0.children){
var wallpaperUUIDString = wallpaperUUID.key.toString()
var indexOfImage: Int = wallpaperArray.indexOf(wallpaperUUIDString)
Log.i("Index of Image", indexOfImage.toString())
val time = measureTimeMillis {
runBlocking {
for(i in 1..100000) {
launch {
delay(2000L)
}
}
}
}
}
You're using runBlocking which means it blocks the current thread until its action is done.
What you need is asynchronous call for that you can use Job and CoroutineScope in a Dispatchers.Default
ex:
val coroutineScope = CoroutineScope(Dispatchers.Default)
coroutineScope.launch {
// Your code
}
You can also call coroutineScope.cancel() to cancel the work you're doing.
I have an app with a splashscreen, which stays for about 2 seconds.
After that, it switches to another activity A.
In A, I set a value in a SeekBar and after that, click a Button to confirm.
When I simply start a recorded Espresso test doing this, it tries to play while on the splashscreen. So when it tried to set the SeekBar value or click the Button, I get a NoMatchingViewException. So my first attempt at fixing this was to simply add a sleep(5000). This worked.
However, I don't want to put a manual sleep in after every Activity switch.
Because it seems like unnecessary code
Because it would mean unnecessary waiting time for running the test
The timing might be arbitrary and could be different for different devices
So I tried to check whether or not I'm in the right Activity/can see the right views. I did this using some SO links: Wait for Activity and Wait for View.
However, even that does not work 100%.
I have these two functions:
fun <T: AppCompatActivity> waitForActivity(activity: Class<T>, timeout: Int = 5000, waitTime: Int = 100) {
val maxTries = timeout / waitTime
var tries = 0
for(i in 0..maxTries) {
var currentActivity: Activity? = null
getInstrumentation().runOnMainSync { run { currentActivity = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(
Stage.RESUMED).elementAtOrNull(0) } }
if(activity.isInstance(currentActivity)) {
break
} else {
tries++
sleep(waitTime.toLong())
}
}
}
fun waitForView(
#IntegerRes id: Int,
waitMillis: Int = 5000,
waitMillisPerTry: Long = 100
): ViewInteraction {
// Derive the max tries
val viewMatcher = allOf(
withId(id),
isDisplayed()
)
val maxTries = waitMillis / waitMillisPerTry.toInt()
var tries = 0
for (i in 0..maxTries)
try {
tries++
val element = onView(viewMatcher)
element.check { view, noViewFoundException ->
if(view == null) {
throw noViewFoundException ?: Exception("TEST")
}
if(view.hasWindowFocus()) {
throw noViewFoundException ?: Exception("TEST2")
}
}
return element
} catch (e: Exception) {
if (tries == maxTries) {
throw e
}
sleep(waitMillisPerTry)
}
throw Exception("Error finding a view matching $viewMatcher")
}
Neither of those work 100%. Both of them seem to return within the timeout restrictions, and have "found" the activity/view. However, the expected view, e.g. a Button is not yet ready to perform, for example, element.perform(click()). It does not lead to a NoMatchingViewException, but it does not perform the click I did either. For the SeekBar, I use this:
private fun setSeekValue(seekBar: ViewInteraction, age: Int) {
val fullPercentage = .9f
val step = 1/99f
seekBar.perform(
GeneralClickAction(
Tap.SINGLE,
CoordinatesProvider { view ->
val pos = IntArray(2)
view?.getLocationOnScreen(pos)
FloatArray(2).apply {
this[0] = pos[0] + view!!.width * (.05f + fullPercentage*step*age)
this[1] = pos[1] + view.height * .5f
}
},
PrecisionDescriber {
FloatArray(2).apply {
this[0] = .1f
this[1] = 1f
}
},
InputDevice.SOURCE_MOUSE,
MotionEvent.ACTION_BUTTON_PRESS
)
)
}
However, when I use these functions and just put a very short sleep, e.g. sleep(100) after it, it works. This again however, would go against the three reasons listed above, which I'm trying to avoid here.
As you can see in the function waitForView, I tried to check if the View is "usable", using hasWindowFocus(). But this still does not perform the click, except for when I again put a sleep(80) or something after it. So it waits for the splashscreen to switch to A, finds the view it's looking for and then can't perform the click, except for when I wait a little bit.
I have also tried these functions of View:
isEnabled
isShown
visibility
getDrawingRect
isFocusable
isFocused
isLayoutDirectionResolved
Neither of them worked as I expected. With all of them, after the needed value was returned on the element.check part of waitForView, they would still not be accessible without putting a short sleep after.
Is there a way to reliably check if I can perform a click on a view/safely can perform ViewInteraction.perform()
Either by checking, if an activity is fully loaded to a point where its views are usable. Or by directly checking if a view is usable.