Android Question How to automate app updating?

udg

Expert
Licensed User
Longtime User
Note: posts from #1 to #8 refer to an old version of the library based on an Activity object. Please start reading from post #9 to follow ongoing work on current version of AppUpdating.

Hi all,

I nearly completed my first "useful" app so I'm faced with the problem of how to automatically update it when new releases will be ready.
I'm not going to use any official market, just a dir on my Drupal website.

What I did so far, was to add a simple text file named after the app (in my case tmb.apk and tmb.txt) in that same dir and have its first (and only) row like this one: ver=1.02
Then I developed 3 simple functions GetCurVN, GetWebVN and UpdateApk to simply read the Version Number buried in the currently executing app (ATTRIBUTE #VersionName), the one stored on the web text file and finally download and install the new app version (if the case).

Since both GetWebVN and UpdateApk rely on HttUtils2.Download mechanism I am not able to wait for the completion of the first call before checking version and eventually call the updating sub.

How do you accomplish you apps' updating?

TIA

Umberto
 
Last edited:

qsrtech

Active Member
Licensed User
Longtime User
Hi Um, managed to build a quick and dirty "push" update. Works pretty good. One thing you have to be careful of is having hundreds/thousands of users trying to update the app all at once. Originally I auto downloaded the app after receiving the push notification but then realized how dangerous this could be so now I just download the new version when the user clicks the notification.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi qsrtech,

let me congratulate with you! I too agree with your solution to avoid auto-download the newer app on push notification.
Would you like to share more details of your whole push service?
Does any app need to incorporate a service in order to listen to an eventual push message announcing the availability of a newer version?

Umberto
 
Upvote 0

qsrtech

Active Member
Licensed User
Longtime User
It's not complicated but it does involve a fair bit of steps.
1. Setup GCM service
2. Implement some of Erel's push service Registration/Receiving code
NOTE YOU NEED TO UPDATE YOUR MANIFEST AS PER EREL'S PUSH EXAMPLE
B4X:
'registration storage support subs
Sub getRegistrationID(SenderID As String) As Boolean
    Dim regid As String
    Dim pm As PreferenceManager
    regid=pm.GetString(SenderID)
    If regid="" OR AppVersion<>QSRGlobals.GetVersionname Then
        Log("getRegistrationID=false")
        Return False
    Else
        Log("getRegistrationID=true")
        Return True
    End If
End Sub

Sub storeRegistrationID(SenderID As String,regID As String)
    Dim pm As PreferenceManager
    pm.SetString(SenderID,regID)
End Sub

Sub RegisterDevice (Unregister As Boolean,pid As String)
    Dim i As Intent
    'i wish there was a way to include the ID in the return intent so we know what Project ID was registered
    ProjectID=pid 'use this global variable to hopefully get the right Project ID with RegID when it comes back registered
    If Unregister Then  
        i.Initialize("com.google.android.c2dm.intent.UNREGISTER", "")
    Else
        i.Initialize("com.google.android.c2dm.intent.REGISTER", "")
        i.PutExtra("sender",pid)
    End If
    Dim r As Reflector
    Dim i2 As Intent
    i2 = r.CreateObject("android.content.Intent")
    Dim pi As Object
    pi = r.RunStaticMethod("android.app.PendingIntent", "getBroadcast", _
        Array As Object(r.GetContext, 0, i2, 0), _
        Array As String("android.content.Context", "java.lang.int", "android.content.Intent", "java.lang.int"))
    i.PutExtra("app", pid)
    StartService(i)
End Sub

Sub HandleRegistrationResult(Intent As Intent)
    Log(Intent.ExtrasToString)
    If Intent.HasExtra("error") Then
        Log("Error: " & Intent.GetExtra("error"))
        ToastMessageShow("Error: " & Intent.GetExtra("error"), True)
    Else If Intent.HasExtra("unregistered") Then
        'Empty id is sent here. This will cause the board to delete this name.
    Else If Intent.HasExtra("registration_id") Then
        Dim rid As String
        rid = Intent.GetExtra("registration_id")
        Log(rid)
        Dim sql As String
        Dim pid As String
        sql="execute dbo.[spUpdatePushDevices] '" & ProjectID & "','" & rid & "'," & SOMEID '(@ServiceID varchar(50),@DeviceID varchar(500),@Data Int,@Platform tinyint=0,@Active Bit=1)
        Log(sql)
        Dim http As HttpJob
        http.Initialize("UpdatePushDevices",Me,"UpdatePushDevicesDone")
        Log(sql)
'note this uses my custom implementation of the httpservice
        http.ExtraData=rid
        http.ExtraData2=pid
        http.PostString("YOUR SCRIPT HERE",sql)
    End If
End Sub

Sub UpdatePushDevicesDone(job As HttpJob)
    If job.Success Then
        Dim result As String=job.GetString
        If result<>"0" Then
            Globals.storeRegistrationID(job.ExtraData2,job.ExtraData)
        Else
            Log("no pushid")
        End If
    End If
    job.Release
End Sub
3. I use SQL Server DB to maintain list of "subscribers" (after registration you send the ID to your DB Server)
B4X:
CREATE TABLE [dbo].[PushSetup](
    [RowID] [int] IDENTITY(1,1) NOT NULL,
    [UnitID] [int] NOT NULL,
    [ServiceID] [varchar](50) NOT NULL,
    [APIKey] [varchar](50) NOT NULL,
    [PlatForm] [tinyint] NOT NULL CONSTRAINT [DF_PushSetup_PlatForm]  DEFAULT ((0)),
    [BrandID] [int] NOT NULL CONSTRAINT [DF_PushSetup_BrandID]  DEFAULT ((0)),
    [Description] [varchar](50) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[PushSetup_Devices](
    [PushID] [int] IDENTITY(1,1) NOT NULL,
    [ServiceID] [varchar](50) NOT NULL,
    [DeviceID] [varchar](500) NOT NULL,
    [Data] [int] NOT NULL CONSTRAINT [DF_PushSetup_Devices_Data]  DEFAULT ((0)),
    [Active] [bit] NOT NULL CONSTRAINT [DF_PushSetup_Devices_Active]  DEFAULT ((1)),
    [Platform] [tinyint] NOT NULL CONSTRAINT [DF_PushSetup_Devices_Platform]  DEFAULT ((0))
) ON [PRIMARY]
--Platform defaults to 0 for android, caller could send "1" for Apple, "2" for MS, etc.
--ServiceID is the ID of the GCM project, not sure how apple, etc. is yet
CREATE PROCEDURE dbo.[spUpdatePushDevices] (@ServiceID varchar(50),@DeviceID varchar(500),@Data int,@Platform tinyint=0,@Active bit=1)
as
--check if we have a device registered for this service yet
DECLARE @PushID int;set @PushID=0
DECLARE @IDCount int
SELECT @IDCount=count(*) FROM EasyOrder.POS_PushSetup_Devices
WHERE Data=@Data and ServiceID=@ServiceID
IF @IDCount>1 --just in case we have dupes we wanna delete them
BEGIN
delete from EasyOrder.POS_PushSetup_Devices
WHERE Data=@Data and ServiceID=@ServiceID and Platform=@Platform
END 
SELECT @PushID=isnull(pushid,0) FROM EasyOrder.POS_PushSetup_Devices
WHERE Data=@Data and ServiceID=@ServiceID --and Platform=@Platform
----if so then probably just updating it's subscription, i.e. on/off or data
IF @PushID<>0
BEGIN
    UPDATE EasyOrder.POS_PushSetup_Devices SET Data=@Data,Active=@Active,Platform=@Platform
    WHERE PushID=@PushID
END
ELSE
BEGIN
    INSERT INTO EasyOrder.POS_PushSetup_Devices (ServiceID,DeviceID,Data,Active,Platform)
    VALUES(@ServiceID,@DeviceID,@Data,@Active,@Platform)
    SET @PushID=(SELECT @@IDENTITY)
END
SELECT @PushID
4. Post notification (I use a COM DLL I made to send the push notification from SQL Server)
B4X:
CREATE Procedure dbo.[spSendPush](@APIKey varchar(50),@DeviceID varchar(500),@Data varchar(max))
as
DECLARE @object int;
DECLARE @hr int;
DECLARE @src varchar(255), @desc varchar(255);
EXEC @hr = sp_OACreate 'GCM.GCMObject', @object OUT,1;
IF @hr <> 0
BEGIN
  EXEC sp_OAGetErrorInfo @object, @src OUT, @desc OUT
  --raiserror('Error Creating COM Component 0x%x, %s, %s',16,1, @hr, @src, @desc)
    RETURN
END;
ELSE
BEGIN
EXEC @HR = sp_OAMethod @Object,
        N'SendMessage',
        NULL,
        @apikey,
        @deviceid,
        @data;--need to add platform to this call so our COM DLL can decide which url to use
END
5. Upload APK to Server
6. Notify Subscribers of new version
B4X:
CREATE PROCEDURE dbo.[spSendUpdateApp]
as
--see if they subscribe to pushes
DECLARE @ServiceID varchar(50)
DECLARE @APIKey varchar(50)
DECLARE @DeviceID varchar(MAX)
SET @ServiceID='ENTER YOURS HERE'
--could skip this next line and just hard code the APIKey if desired
SELECT @APIKey=apikey from dbo.PushSetup where ServiceID=@ServiceID
--will have an issue here if over 1,000 subscribers, need to cycle through each 1,000
SELECT    @DeviceID=COALESCE(@DeviceID+'","' ,'') + PSD.DeviceID
FROM  dbo.PushSetup_Devices AS PSD
WHERE    (PSD.ServiceID = @ServiceID)
--send a push notifying them the update
DECLARE @Data varchar(max)
SET @Data='1' --use whatever code you want here
exec dbo.spSendPush @APIKey,@DeviceID,@Data
7. Receive Notification
B4X:
Sub MessageArrived (Intent As Intent)
    Dim From, CollapseKey, data As String 'ignore
    If Intent.HasExtra("from") Then From = Intent.GetExtra("from")
    If Intent.HasExtra("data") Then data = Intent.GetExtra("data")
    If Intent.HasExtra("collapse_key") Then CollapseKey = Intent.GetExtra("collapse_key")
    Log(Intent.ExtrasToString)
    'Here you should handle the new message:
    Log("New message arrived: " & data)
    Select Case From 'use case to test who sent it in case we have multiple push service
        Case Main.ProjectID
            If data="1" Then
                'create a notification icon that a new version is ready to install
                UpdateApp
            Else
                'do something else
            End If
        Case Else
    End Select
End Sub

Sub UpdateApp
    Dim ni As Notification
    ni.Initialize
    ni.Icon="icon"
    ni.AutoCancel=True
    ni.Sound=True
    ni.Vibrate=True
    ni.SetInfo2("App Update","Click here to install new version","update",Main)
    ni.Notify(1)
End Sub
8. Download APK from server
B4X:
Sub Activity_Resume
    Dim i As Intent
    i=Activity.GetStartingIntent
    If i.HasExtra("Notification_Tag") Then
        If i.GetExtra("Notification_Tag")="update" Then
            'how to update app now?
            CallSubDelayed(Me,"DownloadApp")
        End If
    End If
End Sub

Sub DownloadApp
    Dim dl As RSAsyncDownloader
    dl.Initialize("AsyncDownloader")
    dl.Directory=File.DirDefaultExternal
    dl.FileName="YOURAPP.APK"
    If File.Exists(dl.Directory,dl.FileName) Then
        Try
            File.Delete(dl.Directory,dl.FileName)
        Catch
            Log(LastException)
        End Try
    End If
    dl.Download(APKURL)
End Sub

Private Sub AsyncDownloader_Started
'The download has started.
    pnlProgress.Visible=True
End Sub

Private Sub AsyncDownloader_Update (Progress As Int)
'Monitor the progress here.
    ProgressBar1.Progress=Progress
End Sub

Sub cmdCancel_Click 'user canceled it
    pnlProgress.Visible=False
    UpdateCanceled=True
End Sub
9. use PackageManager Intent to install downloaded APK
B4X:
Private Sub AsyncDownloader_Finished (Result As String)
'The download has finished.
    'I wish the result contained the whole filename so we could skip a bunch of code
    Dim async As RSAsyncDownloader
    async=Sender
    pnlProgress.Visible=False
    If Not(UpdateCanceled) Then
        Dim pm As Intent
        Log(async.Directory & "/" & async.FileName)
        pm.Initialize(pm.ACTION_VIEW, "file://" & async.Directory & "/" & async.FileName)'"file:///sdcard/MyAPK.apk")
        pm.SetType("application/vnd.android.package-archive")
        StartActivity(pm)
    End If
End Sub
 
Last edited:
Upvote 0

udg

Expert
Licensed User
Longtime User
Thank you, qsrtech.
I hope that many of us could make good use of your so kindly shared info.

As for my AppUpdating lib, I'm gonna to release a new cleaned-up version and start a new thread about how to automatically relaunch an updated app.

Umberto
 
Upvote 0

GaNdAlF89

Active Member
Licensed User
Longtime User
My question is: after the download of new apk, it is automatically installed or it prompts the installation to user? Thanks
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi Gandalf!

Current experimental version of AppUpdating does automatic updating. This means that once the newer version is downloaded from the server the running apk code is stopped and the newer one is loaded in memory.

For the bad news: I'm experiencing a few problems with the auto-reload mechanism. I hope, with the help of more knowledgeble people here in the NG to overcome them as soon as possible.

No problem to share the work in progress as is, since my final goal is still the publishing of the library along with its source code.

Umberto
 
Upvote 0

Stephenz43

Member
Licensed User
Longtime User
Hello

I am reviewing appUpdating ( 1.05 ) and have been throwing a error as follows :
---------JobDone-----JobName = Job3,Sucess = true
Read while on Job 3 : 2.00 <--------version number
web version:
java.lang.numberFormatException: invalid double: <====ERROR in here

Any help would be appreciated
Stephen
 
Upvote 0

JakeBullet70

Well-Known Member
Licensed User
Longtime User
Check your number in you appName.txt file on your server.
Also check your #VersionName in Module Attributes

they need to be X.XX in numbers format like 7.15 or 2.00
 
Upvote 0

JakeBullet70

Well-Known Member
Licensed User
Longtime User
Umberto

This is working great, took me 20 minutes to get it to function in my new app. GREAT WORK!!!
I am too having an issue with the restart.

Have you seen this code?

B4X:
Public Sub RestartApp
    g.ToastMessageShowX("Re-Starting...",True)
    Activity.Finish
    Dim r, r2 As Reflector
    r.Target = r.GetActivity
    r.Target = r.RunMethod("getApplicationContext")
    r2.Target = r.RunMethod("getPackageManager")
    Dim I As Intent = r2.RunMethod2("getLaunchIntentForPackage", r.RunMethod("getPackageName"), "java.lang.String" )
    r.Target = I
    r.RunMethod2("addFlags",  67108864, "java.lang.int")
    StartActivity(I)
End Sub

I found it on the forum here somewhere a while back.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi Stephen,

seems your problem derives from the "dirty" way my lib treats the line read from the text file.
It expects a line formed as:
ver=x.xx
so internally it uses "webver=Job.GetString.SubString(4)".
I shoud change it to something that looks for a period or other "smart" version discovery..but I'm soooo lazy!

Umberto
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi Jake,
glad to hear AppUpdating worked for you!

As noted above I'm having a few issues with auto-reloading. Here what I learned so far:

I added a service as part of the lib and in its Service-Start I coded
B4X:
If StartingIntent.Action = "android.intent.action.PACKAGE_REPLACED" Then
    Log("package replaced intent received!")
     pkg = GetPackageName
    If StartingIntent.GetData="package:" & pkg Then
      MyAppReload
    End If
End If  
..

Sub GetPackageName As String
  Dim r As Reflector
  Return r.GetStaticField("anywheresoftware.b4a.BA", "packageName")
End Sub

Sub MyAppReload
   Log("MyAppReload**** ")
  If IsPaused("main") Then
    StartActivity("main")
  End If
End Sub
To have this to work, we need to modify our app's Manifest (not the library one, as I hoped for).
B4X:
AddReceiverText(eu.dgconsulting.appupdating.newinst2,
  <intent-filter>
  <action android:name="android.intent.action.PACKAGE_REPLACED" />
  <data android:scheme="package" />
  </intent-filter>)

Since you are using the lib, maybe it's time to publish it along with its source in the contributed lib area.

Umberto
 
Upvote 0

JakeBullet70

Well-Known Member
Licensed User
Longtime User
Yes, time to release it. Besides the re-start issue it works GREAT!!!

I think if you include the source then other people can study and improve it.
I wish more people would include source with their library's.

Thanks again for sharing!

--Jake
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi all,

finally I dared to publish AppUpdating lib.
Please find its most recent version here.

Note: this last version breaks existing code due to some renaming and a different way to track its internal status. Sorry for the inconvenience.

Umberto
 
Upvote 0

Robert Loiacono

Member
Licensed User
Longtime User
I have had success using DropBox. On finding a different "version code" in a downloaded .txt file and comparing it, the User is advised an update exists. The update is also downloaded and sits in (root) where the User can install it easily (tested so far OK).
Still working on improvements but for the users of my work, this is good. They sync their tablets with DropBox regularly and each time the version code is compared. Next looking to set up a button to point to and open the app file and see if I can invoke the app install dialog.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Hi Robert,

glad to hear that some of the previous posts were useful to you.
If I can help somehow with next step, just ask.

Umberto
 
Last edited:
Upvote 0
Top