Android Tutorial Android home screen widgets tutorial - part II

Status
Not open for further replies.
Please start with the first part of this tutorial if you haven't read it before.
In this part we will build a "quote of the day" widget.

quote_1.png


We will start with the layout. The widget is made of a Label for the text and an ImageView for the arrow button.

The layout in the designer:
quote_design.png


You can see in the above picture that we use two panels. The base panel named pnlBase is a transparent panel (Alpha=0). The base panel contains another panel which is the grey panel.
The purpose of the transparent panel is to add some padding to the other views. The widget size is determined by the base panel. Without the transparent panel there will be no margin between the visible widget and the screen left edge.

We are setting the base panel size to 294x72. This is the recommended size for a 4x1 cells widget.
Tip: in some cases when you change the layout and there is already an existing widget in the device, the widget doesn't get updated. Remove the widget and add it again to see the change.

Now for the program logic.
Once a day the program fetches 20 quotes from 5 feeds available from Famous Quotes at BrainyQuote
Then the first quote is displayed. Each time the user presses on the arrow button the next quote is displayed.
While getting the quotes the first quote of each feed is added to the beginning of the quotes list. Only the first quote on each feed is new, and we want to start with the new quotes.
Downloading the feeds is done with HttpUtils and parsing them is done with XmlSax library. See the code for more information.

The widget is configured to be updated automatically every 24 hours. This is done in this line:
B4X:
    'configure the widget and set it to update every 24 hours (1440 minutes).
    rv = ConfigureHomeWidget("WidgetLayout", "rv", 1440, "Quote of the day")
After 24 hours or when the widget is first added or after a boot the RequestUpdate event is raised.
B4X:
Sub rv_RequestUpdate
    quotes.Clear
    currentQuote = -1
    HttpUtils.DownloadList("Quotes", Array As String("http://feeds.feedburner.com/brainyquote/QUOTEBR", _
        "http://feeds.feedburner.com/brainyquote/QUOTEAR", "http://feeds.feedburner.com/brainyquote/QUOTEFU", _
        "http://feeds.feedburner.com/brainyquote/QUOTELO", "http://feeds.feedburner.com/brainyquote/QUOTENA"))
End Sub
First we clear the current quotes and then we fetch the new ones. Note that if the device was sleeping at this time then the calls are likely to fail as most devices turn off the wifi while sleeping. In this case new quotes will arrive when the user presses on the arrow button.
In cases like this you should not count on the automatic update to succeed and make sure that there is an alternative way to update the widget.

Persisting the data. The process running our widget code will not stay alive forever. It will be killed by the OS at some point.
Therefore we cannot rely on global variables to store our data.
All of the "state" variables must be written to a file.
RandomAccessFile.WriteObject and ReadObject are very useful for such tasks.
Each time that the widget sends a request to our application, Service_Start is called.
Not much is done in this sub:
B4X:
Sub Service_Start (StartingIntent As Intent)
    If rv.HandleWidgetEvents(StartingIntent) Then Return
End Sub
This code allows RemoteViews to raise the correct event.

However if our process is not alive yet then Service_Create will be called before. Service_Create is an important point, as it allows us to read the previously saved state to memory:
B4X:
Sub Service_Create
    'configure the widget and set it to update every 24 hours (1440 minutes).
    rv = ConfigureHomeWidget("WidgetLayout", "rv", 1440, "Quote of the day")
    HttpUtils.CallbackActivity = "WidgetService"
    HttpUtils.CallbackUrlDoneSub = "UrlDone"
    HttpUtils.CallbackJobDoneSub = "JobDone"
    parser.Initialize
   
    'Load previous data if such is available.
    'This is relevant in case our process was killed and now the user pressed on the widget.
    If File.Exists(File.DirInternalCache, QUOTES_FILE) Then
        raf.Initialize(File.DirInternalCache, QUOTES_FILE, True)
        quotes = raf.ReadObject(0)
        raf.Close
    Else
        quotes.Initialize
    End If
    If File.Exists(File.DirInternalCache, CURRENTQUOTE_FILE) Then
        currentQuote = File.ReadString(File.DirInternalCache, CURRENTQUOTE_FILE)
    End If
End Sub

The project is attached.
 

Attachments

  • Quotes.zip
    12.3 KB · Views: 2,863
Last edited:

corwin42

Expert
Licensed User
hi erel
is there a way to understand if a widget is present on any page of smathphone ?
many thanks

In my (advanced) tutorial about different widget instances I have a Sub that returns all Widget Ids of widgets which are present on any homescreen.

B4X:
'Get the Ids of all instances of this widget on the homescreen created by the given service
'
' ServiceName - The name of the WidgetService
Public Sub GetWidgetIds(ServiceName As String) As Int()
    Dim Obj1, Obj2 As Reflector
    Dim cn As Object
   
    Obj2.Target = Obj1.RunStaticMethod("android.appwidget.AppWidgetManager", "getInstance", Array As Object(Obj1.GetContext), Array As String("android.content.Context"))
    cn = Obj1.CreateObject2("android.content.ComponentName", Array As Object(Obj1.GetContext, Obj1.GetStaticField("anywheresoftware.b4a.BA", "packageName") & "." & ServiceName & "$" & ServiceName & "_BR"), Array As String("android.content.Context", "java.lang.String"))
    Return Obj2.RunMethod4( "getAppWidgetIds", Array As Object(cn), Array As String("android.content.ComponentName"))
End Sub

The sub returns an array of Ids. So if the number of Ids is bigger than 0 at least one widget is active on a homescreen.
The sub needs the name of your widget service in lowercase as a parameter.
 

chrjak

Active Member
Licensed User
Hey Erel,

Is it possible to add a miniature picture for the Widgets? For example on my Phone with Android 4.4 you can see at the Widgets Part the Design already. There is only the app logo yet.
 

Wembly

Member
Licensed User
Hi All

Can anyone please advise if its possible to configure a widget to take the full width of the screen. Using a width of 294 as recommend leaves quite a gap to the right side of the widget.

Thanks
 

zlatan

Member
Licensed User
Hi everybody,
how can I make the widget to get updated each time an internet connection is detected?
I need it particularly in case when the phone resumes from sleep mode or when it accesses a wifi network.
Thanks
 

chrjak

Active Member
Licensed User
You can try to check at _updatewidget if there is a internet connection...

Regards
 

chrjak

Active Member
Licensed User
You can make a service checking the connection. and then you can call the rvupdate manually
 
Status
Not open for further replies.
Top