Android Question Regarding not being able to use MsgBox from Starter

Sandman

Expert
Licensed User
Longtime User
We all know that we can't show UI things from Starter (or any services really), like MsgBox etc. (Toasts are an exception for some reason, but for this thread I'll ignore them.)

Let's say I have a class in Starter. Then that class won't be able to use MsgBox etc. And if that class has a class in it, that in turn has a class in it, then that can't show MsgBox etc. You know, it's not possible, because they all inherit the Starter context.


So my question is this:

Is there a way for a sub to detect if it's in a Starter (or other service) context and thus cannot show MsgBox etc?
 

greenpepper7

New Member
I found something https://www.b4x.com/android/forum/t...-module-compile-to-library.120080/post-750782

B4X:
    Private ctxt As JavaObject
    Log("***" & GetType(ctxt.InitializeContext))

    'Log for Starter call: ***android.app.Application
    'Log for Activity call: ***b4a.example.main
This is a neat solution. I had forgotten about using JavaObject to get the current context. Using this I've made a small project to address sandman's original question.

Add this to the starter service so it's accessible everywhere:
Starter:
#If JAVA
public static boolean isContextActivityContext(android.content.Context ctxt) {
    return (ctxt instanceof android.app.Activity);
}
#End If
Then in your class within a class within a class within your service/activity ? (or however you have it setup), whenever you need to check if you can show a message box, you can call this method. For example:
Checking for activity context:
Public Sub SubThatInvolvesMessageBoxes
    Dim jo As JavaObject : jo.InitializeContext

    If Starter.As(JavaObject).RunMethod("isContextActivityContext", Array As Object(jo)) Then 'Ignore unmatched types warning
        'Called from activity context, we can safely use message boxes
        
        MsgboxAsync("Called from activity", "")
    Else
        'Called from non activity context - we cannot use message boxes
        
        ToastMessageShow("Cannot show msgbox - called from service", False)
    End If
End Sub
I've tested this in a few different situations in debug and release, and it works as expected. If you think its worth implementing yourself (and if you can look past my ugly coding style ?), test it out and let me know how it goes.
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
Thank you. Tested and works. I've modified it slightly to simplify use in the class.

In Starter:

B4X:
Public Sub isActivity(ctxt As JavaObject) As Boolean
    Dim jo As JavaObject = Me
    If jo.RunMethod("isContextActivityContext", Array As Object(ctxt)) Then
        Return True
    Else
        Return False
    End If
End Sub

#If JAVA
public static boolean isContextActivityContext(android.content.Context ctxt) {
    return (ctxt instanceof android.app.Activity);
}
#End If

In Class:

B4X:
Public Sub displayMsg(s As String)
    Private ctxt As JavaObject
    ctxt.InitializeContext
    If Starter.isActivity(ctxt) Then
        xui.MsgboxAsync(s, "")
    Else
        'Do something else
    End If
End Sub

Edit: I removed a reference to ShowCustomToast (which is deprecated)
 
Last edited:
Upvote 0

Spavlyuk

Active Member
Licensed User
Let's say I have a class in Starter. Then that class won't be able to use MsgBox etc. And if that class has a class in it, that in turn has a class in it, then that can't show MsgBox etc. You know, it's not possible, because they all inherit the Starter context.
This seems somewhat of an architectural issue. You probably shouldn't put classes that contain user interface elements in the Starter service and instead have the UI call the service or have the service start the activity when appropriate.
 
Upvote 0

asales

Expert
Licensed User
Longtime User
'@Erel link https://www.b4x.com/android/forum/threads/custom-toast-message.89173/
Sub ShowCustomToast(Text As Object, LongDuration As Boolean, BackgroundColor As Int)
Dim ctxt As JavaObject
ctxt.InitializeContext
Dim duration As Int
If LongDuration Then duration = 1 Else duration = 0
Dim toast As JavaObject
toast = toast.InitializeStatic("android.widget.Toast").RunMethod("makeText", Array(ctxt, Text, duration))
Dim v As View = toast.RunMethod("getView", Null)
Dim cd As ColorDrawable
cd.Initialize(BackgroundColor, 20dip)
v.Background = cd
toast.RunMethod("setGravity", Array(Bit.Or(Gravity.CENTER_HORIZONTAL, Gravity.CENTER_VERTICAL), 0, 0))
toast.RunMethod("show", Null)
End Sub[/CODE]
The ShowCustomToast will fail in Android 30+ because this:
B4X:
public View getView (). This method was deprecated in API level 30
Source:
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
This seems somewhat of an architectural issue.
Oh, absolutely.

You probably shouldn't put classes that contain user interface elements in the Starter service and instead have the UI call the service or have the service start the activity when appropriate.
Yep, I agree.

The reason for why this thread was created was simply that I was moving some stuff around and made a blunder to move a class with a MsgBox to Starter. That class isn't used very often, so when it actually was used (by me, in testing) it crashed. And it was so long ago that I had done that that I wasted too much time finding what the problem could be.

So this idea came up. Not really as a way of wrapping every UI call (because that would be insane), but more like this:
B4X:
Public Sub Initialize
#If DEBUG
    ' Pseudocode obviously
    if Not(ClassCanShowUI) Then LogColor("REMINDER: MyFineClass can NOT show UI elements", Colors.Red)
#End if
End Sub

Perhaps it's stupid, but I wouldn't have wasted a day to find a stupid error if I had this in place...
 
Upvote 0

Sandman

Expert
Licensed User
Longtime User
Just a small note on this code:
B4X:
Public Sub isActivity(ctxt As JavaObject) As Boolean
    Dim jo As JavaObject = Me
    If jo.RunMethod("isContextActivityContext", Array As Object(ctxt)) Then
        Return True
    Else
        Return False
    End If
End Sub

It can be shortened to this:
B4X:
Public Sub isActivity(ctxt As JavaObject) As Boolean
    Dim jo As JavaObject = Me
    Return jo.RunMethod("isContextActivityContext", Array As Object(ctxt))
End Sub

And even one more step:
B4X:
Public Sub isActivity(ctxt As JavaObject) As Boolean
    Return Me.As(JavaObject).RunMethod("isContextActivityContext", Array As Object(ctxt))
End Sub


But in the end I went with doing it like this for now:
Starter:
#If DEBUG

Public Sub reportOnCanShowUserInterface(jo As JavaObject, name As String)
    If Not(Me.As(JavaObject).RunMethod("isContextActivityContext", Array As Object(jo))) Then
        LogColor($"WARN: ${name} can NOT show UI"$, Colors.Red)
    End If
End Sub

#If JAVA
public static boolean isContextActivityContext(android.content.Context ctxt) {
    return (ctxt instanceof android.app.Activity);
}
#End If

#End If

With example usage:
B4XMainPage:
Public Sub Initialize
#If DEBUG
    Dim jo As JavaObject
    jo.InitializeContext
    Starter.reportOnCanShowUserInterface(jo, "B4XMainPage")
#End If
End Sub

(It sure would be nice if we could get the module name by some magic variable that the compiler fills in at compile time, then the usage code could be identical all over the place.)
 
Last edited:
Upvote 0
Top