Android Question [Solved?] Home Widget after reboot

udg

Expert
Licensed User
Longtime User
Hi all,

I'm updating some old code of mine about a weather forecast home widget in order to use Receivers.
It seems to work ok until the device gets rebooted.
Here it is the log
Logger connected to: 52003f8fb48055e3
--------- beginning of main
--------- beginning of system
*** Receiver (dgmd) Receive (first time) ***
dgmday: receiver receive: true
(Intent) Intent { act=android.appwidget.action.APPWIDGET_ENABLED flg=0x10000010 cmp=eu.dgc.dgmeteo/.dgmd }
*** Receiver (dgmd) Receive ***
dgmday: receiver receive: false
(Intent) Intent { act=android.appwidget.action.APPWIDGET_UPDATE flg=0x14000010 cmp=eu.dgc.dgmeteo/.dgmd (has extras) }
dgmDay: inizio aggiornamento dati
*** Receiver (dgmd) Receive ***
dgmday: receiver receive: false
(Intent) Intent { act=android.appwidget.action.APPWIDGET_UPDATE_OPTIONS flg=0x10000010 cmp=eu.dgc.dgmeteo/.dgmd (has extras) }
sending message to waiting queue of uninitialized activity (submitjob)

The code in the affected receiver is as follows
B4X:
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)
    Log("dgmday: receiver receive: "&FirstTime)
    If FirstTime Then
        rv = ConfigureHomeWidget("lytWidgetD", "rv", 30, "DG Meteo Day",True)
        commons.Initialize
    End If
    Log(StartingIntent)
    rv.HandleWidgetEvents(StartingIntent)
End Sub

Private Sub rv_RequestUpdate
    Log("dgmDay: inizio aggiornamento dati")
    LastUpdate = DateTime.Now
    'aggiorna i dati istantanei della città selezionata
    wait for (ReadNewData) Complete(res As Object)
    Log("dgmDay: fine ReadNewData")
    'aggiorna i dati delle prox 5 fasce orarie prendendoli da quelli settimanali (prox 5 giorni)
    wait For (ReadFiveDays) Complete(res As Object)
    Log("dgmDay: fine ReadFiveDays")
 
    rv.UpdateWidget
    Log("dgmDay: fine aggiornamento dati")
End Sub

ReadNewData and ReadFiveDays are quite simple. Something like:
B4X:
Sub ReadNewData As ResumableSub
    Dim j As HttpJob
    j.Initialize("", Me) 'name is empty as it is no longer needed
    j.Download($"http://api.openweathermap.org/data/2.5/weather?id=${commons.Settings.Get("idtday")}&units=metric&lang=it&appid=${commons.AppKey}"$)
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then

Since compiling-installing-running seems to work ok, it looks like that the Download can't be done in the Receiver soon after reboot 'cause the Main activity is not started yet and so there's no proper context for it. If it's so, how should the widget be designed?
 

drgottjr

Expert
Licensed User
Longtime User
perhaps you have seen this:

B4X:
Private Sub StartHttpUtils2Service
    'HU2_PUBLIC
    #if release
    If HttpUtils2Service.TempFolder = "" Then
        Dim jo As JavaObject
        jo.InitializeNewInstance(Application.PackageName & ".httputils2service", Null)
        jo.RunMethod("onReceive", Array(Null, Null))
    End If
    If HttpUtils2Service.TempFolder <> "" Then Log ("okhttp good to go")
    #end if
End Sub


it addresses the problem of no network connection immediately on boot. erel provided this fix and said he might incorporate it into okhttputils2 at some point.

you have to set the conditional compiler option HU2_PUBLIC (i hope you are familiar with that) and run the code somewhere in the widget code.

you could try it the first time the receiver runs. the code loops until a certain folder is created. that only happens when the network connection is up. assuming networking is enabled on your device, the routine will eventually return. it shouldn't take long, so it shouldn't interfere with the receiver.

i have a similar widget, but i don't use okhttputils for my connection. when the widget starts (on boot), i get an error indicating that there is no network, so i wait a few seconds and try again. that works, and i can schedule my widget to download something periodically.

the code snippet is designed for okhttputils2, and when i do use it (for okhttputils2) it always works. the tricky part in this case, is where to put it since there is no activity. you'll have to try a few different places if it doesn't work when the receiver starts up for the first time. a number of members were posting about the network problem at boot when using okhttputils2. erel wrote the fix.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Good catcth, @drgottjr !
Let me see what I can come up with.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Follow up: it seems solved thanks to the hint by @drgottjr
I tried the simplest approach first, using the Sleep function.. I don't know if it could be considered the right approach but it worked.
So my rv_RequestUpdate now looks like this:
B4X:
Private Sub rv_RequestUpdate
    Log("dgmDay: inizio aggiornamento dati")
    If Not(B4XPages.IsInitialized) Then
        Log("Pause for 55s in the hope the network is established meanwhile")
        Sleep(55000)
    End If
    LastUpdate = DateTime.Now
    'aggiorna i dati istantanei della città selezionata
    wait for (ReadNewData) Complete(res As Object)
    Log("dgmDay: fine ReadNewData")
    'aggiorna i dati delle prox 5 fasce orarie prendendoli da quelli settimanali (prox 5 giorni)
    wait For (ReadFiveDays) Complete(res As Object)
    Log("dgmDay: fine ReadFiveDays")

    rv.UpdateWidget
    Log("dgmDay: fine aggiornamento dati")
End Sub

If we consider it right, a simple message on the widget like "updating data.." for those 55s would suffice, I guess.
Why 55s? No reason. I tried 15, 25, then jumped to 55. I expect this to be device-dependent so a large delay should account for most if not all devices. Again, if the Sleep approach is the correct one..

Edit: no, it's not.
 
Last edited:
Upvote 0

udg

Expert
Licensed User
Longtime User
I explored the BOOT_COMPLETED path but it still doesn't guarantee that the network (WiFi or cell net) is ready.
Eventually a combination of boot-completed and StartReceiverAt could solve. I mean, the receiver gets called on boot complete; if the Internet is not available it schedules itself for a minute later (and so on). Once the Internet is available, it downloads what's needed and starts Main (having the draw on other apps code as part of the app) in order to finally have B4xPages active without user interaction.

It looks so much convoluted when you consider that it should be a simple weather forecast home widget.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
i agree in principle that something so simple as a weather widget shouldn't be so
difficult to set up, but - for whatever reason - the boot process is considered
"complete" before any network connection is made. there are many complaints such
as yours relating to this, and google - in its wisdom - apparently doesn't feel it needs
to be changed.

if you don't like the idea of having to loop periodically until the network is up,
you could trap for the network connectivity change broadcast as well as the boot
completed broadcast. so no looping. let the system do the dirty work for you.

B4X:
<receiver android:name=".ReceiverName" >
    <intent-filter >
        <action android:name="android.net.wifi.STATE_CHANGE" />
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>


it shouldn't be too difficult for you to use a couple flags to indicate state (up, connected). when boot completed is triggered, you check connection flag. if not, you do nothing. a couple seconds later the connection broadcast will be triggered by the system, and you get the weather.
 
  • Like
Reactions: udg
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi @drgottjr ,
thank you again for your support. I found on stackoverflow the same hint (boot+net/wifi change) you suggest and it became the best candidate for a solution.
The problem with the loop (or my attempts with sleep) is that the receiver has limited time to execute and I fear that some devices would not be ready in time. My own device takes from 50s to 120s (will it depend on the mood it has when it wakes up? :) )

Eventually I will post a wish for an example of an home widget based on B4xPages, having two or more pages (e.g. Setings, user choices..) and receiver able to download new data at time intervals and soon after a reboot.
Once ready, my own code could be a starting point.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
and the more i think about it, the less sense it makes to run the widget on boot completed since, if you're not connected, the widget can't get the weather...
you might as well simply use connectvity change and wifi status as your trigger. since these will occur shortly after boot completed, the widget effectively runs on boot up, no?
 
Upvote 0

udg

Expert
Licensed User
Longtime User
I agree with you, boot_completed isn't an indication that Internet access is available so focusing on net/wifi state change should suffice.
But I'm considering a role for boot_completed. See if it convice you as well.
On boot_completed I set a JustBooted flag to True, nothing else.
Then, on net/wifi change, if it returns True (the Interent should be available) I set JustBooted to False and download remote data.

Why? Because the net status change could happen several times in a day but I need the download just once, at the device boot.
After that, it will be the standard receiver dedicated to the home page updating that will try to download updated data each 30 minutes.

So, "boot receiver" to display some data after boot (and Internet available) and "regular receiver" for as long the device stays operational.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Ok, this is totally unexpected.
The "regular receiver" (the standard one for an home widget) is run BEFORE the new, experimental "boot receiver" (making it useless).

So it seems we're again at square zero. We should count only on the regular receiver and play with loop, sleep, StartReceiverAt to be able to download initial data when Internet access is finally available.
 
Upvote 0

drgottjr

Expert
Licensed User
Longtime User
initially, i imagined a "noop" boot receiver. that's when i assumed it could be
removed since, in this case, it did nothing. that would leave the network receivers to do their thing.

the connection/status actions could have flags, of course, to monitor whether
or not the weather had been downloaded. 1) it wouldn't be the end of the world
to receive the weather more than once a day. 2) how many times a day does your
device connect and reconnect? in any case, flags can be used as you suggest.

i suspected that the nature of the home widget might be a problem. i was hoping
to test things. assuming that the solutions we were considering are moot, i have to
say that i don't have a problem with a loop. we do, however, go about it differently.
in your case, you keep trying for the connection by looping. in my case, when my
home widget starts and then triggers a regular timed receiver, if there is no connection,
the receiver does nothing (until the next time it's scheduled). so i only have 1 "loop":
the regular start-at receiver. by the time the receiver comes around again, the
connection (if any) has been made. assuming worse case (network is down), the
widget shows a message, (just like it would do anyway). i think in your case, depending
on how long it takes to make a connection, you could end up killing the receiver. in my
case, the connection is taking place by itself in the background controlled by the system.
i don't care how long it takes. if it's up the next time my receiver runs, i'm good. if not,
the receiver does nothing.

edit: i forgot something. my "run if connected" receiver can handle connects and disconnects
normally. your "loop until connected" boot receiver only checks the network at boot. if you
lose the connection during the day, your regular receiver presumably does nothing because
there is no connection. assuming you wanted to know if you were connected, you would have
to move/copy your connection loop into the regular receiver as well.
 
Last edited:
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi @drgottjr
thank you for your support. I'm not 100% sure, but it seems I solved the remote data download problem after reboot.
At this time it's highly experimental, but it seems to work.
What I did is to have two receivers. The first one is the "regular one" (as from Erel's example) where I added a boolean flag and a public sub.
The flag starts as False so when the receiver is firstly called by the OS to update widget's labels, it starts the second receiver instead (after a 60s delay)

The second receiver just try to use the Internet. If it fails it starts itself again after a minute otherwise it sets the first receivers's flag to True then calls its public sub to immediately update labels (which means that the first receiver can safely download data and update labels)

From that point on, the OS will awake the first (regular) receiver at 30minutes intervals to update data.

Note: I don't need a fool-proof solution since the widget is for my personal use only. For example, I'm not going (at least for now) to test Internet availability each time I need new data (it should be easy but I've real life chores to accomplish).
For a solution intended to be published, a better approach would be to periodically collect data at a server (which will acts as cache) and millions of devices querying the server. This will keep the "secret key" on a safe plase (the server) and enforce the maximum number of daily queries the weather service allows for a free plan.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi all,
after a couple of days of use, the solution outlined in post #12 above seems to work. At least on my Android 10 device.
So, schematically, I copy here the essential parts of it for the benefit of others (and for @Erel to amend and/or to suggest a correct/proper way to do it)
As said above I have two receivers: dgmd and rcboot
The first one (dgmd) is the "regular" one. It's intended to update home widget's labels every 30 minutes, called by the OS
The second one (rcboot) is my way to cope with the boot/reboot of the device when Internet is not yet available.

Fundamentally, dgmd is started by the OS when installing the widget and after a boot/reboot. In the first case you can expect the Internet to be already available so everything should work as usual. After a boot/reboot there's the concrete possibility that when the receiver is called by the OS, Internet access isn't available, so dgmd starts in a minute the second receveir.
The second receiver (rcboot) has the sole task to test for Internet availability and, in case of failing, to try again in a minute.
Once Internet access is available, rcboot calls back in dgmd to immeditaley update its labels (not waiting for the 30 minutes delay of the OS)
That's all

Note: since this is a quick and dirty solution for my device and personal use, I didn't bother with Internet on/off or other situations. As said in post #12 for a general solution I would rather use a server querying the weather api tocache data at given intervals, while listening to queries from user devices to server that data.

dgmd receiver
B4X:
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)
    LogColor("dgmday: receiver_receive: "&FirstTime, 0xFFFF0000)
    Log(StartingIntent)
    
    If FirstTime Then
        rv = ConfigureHomeWidget("lytWidgetD", "rv", 30, "DG Meteo Day",True)
        commons.Initialize
        Wdata.Initialize
    End If
    rv.HandleWidgetEvents(StartingIntent)
End Sub

Private Sub rv_RequestUpdate
    If Not(rcboot.InternetOK) Then
        StartReceiverAt(rcboot, DateTime.Now + (1 * DateTime.TicksPerMinute), False)  'run this receiver again in 1 minute
        rv.SetText("lblTown", "Internet not yet available")
    Else
        LastUpdate = DateTime.Now
        'read remote data
        wait for (ReadNewData) Complete(res1 As Boolean)
        rv.SetText("lblTown", Wdata.Get("Tname"))
       'update some othern labels
    End If
    rv.UpdateWidget
End Sub

rcboot receiver
B4X:
Private Sub Receiver_Receive (FirstTime As Boolean, StartingIntent As Intent)
    LogColor("rcboot: receiver receive: "&FirstTime, 0xFFFF0000)
    If Not(InternetOK) Then
        Dim j As HttpJob
        j.Initialize("", Me) 'name is empty as it is no longer needed
        j.Download("www.google.com")
        j.GetRequest.Timeout = 5000
        Wait For (j) JobDone(j As HttpJob)
        Log("rcboot success: "&j.Success)
        InternetOK = j.Success
        If j.Success Then
            CallSubDelayed(dgmd, "UpdtdgmD")
        Else
            StartReceiverAt(Me, DateTime.Now + (1 * DateTime.TicksPerMinute), False)  'run this receiver again in 1 minute
        End If
     End If
End Sub
 
Upvote 0
Top