Android Code Snippet Sending push messages from B4A

1. Download dependencies from: https://www.b4x.com/android/forum/threads/b4x-firebase-push-notifications-2023.148715/
2. Download: https://repo1.maven.org/maven2/com/google/guava/failureaccess/1.0.2/failureaccess-1.0.2.jar

Copy all jars to the additional libraries folder.

3. Add to main module:
B4X:
#AdditionalJar: google-auth-library-oauth2-http-1.18.0.jar
#AdditionalJar: google-auth-library-credentials-1.18.0.jar
#AdditionalJar: com.google.guava:guava
#AdditionalJar: com.google.guava:listenablefuture
#AdditionalJar: failureaccess-1.0.2.jar
#AdditionalJar: google-http-client-1.43.3.jar
#AdditionalJar: google-http-client-gson-1.43.3.jar
#AdditionalJar: gson-2.10.1.jar
#AdditionalJar: opencensus-api-0.31.1.jar
#AdditionalJar: opencensus-contrib-http-util-0.31.1.jar
#AdditionalJar: grpc-context-1.27.2.jar

4. NetworkOnMainThread check should be disabled. This is done by calling this sub:
B4X:
Private Sub DisableStrictMode
    Dim jo As JavaObject
    jo.InitializeStatic("android.os.Build.VERSION")
    If jo.GetField("SDK_INT") > 9 Then
        Dim policy As JavaObject
        policy = policy.InitializeNewInstance("android.os.StrictMode.ThreadPolicy.Builder", Null)
        policy = policy.RunMethodJO("permitAll", Null).RunMethodJO("build", Null)
        Dim sm As JavaObject
        sm.InitializeStatic("android.os.StrictMode").RunMethod("setThreadPolicy", Array(policy))
    End If

The code is the same as in B4J.

Note that storing the key inside the app is insecure. This is useful for internal apps.
 
Last edited:

Bryan Joyce

Member
Licensed User
Longtime User
Thanks Erel. This works perfectly for me in B4A when in Debug mode. When I switch to Release mode I get an authentication error

Authentication Error:
** Activity (main) Resume **
sending message to waiting queue of uninitialized activity (subscribetotopics)
ResponseError. Reason: , Response: {
  "error": {
    "code": 401,
    "message": "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
    "status": "UNAUTHENTICATED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "CREDENTIALS_MISSING",
        "domain": "googleapis.com",
        "metadata": {
          "method": "google.firebase.fcm.v1.FcmService.SendMessage",
          "service": "fcm.googleapis.com"
        }
      }
    ]
  }
}
*** Receiver (firebasemessaging) Receive (first time) ***
TokenRefresh: fbuRepVbS_Sv90cIY8Abne:APA91bHj730VB4YErGTDyogm-zq0Y36wjfZ0219sCh08keXJIGbUkGaIWp6xFtzh8b1MKV9NJ3EmpeCxUiqyXnniriklfA26CeTrJvCgiSXdOGAMm_QGkeuFvVtyl_hNcdLypPosWrvi
** Activity (main) Pause event (activity is not paused). **

Error seems to come from Private Sub GetTokenValue

My Code:
#Region  Project Attributes
    #ApplicationLabel:B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: portrait
    #CanInstallToExternalStorage: False
    #BridgeLogger:True
    #MultiDex: true
    #AdditionalJar: google-auth-library-oauth2-http-1.18.0.jar
    #AdditionalJar: google-auth-library-credentials-1.18.0.jar
    #AdditionalJar: com.google.guava:guava
    #AdditionalJar: com.google.guava:listenablefuture
    #AdditionalJar: failureaccess-1.0.2.jar
    #AdditionalJar: google-http-client-1.43.3.jar
    #AdditionalJar: google-http-client-gson-1.43.3.jar
    #AdditionalJar: gson-2.10.1.jar
    #AdditionalJar: opencensus-api-0.31.1.jar
    #AdditionalJar: opencensus-contrib-http-util-0.31.1.jar
    #AdditionalJar: grpc-context-1.27.2.jar
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    Private Const ProjectId As String = "fcmtest2-124ae" 'change
    Private ServiceAccountFilePath As String
    Private Token As String
End Sub
Sub Globals
    
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Dim pm As B4XPagesManager
    pm.Initialize(Activity)
    File.Copy(File.DirAssets, "fcmtest2-124ae-firebase-adminsdk-rii0r-494c60c2d3.json", File.DirInternal, "fcmtest2-124ae-firebase-adminsdk-rii0r-494c60c2d3.json")
    ServiceAccountFilePath = File.DirInternal & "/fcmtest2-124ae-firebase-adminsdk-rii0r-494c60c2d3.json"
    If File.Exists(File.DirInternal, "/fcmtest2-124ae-firebase-adminsdk-rii0r-494c60c2d3.json") Then
        Log("#########################File Exists")
    End If
    Log(ServiceAccountFilePath)
    Send("general", "title", "Initial run message")
End Sub

Private Sub Send (topic As String, title As String, body As String)
        Dim Token As String = GetTokenValue(ServiceAccountFilePath)
        Wait For (SendMessage(topic, title, body)) Complete (Success As Boolean) 'B4A - commend if not needed
End Sub

Private Sub SendMessage(Topic As String, Title As String, Body As String) As ResumableSub
    Try
        Dim Job As HttpJob
        Job.Initialize("", Me)
        Dim data As Map = CreateMap("title": Title, "body": Body)
        Dim message As Map = CreateMap("topic": Topic, "data": data)
        message.Put("android", CreateMap("priority": "high"))
        Dim jg As JSONGenerator
        jg.Initialize(CreateMap("message": message))
        Log(jg.ToPrettyString(4))
        Job.PostString($"https://fcm.googleapis.com/v1/projects/${ProjectId}/messages:send"$, jg.ToString)
        Job.GetRequest.SetContentType("application/json;charset=UTF-8")
        Job.GetRequest.SetHeader("Authorization", "Bearer " & Token)
        Wait For (Job) JobDone(Job As HttpJob)
        If Job.Success Then
            Log(Job.GetString)
        End If
        Job.Release
        Return True
    Catch
        Return False
    End Try
End Sub

Private Sub GetTokenValue (FilePath As String) As String
    Try
        Dim GoogleCredentials As JavaObject
        GoogleCredentials.InitializeStatic("com.google.auth.oauth2.GoogleCredentials")
        Dim Credentials As JavaObject = GoogleCredentials.RunMethodJO("fromStream", Array(File.OpenInput(FilePath, ""))) _
        .RunMethod("createScoped", Array(Array As String("https://www.googleapis.com/auth/firebase.messaging")))
        Credentials.RunMethod("refreshIfExpired", Null)
        Return Credentials.RunMethodJO("getAccessToken", Null).RunMethod("getTokenValue", Null)
    Catch
        Log("Token Failed to be created")
        Return ""
    End Try
End Sub
 

Bryan Joyce

Member
Licensed User
Longtime User
I am not sure if this helps but the error without the Try-Catch is

Error:
Logger connected to:  samsung SM-G935W8
--------- beginning of crash
--------- beginning of main
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create (first time) **
main_gettokenvalue (java line: 387)
java.lang.reflect.InvocationTargetException
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:132)
    at FCM.Test2.main._gettokenvalue(main.java:387)
    at FCM.Test2.main$ResumableSub_Send.resume(main.java:455)
    at FCM.Test2.main._send(main.java:428)
    at FCM.Test2.main._activity_create(main.java:371)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:221)
    at FCM.Test2.main.afterFirstLayout(main.java:105)
    at FCM.Test2.main.access$000(main.java:17)
    at FCM.Test2.main$WaitForLayout.run(main.java:83)
    at android.os.Handler.handleCallback(Handler.java:789)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6944)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)
Caused by: android.os.NetworkOnMainThreadException
    at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1448)
    at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:102)
    at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:90)
    at java.net.InetAddress.getAllByName(InetAddress.java:787)
    at com.android.okhttp.Dns$1.lookup(Dns.java:39)
    at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:200)
    at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:148)
    at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:90)
    at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:190)
    at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:142)
    at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:104)
    at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:410)
    at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:343)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:489)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
    at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:262)
    at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getOutputStream(DelegatingHttpsURLConnection.java:218)
    at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(Unknown Source:0)
    at com.google.api.client.http.javanet.NetHttpRequest.execute(NetHttpRequest.java:113)
    at com.google.api.client.http.javanet.NetHttpRequest.execute(NetHttpRequest.java:84)
    at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1012)
    at com.google.auth.oauth2.ServiceAccountCredentials.refreshAccessToken(ServiceAccountCredentials.java:538)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:269)
    at com.google.auth.oauth2.OAuth2Credentials$1.call(OAuth2Credentials.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at com.google.auth.oauth2.OAuth2Credentials$RefreshTask.run(OAuth2Credentials.java:633)
    at com.google.common.util.concurrent.DirectExecutor.execute(DirectExecutor.java:31)
    at com.google.auth.oauth2.OAuth2Credentials$AsyncRefreshResult.executeIfNew(OAuth2Credentials.java:581)
    at com.google.auth.oauth2.OAuth2Credentials.asyncFetch(OAuth2Credentials.java:232)
    at com.google.auth.oauth2.OAuth2Credentials.refreshIfExpired(OAuth2Credentials.java:203)
    ... 18 more
 

Mariano Ismael Castro

Active Member
Licensed User
Caused by: android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1448) com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getOutputStream(Unknown Source:0)
Look at this
 

Bryan Joyce

Member
Licensed User
Longtime User
Excellent, that worked. Thank you very much. I added the following to the main thread
Main:
    Sub Activity_Create(FirstTime As Boolean)
    If FirstTime Then
        DisableStrictMode
    End If
    End Sub
    
    Private Sub DisableStrictMode
    'This was recommended to make the FCM work.  Fix the error.  Called from if first time.
    Dim jo As JavaObject
    jo.InitializeStatic("android.os.Build.VERSION")
    If jo.GetField("SDK_INT") > 9 Then
        Dim policy As JavaObject
        policy = policy.InitializeNewInstance("android.os.StrictMode.ThreadPolicy.Builder", Null)
        policy = policy.RunMethodJO("permitAll", Null).RunMethodJO("build", Null)
        Dim sm As JavaObject
        sm.InitializeStatic("android.os.StrictMode").RunMethod("setThreadPolicy", Array(policy))
    End If
End Sub
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…