Android Question Can a Receiver call code in rest of app?

Sandman

Expert
Licensed User
Longtime User
I have a receiver for geofence events. I see this comment in the receiver module:
B4X:
'Do not assume that anything else, including the starter service, has run before this method.
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)

Does this mean that I can't access anything else in the app from the receiver?

In my app, I have a set of classes that make out the actual app, and a couple of the classes are declared in Starter. Before I can act on anything in the receiver I need to call some code in the classes declared in Starter. How should I do this? (The app is using B4XPages and I don't think it would be a problem to declare the classes in B4XMainPage instead, if I just could count on it always being there when the receiver is executed.)

Sorry if I'm missing something obvious, I'm failing finding anything to read on this topic.
 

Sandman

Expert
Licensed User
Longtime User
And if it isn't True, then what? Should I do a small loop and wait for it to be automatically created and repeatedly check for it, and when it is available do my stuff with it? Or do I need to do something for it to be created?
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
I've done some testing. I used this (somewhat sloppy) test code:
B4X:
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)

    Log("Receiver_Receive got a geofence event")

    Dim loopcounter As Int = 0
    Dim bp As Boolean = B4XPages.IsInitialized
    Dim start As Long = DateTime.Now
    
    Do While Not(B4XPages.IsInitialized)
        Log("Loopcounter: " & loopcounter)
        loopcounter = loopcounter + 1
        Sleep(1000)
    Loop
    
    Log("Loop done")
    
    Dim total As Long = (DateTime.Now - start) / 1000

    If bp Then
        ToastMessageShow("B4XPages available from start", False)
        Log("B4XPages available from start")
    Else
        ToastMessageShow("B4XPages available after " & total, False)
        Log("B4XPages available after " & total & " sec")
    End If

    ' Standard geofence code here, removed in this code block because it's not relevant

End Sub

After putting the app in the background and making sure it doesn't run, I trigger a geofence event by changing the location. I can see the initial log message confirming that the receiver got called. And then it goes into a never-ending loop. Or, rather, I imagine it's never-ending. When it's counted for ten minutes I've killed the app, because it just doesn't seem very reasonable that B4XPages would randomly be available after longer than that.

As far as I can tell, I can't assume that the rest of the app will automatically be available, not even if I wait for it.

So now the question is how to start B4XPages and the rest of the app. Is that possible to do? Please note that I have no interest in bringing the app to front, I just want to access code in other modules.

Or should we declare enough classes in the receiver to make a duplicate version of the whole app - but a very stripped down version, just enough for the receiver to do its job?


An observation: It's interesting that the receiver isn't killed. It was my understanding that they were meant to be very short-lived and should expect to not live more than 10 seconds. Erel has said that he has seen them live longer, but ten minutes is very much longer, so that's surprising...
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
I'm really struggling here.

I've got a very basic KVS class that usually are declared in Starter. As nothing is available from the receiver, I've also declared the KVS class in the receiver. When I init the KVS class from the receiver I keep getting crashes like this from the KVS class:
B4X:
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean anywheresoftware.b4a.sql.SQL.IsInitialized()' on a null object reference

Turns out the KVS class can't see the declaration in its own class:
B4X:
Sub Class_Globals
    Public kvsDatabase As SQL
End Sub

If I try to access that kvsDatabase it's always Null, which means that I can't even .Initialize(...) it.

However, if I move the kvsDatabase declaration into each sub in the KVS class, things work as expected. But surely that's not how it's meant to work?

Is this a bug with B4X or am I missing something crucial? (Things worked just fine before the split to service+receiver.)
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
This seems solved. I got sidetracked by the suggestion to check for B4XPages.IsInitialized. As far as I can tell, that's not part of the solution. The solution is to start Starter, let it do its internal things and then set a variable to true. In the receiver we wait for that flag to be true, and when that happens, we know Starter is up and running and ready to be used.

Big thanks to @Jmu5667 who helped me reason about this and came up with the actual solution. :)

This works:
B4X:
'Called when an intent is received.
'Do not assume that anything else, including the starter service, has run before this method.
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)
    CallSubDelayed(Me, "waiterSub")
End Sub

Private Sub waiterSub
    
    Dim start As Long = DateTime.Now

    If IsPaused(Starter) Then StartService(Starter)

    Do While Not(Starter.initDone)
        Sleep(0)
    Loop
    
    Log("Starter available after " & (DateTime.Now - start) & " ms")
    
    Log(Starter.CLKVS.Get("mykey")) ' Proof it works

    ' Do things
End Sub
 
Upvote 0

Alessandro71

Well-Known Member
Licensed User
Longtime User
Good, this restores the concept of "Starter is the single entry point".
Is there any reason waiterSub is called by CallSubDelayed instead of called by name directly?

Have you tested this on Android 13 also? I tried a similar trick for my app also some time ago, but came up with a "can't start foreground service" or similar error, and gave up.

And, by the way, this does not start B4XPages as well, doesn't it?
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
I tried a similar trick for my app also some time ago, but came up with a "can't start foreground service" or similar error

Actually, as of Android 12, there is only a handful of exceptions that allow a Receiver to start a foreground service or activity. The exemptions are listed here:
and gave up.
Two if the simpler methods are (*):
Both need user intervention.

(*) No clue how these would impact Play Store submissions
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
Is there any reason waiterSub is called by CallSubDelayed instead of called by name directly?
Well, it's actually three levels:
  1. Keeping code from waiterSub in Receiver_Receive.
  2. Making waiterSub and have Receiver_Receive call it with waiterSub(...).
  3. Making waiterSub and have Receiver_Receive call it with CallSubDelayed(...).
At some point I got weird results from running code in Receiver_Receive. I barely remember the details now, I've passed so many iterations, sorry. Then @Jmu5667 suggested doing it like this instead, which places the call on the message queue, which I suppose also means that it's not doing it from Receiver_Receive. He also recommended to place the actual waiting in another sub than Receiver_Receive.

I don't have a good answer, and doing a quick test now seems to show that #1 and #2 works fine too. Perhaps @Jmu5667 could add some insight?
 
Upvote 0
Top