B4A Class [B4X] CallSubPlus - CallSub with explicit delay

Erel

Administrator
Staff member
Licensed User
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: http://www.b4x.com/android/forum/th...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

LucaMs

Expert
Licensed User
As you know, you can get a warning if you declare a Timer elsewhere than in Process_Globals.
Here you declare a Timer in a sub; I suppose that the warning is related to activities only, right?


[P.S. Nice: you use timers as keys of a map :D]
 

DonManfred

Expert
Licensed User
How to pass the parameter in ...Plus2 if the parameter is integer and not string?
B4X:
Starter.csu.CallSubPlus2(Me, "Sub_2", 1000, Array("Value 1", 0815)) 'run in one second
CallSubPlus2 is sending an Array of Object. It i not an Array as String.
 

LucaMs

Expert
Licensed User
I don't understand the problem.
@DonManfred code should work.

How to pass the parameter in ...Plus2 if the parameter is integer and not string?
@EvgenyB4A wants to call a sub like:
B4X:
Sub2(Param As Int)
DonManfred's code is not the solution, it can call this sub:
B4X:
Sub2(Param As Object)
    Dim Data() As Object = Param
    Log(Data(0) & tab & Data(1))
End Sub

The solution could be in my post #6.
 
Last edited:

Filippo

Expert
Licensed User
Thank Erel! :)
The class is so good, it could have been from me.:D
 

Filippo

Expert
Licensed User
Hi,

Here a small change.
I've added this code "If Not(IsPaused(rdd.Module)) Then " so as to avoid that if the activity is not active, no error occurs.

B4X:
'Class module
Sub Class_Globals
    Private RunDelayed As Map
    Type RunDelayedData (Module As Object, SubName As String, Arg() As Object, Delayed As Boolean)
End Sub

Public Sub Initialize

End Sub

'Similar to CallSubDelayed. This method allows you to set the delay (in milliseconds).
'Note that the sub name must include an underscore if compiled with obfuscation enabled.
Public Sub CallSubDelayedPlus(Module As Object, SubName As String, Delay As Int)
    CallSubDelayedPlus2(Module, SubName, Delay, Null) 
End Sub

'Similar to CallSubDelayed. This method allows you to set the delay (in milliseconds).
'Note that the sub name must include an underscore if compiled with obfuscation enabled.
'The target sub should have one parameter with a type of Object().
Public Sub CallSubDelayedPlus2(Module As Object, SubName As String, Delay As Int, Arg() As Object)
    PlusImpl(Module, SubName, Delay, Arg, True)
End Sub

'Similar to CallSub. This method allows you to set the delay (in milliseconds).
'Note that the sub name must include an underscore if compiled with obfuscation enabled.
Public Sub CallSubPlus(Module As Object, SubName As String, Delay As Int)
    CallSubPlus2(Module, SubName, Delay, Null) 
End Sub

'Similar to CallSub. This method allows you to set the delay (in milliseconds).
'Note that the sub name must include an underscore if compiled with obfuscation enabled.
'The target sub should have one parameter with a type of Object().
Public Sub CallSubPlus2(Module As Object, SubName As String, Delay As Int, Arg() As Object)
    PlusImpl(Module, SubName, Delay, Arg, False)
End Sub

Private Sub PlusImpl(Module As Object, SubName As String, Delay As Int, Arg() As Object, delayed As Boolean)
    If RunDelayed.IsInitialized = False Then RunDelayed.Initialize
    Dim tmr As Timer
    tmr.Initialize("tmr", Delay)
    Dim rdd As RunDelayedData
    rdd.Module = Module
    rdd.SubName = SubName
    rdd.Arg = Arg
    rdd.delayed = delayed
    RunDelayed.Put(tmr, rdd)
    tmr.Enabled = True
End Sub

Private Sub tmr_Tick
   Dim t As Timer = Sender
   Dim rdd As RunDelayedData = RunDelayed.Get(t)
   If Not(IsPaused(rdd.Module)) Then
     RunDelayed.Remove(t)
     t.Enabled = False
     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 If
End Sub
 
Last edited:

LucaMs

Expert
Licensed User
I've added this code "If Not(IsPaused(rdd.Module)) Then " so as to avoid that if the activity is not active, no error occurs.
If the Activity is not active, you might want to start it, using CallSubDelayed. I think you should add that check only to CallSub calls.
 
Last edited:

Filippo

Expert
Licensed User
If the Activity is not active, you might want to start it, using CallSubDelayed. I think you should add that check only to CallSub calls.
Did you test it?
I have tested it so and it works. ;)
 

LucaMs

Expert
Licensed User
Did you test it?
I have tested it so and it works. ;)
It should work for sure but...

Beh scrivo in italiano, poi FORSE traduco una sintesi, mi riesce meglio :)

Funziona di sicuro, la tua modifica; però, metti che tu, dall'activity Main, vuoi chiamare una routine dell'activity actTwo, devi chiamarla tramite CallSubDelayed, in modo che actTwo, che non è attiva, venga caricata.
Se usi la CallSubDelayed direttamente, tutto funziona. Se usi questa classe, dato che la actTwo non è attiva, la chiamata sarà ignorata, per via del
If Not(IsPaused(rdd.Module)

Sorry, in short:
It should work for sure but...
If you want, from Main, call a sub of another activitiy (actTwo), you can use CallSubDelayed and the call will have success (actTwo will be started).
If you add your change to this class, the call will be ignored, because actTwo is not in foreground.
 
Top