AndroidTV IPTV change channel by number input - android

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()
}

Related

Kotlin function to count win rate after given instruction

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", "")
}

Why does the coroutine break my app when using setOnTouchListener?

I want to call some code when the user touches the button. When the user stops pressing, the app should stop sending info to the server - chunks of audio. And I should use coroutines, I suppose. But the screen freezes and the app breaks.
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
runBlocking {
var job : Job? = null
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (isActive) { // cancellable computation loop
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
}
MotionEvent.ACTION_UP -> {
println("main: I'm tired of waiting!")
job?.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
else ->
Log.i("else", "else")
}
}
return v?.onTouchEvent(event) ?: true
}
})
This happens when using the onTouch with MotionEvent, I suppose. What's wrong? What do I suppose to do?
Never use runBlocking in an app. It's for testing or for the main function in native Java console apps with no GUI.
runBlocking defeats the purpose of using coroutines and blocks the thread it was called from, causing your asynchronous work to block the thread. In this case, since it is called from the main thread, it blocks the main thread, so your UI will freeze and it will be impossible for the ACTION_UP signal to ever be received.
To fix your current code, you should launch a coroutine from lifecycleScope instead of using runBlocking. Also, you shadowed the job variable inside your ACTION_DOWN section. You need to use your existing variable to be able to cancel it outside that scope.
override fun onTouch(v: View, event: MotionEvent): Boolean {
var job : Job? = null
when (event.action) {
MotionEvent.ACTION_DOWN -> {
val startTime = System.currentTimeMillis()
job = lifecycleScope.launch {
var nextPrintTime = startTime
var i = 0
while (true) { // cancellable computation loop
yield()
// print a message twice a second
if (System.currentTimeMillis() >= nextPrintTime) {
println("job: I'm sleeping ${i++} ...")
nextPrintTime += 500L
}
}
}
}
MotionEvent.ACTION_UP -> {
lifecycleScope.launch {
println("main: I'm tired of waiting!")
job?.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
}
else -> Log.i("else", "else")
}
}
However, your use case seems to be just starting or stopping something from this listener. I'm not sure it makes sense to use coroutines for that. Maybe your class that handles the uploading work would be using coroutines internally, but it doesn't necessarily have to be started or stopped using a suspend function.

send text data when user finishes typing instead of on text changed in kotlin android

I want to send an text to firebase database when the user has finished typing (I use AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED). I don't want it to send text every time the user types a letter because that would result in A LOT of text data, however I don't want them to have to hit the enter button either.
Is there a way so I can detect when the user has finished typing and then send the text data?
Using Kotlin here! Thanks
I don't want to use button
#Inject lateinit var interactor: InteractorAccessibilityData
override fun onAccessibilityEvent(event: AccessibilityEvent) {
when (event.eventType) {
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {
val data = event.text.toString()
if (data != "[]") {
interactor.setDataKey("${getDateTime()} |(TEXT)| $data")
Log.i(TAG, "${getDateTime()} |(TEXT)| $data")
}
}
}
}
In InteractorAccessibilityData.kt->
override fun setDataKey(data: String) {
if (firebase.getUser()!=null) firebase.getDatabaseReference(KEYS).child(DATA).push().child(TEXT).setValue(data)
}
Using a Timer would be the best way.
Another way would be checking the length of the text.
say after an initial length of 10 sends data to firebase and after that every +2 or +3 length.
Introduce a variable outside this function which counts the timer.
// declare this outside onAccessibilityEvent function
private val timer = object : CountDownTimer(50000, 1000) {
override fun onTick(millisUntilFinished: Long) {
//use it if you want to show the timer
}
override fun onFinish() {
//send data to firebase, like
//if (firebase.getUser()!=null) firebase.getDatabaseReference(KEYS).child(DATA).push().child(TEXT).setValue(data)
}
}
//function to start or cancel the timer
private fun x(p: Int = 0) {
when (p) {
0 -> timer.start()
1 -> timer.cancel()
}
}
This method doesn't look efficient but this is how I would do it if I have no other options.
The Best you can do is send text after every word (by using spaces)
Like This
#Inject lateinit var interactor: InteractorAccessibilityData
override fun onAccessibilityEvent(event: AccessibilityEvent) {
when (event.eventType) {
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> {
val data = event.text.toString()
if (data.contain( )) {
interactor.setDataKey("${getDateTime()} |(TEXT)| $data")
Log.i(TAG, "${getDateTime()} |(TEXT)| $data")
}
}
}
}

How to make kotlin flow wait till end before Terminal operator is executed

/**
* Does some work and return true to denote success
* false to denote failure
*/
suspend fun doWork(): Boolean {
val processStatus = processWork()
return processStatus.filter { status ->
status == ProcessStatus.SUCCESS
|| status == ProcessStatus.FAILURE
}.map { filteredStatus ->
filteredStatus == ProcessStatus.SUCCESS
}.single()
}
/**
* Cretaes a channel in which different status will be offered
*/
suspend fun processWork(): Flow<ProcessStatus> {
val channel = BroadcastChannel(Channel.BUFFERED)
doThework(channel)
return channel.asFlow()
}
/**
* Does some work in background thread
*/
fun doThework(channel: BroadcastChannel) {
SomeSope.launch {
//Cretae a coroutine
channel.offer(ProcessStatus.Status1)
channel.offer(ProcessStatus.Status2)
channel.offer(ProcessStatus.Status3)
channel.offer(ProcessStatus.Status4)
channel.offer(rocessStatus.SUCCESS)
channel.close()
}
}
Above is a simplified version of my code.
What I want to do is, make doWork() wait till all the values are emmited and finally return a boolean based on last ProcessStatus.SUCCESS or ProcessStatus.FAILURE.
Right now, what is happening with the above code is, as soon as the processWork() returns the flow. doWork() calls all the operators including the single() and since, the work is still in progress ProcessStatus.FAILURE or ProcessStatus.SUCCESS is still not emmited, making it give an exception.
How can I make the doWork() return statement wait and return only when the flow is Complete?
Edit 1:
Reason, I have to go with the channel is because, this is part of an Android code & the channel.offer() is not actually a new co-routine like in example above, but is being called from a Android BroadcastReceiver.
Since flow is cold, I didn't want user leaving the Activity to stop the task from being completed and notified.
Looks like you can use toList method to collect all values before processing them:
suspend fun doWork(): Boolean {
val processStatus = processWork().toList()
return processStatus.filter { status ->
status == ProcessStatus.SUCCESS
|| status == ProcessStatus.FAILURE
}.map { filteredStatus ->
filteredStatus == ProcessStatus.SUCCESS
}.single()
}

How to return a default value when an Observer subscribes to my Observable

I'm still a rookie in RxJava and trying to implement something using it.
I have notifications counter in my Android application and I want to fetch the count from an API every five minutes, and I want to share this count to multiple activities. So I want to store the count in a pipeline so that each activity listens to that pipeline can get the last emitted value.
I started with an Object class and added two BehaviorSubjects one for providing the default value and the second to emit true value when the 5 minutes finish "to tell the activity that's listening if there's any" that you should fetch new count.
But the problem is that when I listen to the BehaviorSubject it emits all the data in the pipeline! How can I get only that last one
object RxBus {
#JvmStatic
var count: Int = 0
#JvmStatic
private var fetchData: Boolean = true
#JvmStatic
private var behaviorSubject: BehaviorSubject<Any> = BehaviorSubject.createDefault(CountEvent(count))
#JvmStatic
private var fetchSubject: BehaviorSubject<Any> = BehaviorSubject.createDefault(FetchEvent(fetchData))
private val FIVE_MINUTES: Long = 1000 * 60 * 5
init {
fixedRateTimer("countTimer", false, 0L, FIVE_MINUTES) {
fetchSubject.onNext(FetchEvent(true))
}
}
fun publish(event: Any) {
if (event is CountEvent) {
Log.d(TAG, "eventCount = ${event.count}")
count = event.count
fetchData = false
}
}
fun listenToLoadEvent(): Observable<FetchEvent> {
return fetchSubject.ofType(FetchEvent::class.java)
}
fun listen(): Observable<CountEvent> {
return behaviorSubject.ofType(CountEvent::class.java)
}
Please note that I want to listen to both Subjects in onResume of each activity.. Is there any way to do that in a better way? And if not can you help me to just get the last emitted item only.
Thanks in Advance
class CountEvent(val value: Int)
object CounterManager {
val counterDataSource = BehaviorProcessor.create<CountEvent>()
val refreshNotifier = interval(5, TimeUnit.MINUTES).publish()
.refCount()
fun publish(event: Any) {
if (event is CountEvent) {
counterDataSource.onNext(event)
}
}
}

Categories

Resources