B4A Class [B4X] CallSubPlus - CallSub with explicit delay

In many cases you need to run a task in a few seconds. The solution for such cases is to create a timer and then execute the task in the timer's Tick event. This is good for a single task. However if you need to run multiple tasks then it becomes difficult to maintain.

This small class makes it much simpler. You just need to call CallSubPlus with the target sub and the delay.
Internally it uses a (one shot) timer together with CallSubDelayed or CallSub.

There are four methods:
CallSubPlus, CallSubPlus2, CallSubDelayedPlus and CallSubDelayedPlus2.
CallSubPlus and CallSubDelayedPlus should be used with subs with no parameters. The others should be used with subs with a single parameter. The parameter type must be an array of objects.

For example:
B4X:
Sub Activity_Click
   Log($"Click: $Time{DateTime.Now}"$)
   Starter.csu.CallSubPlus(Me, "Sub_1", 2000) 'run in two seconds
   Starter.csu.CallSubPlus2(Me, "Sub_2", 1000, Array("Value 1", "Value 2")) 'run in one second
End Sub

Sub Sub_1
   Log($"Sub_1: $Time{DateTime.Now}"$)
End Sub

Sub Sub_2(args() As Object)
   Log($"Sub_2: $Time{DateTime.Now}, arg(0)=${args(0)}"$)
End Sub

csu is declared in the Starter service:
B4X:
Sub Process_Globals
   Public csu As CallSubUtils
End Sub

Sub Service_Create
   csu.Initialize
End Sub

Notes

- Make sure to include an underscore in the target subs names if you intend to compile with obfuscation.
- CallSubDelayed will start the target service or activity if needed (see this tutorial for more information: https://www.b4x.com/android/forum/t...etween-activities-and-services.18691/#content).
- You can safely call multiple subs. This can be useful to break a long task into smaller tasks.
- This class can also be used with B4i and B4J.
- Add this class to your project with Project - Add Existing Module.
 

Attachments

  • CallSubUtils.bas
    2.5 KB · Views: 1,737

pesquera

Active Member
Licensed User
Longtime User
- Make sure to include an underscore in the target subs names if you intend to compile with obfuscation.
What could happen if not? I must to use CallSubDelayedPlus and wanted to know more about that

I noticed on ClientKVS Class that you does not include an underscore on the Sub HandleQueue, and seems to be fine compiling with obfuscation
B4X:
csu.CallSubDelayedPlus(Me, "HandleQueue", 30000)
 

Swissmade

Well-Known Member
Licensed User
Longtime User
Hi
I know this is B4A but is this also working for B4J??
 

DonManfred

Expert
Licensed User
Longtime User

Dave O

Well-Known Member
Licensed User
Longtime User
This is very handy when dealing with animations.

For example, when the user opens the nav drawer and taps an item, the nav drawer typically takes ~100ms to close. When I tried updating the UI on the activity, it caused the drawer animation to stutter (because they were happening at the same time).

Using CallSubDelayedPlus, however, I give it a 150ms delay and everything is smoothy goodness. :)

Thanks Erel!
 

Stern0m1

Member
Licensed User
Thats what I have been doing. I think a more efficient way would just be a command to cancel. There should be a way to timer.enabled = false in the class. I just dont know anything about classes.

Thanks
 

ilan

Expert
Licensed User
Longtime User
hi,

i have a question please.

i see now that in the CallSubUtils Class in tmr_Tick
when rdd.Delayed is false then the sub will be called with CallSub and not CallSubDelayed. that make sense because this is what we want. (use CallSub and not CallSubDelayed, only in speciffic cases like calling a sub inside the same module)

but my question is, if you call a sub with CallSub and the Module is Paused then you may get a crash right?

should there not be an if statement before trying to call that sub??

this is the original code:

B4X:
Private Sub tmr_Tick
    Dim t As Timer = Sender
    t.Enabled = False
    Dim rdd As RunDelayedData = RunDelayed.Get(t)
    RunDelayed.Remove(t)
    If rdd.Delayed Then
        If rdd.Arg = Null Then
            CallSubDelayed(rdd.Module, rdd.SubName)
        Else
            CallSubDelayed2(rdd.Module, rdd.SubName, rdd.Arg)
        End If
    Else
        If rdd.Arg = Null Then
            CallSub(rdd.Module, rdd.SubName)
        Else
            CallSub2(rdd.Module, rdd.SubName, rdd.Arg)
        End If
    End If
End Sub

this is how i think it should look like (but i may be wrong)

B4X:
Private Sub tmr_Tick
    Dim t As Timer = Sender
    t.Enabled = False
    Dim rdd As RunDelayedData = RunDelayed.Get(t)
    RunDelayed.Remove(t)
    If rdd.Delayed Then
        If rdd.Arg = Null Then
            CallSubDelayed(rdd.Module, rdd.SubName)
        Else
            CallSubDelayed2(rdd.Module, rdd.SubName, rdd.Arg)
        End If
    Else
        If IsPaused(rdd.Module) Then Return 'CHECK FIRST IF MODULE IS NOT PAUSED
        If rdd.Arg = Null Then
            CallSub(rdd.Module, rdd.SubName)
        Else
            CallSub2(rdd.Module, rdd.SubName, rdd.Arg)
        End If
    End If
End Sub
 

ilan

Expert
Licensed User
Longtime User
CallSub will never crash because of a paused activity.

i could reproduce it in this simple example.

the crash is related to the timers in CallSubUtils

when you intialize callsubuitls in the starter service and then call it from 1 activity but before the timer tick was called you switch to another activity you may get this error:

LogCat connected to: 4b2f02ef
--------- beginning of main~i:** Service (firebasemessaging) Start **
Message arrived
Message data: {body=נא לבדוק שאתם בחשבון הראשי עבודה מס 1, title=נעלמו לכם המשמרות? יש פתרון}
--------- beginning of system
Copying updated assets files (2)
** Service (starter) Create **
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
Billing service disconnected.
Billing service disconnected.
Billing service connected.
Checking for in-app billing 3 support.
In-app billing version 3 supported for www.sagitalcashlite.net
Subscriptions AVAILABLE.
Billing service connected.
Checking for in-app billing 3 support.
In-app billing version 3 supported for www.sagitalcashlite.net
Subscriptions AVAILABLE.
Ignoring event: manager_billingsupported
Ignoring event: manager_billingsupported
** Activity (main) Pause, UserClosed = false **
** Activity (act2) Create, isFirst = true **
** Activity (act2) Resume **
** Activity (act2) Pause, UserClosed = true **
** Activity (main) Create, isFirst = false **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (act2) Create, isFirst = false **
** Activity (act2) Resume **
** Activity (act2) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (act2) Create, isFirst = false **
** Activity (act2) Resume **
** Activity (act2) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (act2) Create, isFirst = false **
** Activity (act2) Resume **
** Activity (act2) Pause, UserClosed = false **
** Activity (main) Create, isFirst = false **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
java.lang.RuntimeException: java.io.EOFException
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:165)
at anywheresoftware.b4a.objects.Timer$TickTack.run(Timer.java:105)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: java.io.EOFException
at java.io.DataInputStream.readByte(DataInputStream.java:77)
at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:335)
at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.ShellBA.raiseEvent2(ShellBA.java:134)
... 8 more

what i did to solve it i check if the activity is paused, if true then it wont be called with callsub.
i also stop all timers and delete them when i exit my app. this function is important.

B4X:
Public Sub StopAll
        If RunDelayed.IsInitialized Then
            For Each tmr As Timer In RunDelayed.Keys
                tmr.Enabled = False
            Next
            RunDelayed.Clear
        End If       
End Sub

let say i want to show an ad in 2 seconds so i call csu.... now if the user exit the app then it is not relevant anymore to show that ad so i stop all timers and clear the map otherwise when he will return the timer event will be raised and i dont want that. so the StopAll function is important is some cases.
 

Attachments

  • test.zip
    5.3 KB · Views: 321

ilan

Expert
Licensed User
Longtime User
I don't see any crash here (tested in debug mode). I pressed on Show msg and then switched activity.

The error message you posted looks like a debugger error. Do you see an error in release mode?

in release mode i cant reproduce it but yesterday i am sure i got the same error in release mode.
when i returned to the app i was thrown out and i saw the same error (something with the timer in csu class)

i need to mention that i exit the app with home button. i am not sure if it is related to that.
will make more tests and let you know if i found something.

thanx, ilan
 

ilan

Expert
Licensed User
Longtime User
ok i got a crash on release mode on my app, this is the log:

** Activity (main) Create, isFirst = false **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
** Activity (menu) Create, isFirst = false **
6
jd: 2457836.8628442595
Freitag:
18:54 | 11:42:29
zmanit:1
shabbat = false
bug fixed
XXXX USERID =
0
** Activity (menu) Resume **
adOut: false
START LOAD TIMER
ad failed to load
3
######STARTINT: 1
######HDT: 03/2017
received
######STARTINT: 1
######HDT: 04/2017
######STARTINT: 1
######HDT: 03/2017
######STARTINT: 1
######HDT: 02/2017
######STARTINT: 1
######HDT: 01/2017
######STARTINT: 1
######HDT: 02/2017
######STARTINT: 1
######HDT: 03/2017
######STARTINT: 1
######HDT: 02/2017
callsubutils_tmr_tick (java line: 181)
java.lang.NullPointerException: Attempt to read from field 'boolean www.sagitalcashlite.net.callsubutils$_rundelayeddata.Delayed' on a null object reference
at www.sagitalcashlite.net.callsubutils._tmr_tick(callsubutils.java:181)
at java.lang.reflect.Method.invoke(Native Method)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:179)
at anywheresoftware.b4a.objects.Timer$TickTack.run(Timer.java:105)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)

EDIT: it could be because the csu was intialized in activity main, i moved it to starter service let see if i get that crash again.

btw is there a reason why the CallSubUtils "Library" (not class) does not compile on debug mode?
 
Last edited:
Top