So I'm writing a piece of code to add a pause and resume functionality into the abstract class CountDownTimer (Android.os.CountDownTimer). I'm using the functionality only within this one activity and therefore am just using an object expression to create an anonymous class called sequencetimer. The code looks something like this:
public var sequencetimer = object : CountDownTimer(30000, 1000) {
public var timeremaining : Long = 0
override fun onTick(millisUntilFinished: Long) {
findViewById<TextView>(R.id.textView8).apply {
text = ("seconds remaining: " + millisUntilFinished / 1000)
}
}
override fun onFinish() {
findViewById<TextView>(R.id.textView8).apply {
text = "done"
}
}
public fun pauseTimer() {
timeremaining = findViewById<TextView>(R.id.textView8).text as Long
cancel()
}
public fun resumeTimer() {
onTick(timeremaining)
start()
}
}
Now I want to call the pauseTimer and resumeTimer functions that I added whenever my activity pauses or resumes like so:
override fun onPause() {
super.onPause()
sequencetimer.pauseTimer()
}
override fun onResume() {
super.onResume()
sequencetimer.resumeTimer()
}
The code however keeps throwing me an unresolved reference error for the functions pauseTimer() and resumeTimer(), even though the sequencetimer object is declared within the activity class and I can execute all the other functions such as sequencetimer.onTick() and sequencetimer.start() (however I can also not acces the public var timeremaining). Does anyone have any idea what the issue here is? Or is it simply not possible to expand/extend an abstract class within an anonymous object expression (I would have expected android studio to then throw some type of error)?
As you said yourself: you create anonymous class. That means this class doesn't exists from the developer point of view and the type of sequencetimer is just CountDownTimer which doesn't have pauseTimer() and resumeTimer() functions. You need to create a regular, named class, so it can be referenced in the code.
Alternatively, you can make sequencetimer a private var. In this case Kotlin assumes this is internal stuff and provides some kind of a shortcut. It conditionally permits this kind of operation, even if normally it should not be possible. This behavior is described in the docs: https://kotlinlang.org/docs/object-declarations.html#using-anonymous-objects-as-return-and-value-types
There are some issues with your code.
First issue I see is that public var sequencetimer. With kotlin keywords public, protected, internal you declare to compiler that sequencetimer would be accessible outside of your android activity class's scope. But object created as anonymous class in your activity class. Kotlin compiler decides that the only solution is marking sequencetimer as CountDownTimer.
// byte code
public final getSequencetimer()Landroid/os/CountDownTimer;
#Lorg/jetbrains/annotations/NotNull;()
Secondly, resumeTimer() will call onTick(12345L) once and restart the timer from the millisInFuture 30_000L.
It would be better you create a new count down timer for the remaining time.
And please, decrease the count down interval or you will see the seconds inconsistent 30, 28, 27, 26, 26, 24, ...
Hope, it helps.
Related
Ive been struggling with this for quite some time now, perhaps someone could help...
I have this function in my class under test:
fun launchForegroundTimer(context: Context) {
helper.log("AppRate", "[$TAG] Launching foreground count down [10 seconds]")
timerJob = helper.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), context, this::showGoodPopupIfAllowed)
}
So in that function, I first write to some log and then I call a coroutine function that expects a Dispatcher param, how long to wait before running the action, Any object that I would like to pass on to the action and the actual action function that is invoked when time has passed.
So in this case, the this::showGoodPopupIfAllowed which is a private method in the class, gets called when the 10,000 ms have passed.
Here is that function:
private fun showGoodPopupIfAllowed(context: Context?) {
if (isAllowedToShowAppRate()) {
showGoodPopup(context)
}
}
In that first if, there are a bunch of checks that occur before I can call showGoodPopup(context)
Now, here is the helper.launchActionInMillisWithBundle function:
fun <T> launchActionInMillisWithBundle(dispatcher: CoroutineContext, inMillis: Long, bundle: T, action: (T) -> Unit): Job = CoroutineScope(dispatcher).launchInMillisWithBundle(inMillis, bundle, action)
And here is the actual CoroutineScope extension function:
fun <T> CoroutineScope.launchInMillisWithBundle(inMillisFromNow: Long, bundle: T, action: (T) -> Unit) = this.launch {
delay(inMillisFromNow)
action(bundle)
}
What I am trying to achieve is a UnitTest that calls the launchForegroundTimer function, calls the helper function with the appropriate arguments and also continue through and call that lambda showGoodPopupIfAllowed function where I can also provide mocked behaviour to all the IF statments that occur in isAllowedToShowAppRate.
Currently my test stops right after the launchActionInMillisWithBundle is called and the test just ends. I assume there is no real call to any coroutine because I am mocking the helper class... not sure how to continue here.
I read a few interesting articles but none seems to resolve such state.
My current test function looks like this:
private val appRaterManagerHelperMock = mockkClass(AppRaterManagerHelper::class)
private val timerJobMock = mockkClass(Job::class)
private val contextMock = mockkClass(Context::class)
#Test
fun `launch foreground timer`() {
every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, any()) } returns timerJobMock
val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
appRaterManager.launchForegroundTimer(contextMock)
verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
}
I'm using mockk as my Mocking lib.
AppRaterManager is the class under test
I'd like to also mention that, in theory I could have moved the coroutine invocation outside the class under test. So an external class like activity.onResume() could launch some sort of countdown and then call directly a function that checks showGoodPopupIfAllowed(). But currently, please assume that I do not have any way to change the calling code so the timer and coroutine should remain in the class under test domain.
Thank you!
Alright, I read a bit deeper into capturing/answers over at https://mockk.io/#capturing and saw there is a capture function.
So I captured the lambda function in a slot which enables me invoke the lambda and then the actual code continues in the class under test. I can mock the rest of the behavior from there.
Here is my test function for this case (for anyone who gets stuck):
#Test
fun `launch foreground timer, not participating, not showing good popup`() {
val slot = slot<(Context) -> Unit>()
every { appRaterManagerHelperMock.launchActionInMillisWithBundle(Dispatchers.Main, TimeUnit.SECOND.toMillis(10), contextMock, capture(slot)) } answers {
slot.captured.invoke(contextMock)
timerJobMock
}
every { appRaterManagerHelperMock.isParticipating() } returns false
val appRaterManager = AppRaterManager(appRaterManagerHelperMock)
appRaterManager.launchForegroundTimer(contextMock)
verify(exactly = 1) { appRaterManagerHelperMock.log("AppRate", "[AppRaterManager] Launching foreground count down [10 seconds]") }
verify(exactly = 1) { appRaterManagerHelperMock.isParticipating() }
verify(exactly = 0) { appRaterManagerHelperMock.showGoodPopup(contextMock, appRaterManager) }
}
So what's left now is how to test the coroutine actually invokes the lambda after the provided delay time is up.
The count down timer class in Kotlin / Java is an abstract class hence we can't create an instance of it , but while viewing a tutorial of count down timer in Kotlin , this code went straight over my head
private var restTimer : CountDownTimer ? = null
restTimer = object:CountDownTimer(10000,1000){
override fun onTick(millisUntilFinished: Long) {
// some code
}
override fun onFinish() {
// some code
}.start()
Are we creating an object of this abstract class and why is "object" keyword mentioned here ?
I recommend you to look at Object expressions and declarations.
If you check the CountDownTimer class is :
public abstract class CountDownTimer {
....
/**
* #param millisInFuture The number of millis in the future from the call
* to {#link #start()} until the countdown is done and {#link #onFinish()}
* is called.
* #param countDownInterval The interval along the way to receive
* {#link #onTick(long)} callbacks.
*/
public CountDownTimer(long millisInFuture, long countDownInterval) {
mMillisInFuture = millisInFuture;
mCountdownInterval = countDownInterval;
}
So it is using this constructor to create an anonymous implementation. It is not creating an instance but object can access members, methods without create an instance.
This is what you do in Java
CountDownTimer countDownTimer = new CountDownTimer(long duration, long interval) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
}
};
countDownTimer.start();
So what you are doing is create an anonymous class and implement the necessary methods.
Object Expressions
Object expressions create objects of anonymous classes, that is, classes that aren't explicitly declared with the class declaration. Such classes are handy for one-time use. You can define them from scratch, inherit from existing classes, or implement interfaces. Instances of anonymous classes are also called anonymous objects because they are defined by an expression, not a name.
Object Declarations
Singleton can be useful in several cases, and Kotlin (after Scala) makes it easy to declare singletons.
This is called an object declaration, and it always has a name following the object keyword. Just like a variable declaration, an object declaration is not an expression, and cannot be used on the right-hand side of an assignment statement.
Object declaration's initialization is thread-safe and done at first access
Like yours:
restTimer = object:CountDownTimer(10000,1000){
override fun onTick(millisUntilFinished: Long) {
// some code
}
override fun onFinish() {
// some code
}.start()
source: https://kotlinlang.org/docs/object-declarations.html#object-declarations
private val timer = object : CountDownTimer(result, 1000) {
override fun onFinish() {
//delete the database entry
}
override fun onTick(millisUntilFinished: Long) {
//more code
}
}
As far as my knowledge in kotlin, object gets called before the result gets assigned a value
Initially, the result value is 0, then it gets updated in another function, but the timer gets called with result value as 0.
So what should be the best replacement for object here?
You can keep object, you just need to change order of initialization. One way would be to use by lazy, like this:
var result = 0L
private val timer: CountDownTimer by lazy {
object : CountDownTimer(result, 1000) {
override fun onFinish() {
// delete the database entry
}
override fun onTick(millisUntilFinished: Long) {
// more code
}
}
}
// 'init' block just as an example; the below code works anywhere
// such as in onCreate(), onStart() or wherever
init {
result = 1000
// 'timer' is initialized with result=1000 and then started
timer.start()
}
lazy is a so called property delegate, you can read more about it in the official docs for example.
I don't see how this is related to Kotlin?
You have a member val: timer
It is initialized when the object, the val resides in, is created.
So the problem is not what syntax you are using. It is the time you create timer. If you know when you are going to use it, and you are sure that by this time the result will be intialized, you can use lazy initialization.
I am developing in Android, I want to use HandlerThread to start a countdownTimer like the following code.
private var bgHandlerThread: HandlerThread? = HandlerThread("MyHandlerThread")
private fun startTimer() {
bgHandlerThread = HandlerThread("MyHandlerThread")
bgHandlerThread!!.start()
val bgHandler = Handler(bgHandlerThread!!.looper)
bgHandler.post {
countDownTimer = object : CountDownTimer(COUNT_DOWN_MAX_TIME.toLong(), COUNT_DOWN_INTERVAL.toLong()) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "time:$millisUntilFinished ")
}
override fun onFinish() {
Log.d(TAG, "Timer countDown Finish ")
}
}.start()
}
}
But it show the following error
Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
com.xx.Test.startTimer
So I want to move the startTimer() to the onLooperPrepared.
In Java, it is like the following:
#Override
public void onLooperPrepared() {
}
But I did not see the method in kotlin.
Hot to use the onLooperPrepared in kotlin ?
Thanks in advance.
The onLooperPrepared() method is a protected method inside of the HandlerThread.java class, with no default implementation. If you want to use it in your code, you'd need to override it in a class that extends the HandlerThread class
class YourHandlerThread(val name = "MyHandlerThread") : HandlerThread(name) {
override fun onLoopPrepared() {...}
...
}
Not sure what are you trying to achieve, however if you want to execute something on other thread without blocking the main thread I HIGHLY recommend to start using coroutines, this is the new and recommended way to handle multi threading in kotlin.
To use them you will need to add the following dependencies to your gradle file:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
Coroutines are a big box of different chocolates so you should take your time to learn the capabilities, since it is a different mechanism compared to java threads. However for example to run your timer on a different thread is as easy as:
// it doesn't matter what thread you are currently on
CoroutineScope(Dispatchers.Default).launch {
// the code in brackets runs on a separate thread (read about dispatchers for more information) without blocking the current thread
countDownTimer = object : CountDownTimer(COUNT_DOWN_MAX_TIME.toLong(), COUNT_DOWN_INTERVAL.toLong()) {
override fun onTick(millisUntilFinished: Long) {
Log.d(TAG, "time:$millisUntilFinished ")
}
override fun onFinish() {
Log.d(TAG, "Timer countDown Finish ")
}
}.start()
}
This code will work without any problems since Log allows posting from different threads, however this will not work in case when you want to update UI, since UI can be updated only from the main thread. Before coroutines this was a pain in the ass since you had to make a handler and always send/receive messages with limited capability on cancelling the currently running task. With coroutines this is as easy as:
withContext(Dispatchers.Main) {
// do your ui updates here
}
This snippet can be used inside of your coroutine to switch the context, you don't need anything more, once you switch the context to main thread you can do all the UI updates.
I have a shared piece of code in a function, that should be accessed one at a time . I used mutex.lock/unlock to achieve this while working with coroutines.
public class TestAbc {
val mutex = Mutex()
suspend fun testfunction() {
mutex.lock()
arrayList.add("abc")
hashmap.put("abc", "efg")
mutex.unlock()
}
}
public class InitiatorClass{
val testAbc: TestAbc = TestAbc()
public fun startJob() {
GlobalScope.launch {
testAbc.testfunction()
}
}
}
I tested this by calling Start Job Function twice from a java class ,from different threads.
wanted only one coroutine to access critical-section at once, with help of mutex but its not working.I see multiple coroutines entering the lock.
My Bad, I was creating Multiple Instance of the Class InitiatorClass.
When i corrected it , everything works Fine .