Android Question How to make the app working in background?

Mattiaf

Active Member
Licensed User
Hi, today it's my first day in b4x ecosystem and since I know a bit of vb.net, it has been quite exciting to discover.
So, I'm working for a personal utility which parse a crypto price from an exchange price.
Thanks to all the topics I've been able to code this and run it correctly on my samsung s20+ with android 12:


B4X:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    Private Button1 As Button
    Private Label1 As Label
    Private timer1 As Timer
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("layout")
    timer1.Initialize("Timer1", 100)
timer1.Enabled=True  
    End Sub
Sub Button1_Click
timer1.Enabled=False
End Sub
Sub timer1_tick
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download("https://api.hotbit.io/api/v1/market.last?market=KIBA/USDT")
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        Log(j.GetString)
        Dim parser As JSONParser
        parser.Initialize(j.GetString)
        Dim root As Map = parser.NextObject
        Dim result As String = root.Get("result")
        Label1.Text= result
    End If
    j.Release
End Sub

The fact is now I'm looking to make it works in background and many recents topic are redirecting me to This topic but if I need to be honest, It is 3 ys old and I don't know if that is all I need. Should just insert that line in the app.manifest? if so, is app.manifest on "Project > Manifest Editor"?
I'm just looking to make it works in background and after that I'll send push notifications or play a .mp3 since the app will be an important price alert.
I also read about "ignore battery saving" and "allow data on background" but I hope I can just set them straight from the phone settings.
Thanks,
Mattia
 
Last edited:

Erel

B4X founder
Staff member
Licensed User
Longtime User
Welcome to B4X.

Never use activities. Start with B4XPages. Especially if you want your app to run in the background.

It is 3 ys old and I don't know if that is all I need.
It was last updated a few months ago. You can see it in the bottom right corner. Using a foreground service is the only way to keep your app running in the background, and it will also won't work on all devices.
 
Upvote 0

Mattiaf

Active Member
Licensed User
Hey Erel, thanks for your reply.

As I can see from that line of code

B4X:
SetServiceAttribute(Tracker, android:foregroundServiceType, "location")

contains : Tracker and location which make me think about gps.
In my case I don't need to have gps active in background, since my app doesn't need gps enabled.
Using a foreground service is the only way to keep your app running in the background

Please tell me if I'm wrong but is that sentence an oxymoron? because I obviously can't keep the app in foreground 24/7 since it is a price alert that check the price from the api in background. Just looking for something that allow me to use the phone without caring about the app that is working on background. I am sorry if I making any confusion, hope to understand it better with time.
Still Thanks,
Mattia
 
Upvote 0

Mattiaf

Active Member
Licensed User
I'll try to be as clear as possible.
I m getting a value from an API which correspond to a price . This price is constantly changing and if this price will be higher than x than It will send a push notification to my phone. At this stage, the part of parsing json and letting it run inside of a timer is done. So now what I'm missing is to let the app run in background because the phone can't stay open with the screren active all day. What I'm trying to do is a simple price alert.
Hope this will explain it better, and I'm here to give as much information as you need.
Thanks,
Mattia
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
because I obviously can't keep the app in foreground 24/7
But the service that you are trying to run in the "background" needs to show a notification in order to run. Therefore it's a foreground service (the user is aware of it's existence due to the notification icon). The naming is from Google itself, so blame them for such wording. True background services, without notification icon, are getting more difficult with each Android release and have their own restrictions/constraints.
 
Upvote 0

Mattiaf

Active Member
Licensed User
But the service that you are trying to run in the "background" needs to show a notification in order to run. Therefore it's a foreground service (the user is aware of it's existence due to the notification icon). The naming is from Google itself, so blame them for such wording. True background services, without notification icon, are getting more difficult with each Android release and have their own restrictions/constraints.

OK that sounds good so
B4X:
SetServiceAttribute(Tracker, android:foregroundServiceType, "location")

doesn't correspond to the gps tracking but it is simply a naming used from Google. I also don't have any problem using the foreground service with the notification icon since this is a personal utility that will be installed just on my phone.
Therefore that line of code is the only one I'm going to need in app.manifest, Am I right?
thanks
 
Last edited:
Upvote 0

Mattiaf

Active Member
Licensed User
Hey Erel, hope I don't bother you.

I follow exactly what you've linked and It has been really helpfull!
I managed to recreate the same things and now it is sending the notification, but...

The notification is being sent just when I run b4j which is targeted for Windows, Linux, Mac. Instead, I want the app on b4a to send the notification. Thus even if I create a button with:
B4X:
CallSubDelayed(FirebaseMessaging, "SubscribeToTopics")

It won't send anything, I need to run the tool you created with my api key in order to receive a notification.. What I am doing wrong?
many thanks
 
Upvote 0

Mattiaf

Active Member
Licensed User
Edit 1: you are stating "Note that the API_KEY should be set in the B4J code. It shouldn't be distributed in your app." this means I need to open b4j in order to send the notification, but as I stated in my previous post, I need the android app to make everything without b4j, so I've tried, probably doing something wrong and I am sorry in advance, to try something like this in b4a:

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region
#BridgeLogger: True
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
    Private timer1 As Timer
    Private const API_KEY As String = "api key"


End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    Private Button1 As Button
    Private Label1 As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("layout")
    timer1.Initialize("Timer1", 3000)
timer1.Enabled=True   
    End Sub
Sub Button1_Click
timer1.Enabled=False
End Sub
Sub timer1_tick
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download("https://api.hotbit.io/api/v1/market.last?market=KIBA/USDT")
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        Log(j.GetString)
        Dim parser As JSONParser
        parser.Initialize(j.GetString)
        Dim root As Map = parser.NextObject
        Dim result As String = root.Get("result")
        Label1.Text= result
    End If
    If result > "0.00002874" Then
    CallSubDelayed(FirebaseMessaging, "SubscribeToTopics")
        SendMessage("general", "Price increased above 0.00002874", "Sell")
        'StartMessageLoop
    End If
    j.Release
End Sub
Private Sub SendMessage(Topic As String, Title As String, Body As String)
    Dim Job As HttpJob
    Job.Initialize("fcm", Me)
    Dim m As Map = CreateMap("to": $"/topics/${Topic}"$)
    Dim data As Map = CreateMap("title": Title, "body": Body)
    If Topic.StartsWith("ios_") Then
        Dim iosalert As Map =  CreateMap("title": Title, "body": Body, "sound": "default")
        m.Put("notification", iosalert)
        m.Put("priority", 10)
    End If
    m.Put("data", data)
    Dim jg As JSONGenerator
    jg.Initialize(m)
    Job.PostString("https://fcm.googleapis.com/fcm/send", jg.ToString)
    Job.GetRequest.SetContentType("application/json;charset=UTF-8")
    Job.GetRequest.SetHeader("Authorization", "key=" & API_KEY)
End Sub


Sub JobDone(job As HttpJob)
    Log(job)
    If job.Success Then
        Log(job.GetString)
    End If
    job.Release
    ExitApplication '!
End Sub

sometimes it works, sometimes it doesn't send the notification probably as you stated: "Note that messages sent from Firebase Console will not arrive in some cases. Use the B4J code to test it."
but then it send me in confusion cause why should I use b4j if I only need b4a?

Also if I minimize the app, it goes in pause, log says: "** Activity (main) Pause, UserClosed = false **" which doesn't fix what I need, because it should work when minimized (background), it only works when the app is active and opened.
I registered a gif and after few open/reopen the app it sent me the notification.


Please can you explain me? Thanks
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
First, let your B4A app receive Firebase push notification.
I would suggest you build a server to monitor the price changes. A server can be build with B4J, VB.NET or other languages. Let say, you build it using B4J, then it will have a timer of 3 seconds interval to check with the API using OkHttpUtils. If a certain criteria is met then it will "blast" the push notification to all the "subscribed" devices. Instead of each of 10 devices send request to the Crypto market API, now you only do it once in the server.
 
Upvote 0

Mattiaf

Active Member
Licensed User
Hi aeric, As I stated many times this will be just a personal app, so only one device. I would like to avoid creating a new server since I want the phone itself to be the server and the client together. A server using b4j means I should leave the computer on, which is what I want to avoid.
b4a app, should pause when minimized and needs to check on API and if the price is higher than x than send a notification. If you look close to my gif, that is what I'm trying to do and It works just when the app is open, despite Firebase not sending the messages sometimes.
 
Last edited:
Upvote 0

aeric

Expert
Licensed User
Longtime User
If it is running in B4A then you can use NB6 to show notification.
 
Upvote 0

Mattiaf

Active Member
Licensed User
Thanks I'll try to implement it now.. This will also solve the app not working when minimized since I can't obviously leave the app on the phone opened all day?
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
A server using b4j means I should leave the computer on, which is what I want to avoid
You can use a VPS which is cheap or free. If this is what you want to avoid then forget it.
 
Upvote 0

Mattiaf

Active Member
Licensed User
I don't mind leaving to my phone to do the hard job 24/7. It is just a price alert with a 3 second timer.. :)
 
Upvote 0

OliverA

Expert
Licensed User
Longtime User
I would like to avoid creating a new server since I want the phone itself to be the server and the client together. A server using b4j means I should leave the computer on, which is what I want to avoid.
Then you're back with creating a foreground service that may still be killed. There are partial solutions to this issue (getting killed) such as enabling partial lock, turning off battery optimization and every time your app goes in the background setting a timer (min 15 minutes) to start the service again (just in case your app is killed). Some methods would get you flagged on the Play Store, but I don't think your going for an app for publishing. Also note, some phone manufactures are really set on killing applications in the name of battery savings, so you may still end up with an application that dies. With the above three methods I do seem to have decent luck on a Samsung S7 and S9. As to the newer S20, who knows (only testing will tell).
SetServiceAttribute(Tracker, android:foregroundServiceType, "location")
This setting is only required if you are using a foreground service for location purposes. A standard foreground service does not need this additional Manifest setting
 
Upvote 0

Mattiaf

Active Member
Licensed User
Then you're back with creating a foreground service that may still be killed. There are partial solutions to this issue (getting killed) such as enabling partial lock, turning off battery optimization and every time your app goes in the background setting a timer (min 15 minutes) to start the service again (just in case your app is killed). Some methods would get you flagged on the Play Store, but I don't think your going for an app for publishing. Also note, some phone manufactures are really set on killing applications in the name of battery savings, so you may still end up with an application that dies. With the above three methods I do seem to have decent luck on a Samsung S7 and S9. As to the newer S20, who knows (only testing will tell).

This setting is only required if you are using a foreground service for location purposes. A standard foreground service does not need this additional Manifest setting
Hi olive, thanks for your reply.

As I stated on my post, this is a personal utility and I m not going to move it from my phone and obviously it won't see any google play store. so I can do whatever I need from my phone settings, like ignore battery saving and use data on background..
I'm just doing a personal price alert utility that need to polling a website to check if the price is higher than n. If the price is higher than n then send me a push notification and stop the timer.
As I thought, that setting is only required if I'm using a foreground service for location purposes. I'm just reading what people are suggesting me but it seems no one get my point.. Hope I'm explain it correctly. Thanks
 
Upvote 0

Mattiaf

Active Member
Licensed User
Ignoring the gps thing, I've tried to "copy" the rest and tried to adapt to my code, but I'm doing something wrong. I'll post here what I did..
main:
B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region
#BridgeLogger:True
Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Private xui As XUI
    Private timer1 As Timer
End Sub
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    Private Button1 As Button
    Private Label1 As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("layout")
    timer1.Initialize("Timer1", 3000)
timer1.Enabled=True   
    End Sub
Sub Button1_Click
timer1.Enabled=False
End Sub
Sub timer1_tick
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download("https://api.hotbit.io/api/v1/market.last?market=KIBA/USDT")
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        Log(j.GetString)
        Dim parser As JSONParser
        parser.Initialize(j.GetString)
        Dim root As Map = parser.NextObject
        Dim result As String = root.Get("result")
        Label1.Text= result
    End If
    If result > "0.00002874" Then
    
    'send notification
    End If
    j.Release
End Sub
Sub Activity_Resume
    Starter.rp.CheckAndRequest(Starter.rp.PERMISSION_ACCESS_FINE_LOCATION)
    Wait For Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result Then
        StartService("" )
    Else
        ToastMessageShow("No permission...", True)
    End If
End Sub

Service module Starter:
B4X:
#Region  Service Attributes
    #StartAtBoot: False
    #ExcludeFromLibrary: True
#End Region

Sub Process_Globals
    Public rp As RuntimePermissions
End Sub

Sub Service_Create
    
End Sub

Sub Service_Start (StartingIntent As Intent)
    

End Sub

Sub Service_TaskRemoved
    'This event will be raised when the user removes the app from the recent apps list.
End Sub

'Return true to allow the OS default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub Service_Destroy

End Sub

Sercice module Parsingandinvio
B4X:
#Region  Service Attributes
    #StartAtBoot: True
#End Region

Sub Process_Globals
    Private nid As Int = 1
        Private LastUpdateTime As Long
    Private lock As PhoneWakeState
End Sub

Sub Service_Create
    Service.AutomaticForegroundMode = Service.AUTOMATIC_FOREGROUND_NEVER 'we are handling it ourselves
    lock.PartialLock
End Sub

Sub Service_Start (StartingIntent As Intent)
    Service.StartForeground(nid, CreateNotification("..."))
    StartServiceAt(Me, DateTime.Now + 30 * DateTime.TicksPerMinute, True)
    PrezzoCambiato
End Sub

Sub PrezzoCambiato
    If DateTime.Now > LastUpdateTime + 10 * DateTime.TicksPerSecond Then
        Dim n As Notification = CreateNotification($"Price is higher than n"$)
        n.Notify(nid)
        LastUpdateTime = DateTime.Now
    End If
End Sub

Sub CreateNotification (Body As String) As Notification
    Dim notification As Notification
    notification.Initialize2(notification.IMPORTANCE_LOW)
    notification.Icon = "icon"
    notification.SetInfo("Price is higher than n", Body, Main)
    Return notification
End Sub

Sub Service_Destroy
    lock.ReleasePartialLock
End Sub

Logs:
Logger connesso a: samsung SM-G985F
--------- beginning of main
Copying updated assets files (1)
*** Service (starter) Create ***
** Service (starter) Start **
** Activity (main) Create, isFirst = true **
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
*** Service (httputils2service) Create ***
** Service (httputils2service) Start **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
{"error":null,"result":"0.00002874","id":408249259}
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (CallSubDelayed - JobDone)
sending message to waiting queue (activity_permissionresult)
running waiting messages (2)
{"error":null,"result":"0.00002875","id":363420695}
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **
sending message to waiting queue (activity_permissionresult)
running waiting messages (1)
** Activity (main) Resume **
** Activity (main) Pause, UserClosed = false **

As you can see, when I minimize the app, it goes on pause and so It won't work until I open it again.. I obviously know the code is incorrect, but this is my first time with b4x eco system and please understand me..

editor manifest:
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="29"/>
<supports-screens android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
CreateResourceFromFile(Macro, Themes.LightTheme)
'End of default text.
SetServiceAttribute(Parsingandinvio, android:foregroundServiceType, "location")

and this is the video I made to explain better what I can't by words..
In case someone want, I also attached the zip project on mediafire since here it says the file is too big to be attached.

Many thanks to whoever will spend some time to help me
 
Upvote 0

aeric

Expert
Licensed User
Longtime User
Here you go

You should compare the value using Double instead of String
B4X:
Sub FetchPrice
    Log("Fetching price...")
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download("https://api.hotbit.io/api/v1/market.last?market=KIBA/USDT")
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        Log(j.GetString)
        Dim parser As JSONParser
        parser.Initialize(j.GetString)
        Dim root As Map = parser.NextObject
        Dim result As String = root.Get("result")
        'Label1.Text = result
        CallSubDelayed2(Main, "ShowPriceLabel", result)
        If result.As(Double) > 0.000026 Then
            Log($"Alert! ${result}"$)
            Starter.RunService = False
            CallSubDelayed2(Main, "SetButtonText", "Start Service")
            'timer1.Enabled = False
            ' invia notifica
            Service.StartForeground(nid, CreateNotification(result))
            Return
        Else
            Log("pass...")
        End If
        LastUpdateTime = DateTime.Now
        StartServiceAt(Me, LastUpdateTime + 5 * DateTime.TicksPerSecond, True) ' After 5 seconds
    End If
    j.Release
End Sub
 

Attachments

  • prova3.zip
    10.8 KB · Views: 71
Last edited:
Upvote 0
Top