B4A Library AppUpdating 2.0 - update non-market apps

Time goes by, things change and old tools need to be updated.

AppUpdating 2.0 is a partial rewrite of AppUpdating, a lib written back in 2014 to allow the remote update of non-market applications hosted on a webserver.

Why AU 2.0? Main reason is Google's introduction of Android 8 and the profound changes on a few key points coming with it. But it was time to leverage B4x's new features too. And, finally, a bit of clean up is always due.. :)

On next two posts I'll describe the steps needed to make the lib work with your code.
Edit: see post#43 below for installing instructions related to version 2.05 and higher.

What the lib does is simply to check whether the version number reported by reading an info text file on your webserver is greater than the one showed in the running copy of your app. If it finds indication of a newer version, it downloads it, then it asks the user to install it.
Since we can't know if the user agrees to update the app, we simply go on with our app, knowing that a service burned in the lib will fire when the OS will signal that the user accepted to install the newly downloaded copy of the app. In this latter case, the same service will reload the app when ready.

Files attacched:
AppUpdating.b4xlib - version 2.05 packed following the new b4xlib standard
AU200_src - source code for the lib
AU200_demo - example program using the lib
AU200_lib - lib file compiled with B4A 8.30

Versions changelog
2.05 - made it compatible with simultaneous use of NB6
2.00 - initial release of AppUpdating 2.0
 

Attachments

  • AU200_demo.zip
    31.6 KB · Views: 1,320
  • AU200_src.zip
    14.9 KB · Views: 1,102
  • AU200_lib.zip
    25.3 KB · Views: 1,301
  • AppUpdating.b4xlib
    7.3 KB · Views: 821
Last edited:

udg

Expert
Licensed User
Longtime User
AppUpdating 2.0 - installation

Lib files installation
As usual with any contributed library, download the lib zip file (AUxxx_lib.zip, you need just this one), unzip it and place both files (.xml and .jar) in your additional libraries path (see menu option "Tools/Configure paths" in your B4A installation).
Or just compile to lib the source from AUxxx_src.

Manifest
For any app that you'd like to use this lib with, go to its Manifest editor ("Project/Manifest Editor") and add the following code:
B4X:
AddReceiverText(eu.dgconsulting.appupdating.newinst2,
  <intent-filter>
  <action android:name="android.intent.action.PACKAGE_REPLACED" />
    <data android:scheme="package" />
  </intent-filter>)

' Starting from Android 7 (API 24) we pass a file uri using a FileProvider
AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <files-path name="name" path="shared" />
)

AddManifestText(<uses-permission
   android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="19" />
)

AddPermission(android.permission.REQUEST_INSTALL_PACKAGES)

Main activity
Add to your Main activity the following code and call it before issuing an "install" command to the lib.
B4X:
'Check whether we already have permission for install other apps.
'If not we open the relevant settings page
'then wait for Activity_Resume and check the value of CanRequestPackageInstalls again
Private Sub CheckInstallationRequirements As ResumableSub
   If File.ExternalWritable = False Then
       MsgboxAsync("Storage card not available. Make sure that your device is not connected in USB storage mode.", "")
       Return False
   Else If phone.SdkVersion >= 26 And apkupdt.CanRequestPackageInstalls = False Then
       MsgboxAsync("Please allow me to install applications.", "")
       Wait For Msgbox_Result(Result As Int)
       Dim in As Intent
       in.Initialize("android.settings.MANAGE_UNKNOWN_APP_SOURCES", "package:" & Application.PackageName)
       StartActivity(in)
       Wait For Activity_Resume '<-- wait for Activity_Resume
       Return apkupdt.CanRequestPackageInstalls
   Else If apkupdt.CheckNonMarketAppsEnabled = False Then
       MsgboxAsync("Please enable installation of non-market applications." & CRLF & "Under Settings - Security - Unknown sources" _
            & CRLF & "Or Settings - Applications - Unknown sources", "")
       Return False
   Else
       Return True
   End If
End Sub
Note: the code above assumes you named the instance of the class cl_appupdate as apkupdt (Dim apkupdt As cl_appupdate). If you used a different name, change the above code accordingly.

That's it. You're now ready to use the lib.
You may want to refer to the demo attached to post#1 in order to see how the methods from the lib are used in a couple of scenarios. Or keep reading on next post about the most significant insights.
 
Last edited:

udg

Expert
Licensed User
Longtime User
AppUpdating 2.0 - how to use it

Setting the webserver
This one is easy.
On a webserver (even any free shared service would be ok), create a folder (in the example below, I created folder free_apk on webserver www.dgconsulting.eu) and eventually restrict access to it (I used the .htaccess method).
Prepare an info text file named after your apk (in the example above, you may find AppUpdateExample2.apk and AppUpdateExample2.inf) having at least one single line of text as follows:
ver=x.yz
where x.yz is the newer apk version.
Then, using any FTP client, copy both your newer apk's version and its corresponding text file in the folder set before.
That's all.
An important note about versions compare. Since versions are recorded as strings (both in attribute #VersionName and in the info text file row) and currently there's no conversion to a numeric data type, you are adviced to use a coherent scheme (e.g 1.00 to 1.20 is ok while 1.11 to 1.2 is not).

Info file
It's no longer restricted to a single row, but the very first one must be formatted as follows:
<whatever label you like>=<version number> (e.g. version=1.50, ver=1.50, myapp=1.50..).
What really matters is that the version code strictly follows the equal sign (no spaces allowed)
Following that special first row you can add two optional sections: ChangeLog and FileSize.
ChangeLog could be used to store any kind of information you's like to show to your customer before he/she decides to update. In my example I used it for a fake history log of previuous and current version of the demo app itself.
The format for the section is:
<ChangeLog>
whatever info you like
</ChangeLog>
Similarly, the FileSize optional section is enclosed in <FileSize></FileSize> tags. It is intended to report the size in bytes of the new apk. This value, if available, is used by the library to check if enough space is available on the device prior to try the download of a newer apk. It is critical that the size is expressed in bytes.
The format for the section is:
<FileSize>
1456321
</FileSize>

Your app's Main

0. Put a version number in attribute "#VersionName:" key in Module Attributes. As an example: #VersionName:1.14

1. dim a variable to access the lib's class; do this in Sub Globals
B4X:
Sub Globals
  ... 'your code here
  Dim apkupdt As cl_appupdate
End Sub

2. Add code to Activity_Create to properly initialize the lib
B4X:
Sub Activity_Create(FirstTime As Boolean)
   If FirstTime Then
     ... 'any specific code for your app initialization
   End If
   Activity.LoadLayout("Main")
   .... 'more stuff
   If FirstTime Then
       apkupdt.Initialize(Me,"sep")               'initializes the class
       apkupdt.Verbose = True                    'this one affects the verbosity of the logs
   End If
   'ALWAYS NEEDED - this is your app's package name (see "Project/BuilConfigurations/Package")
   apkupdt.PackageName = "b4a.example.appupdate2"
   'ALWAYS NEEDED - this is the complete path to the info text file holding the newer version number
   apkupdt.NewVerTxt = "http://www.dgconsulting.eu/free_apk/AppUpdateExample2.inf"
   'ALWAYS NEEDED - this is the complete path to your newer apk
   apkupdt.NewVerApk = "http://www.dgconsulting.eu/free_apk/AppUpdateExample2.apk"
   'OPTIONAL - Set credentials to access a protected folder. Not needed for this example
   apkupdt.setCredentials("test","test")
End Sub

3. All the work in a single step: check, download and install
B4X:
Sub ButtonUpdate_Click
   Wait For (CheckInstallationRequirements) Complete (Result As Boolean)
     'OPTIONAL - if you like to show a splash screen while checking for a newer apk goes on
   apkupdt.SetAndStartSplashScreen(Activity,LoadBitmap(File.DirAssets, "updating.jpg"))
     'NEEDED - this is the one you need if you want to perform "automatic" updating of your apk
   apkupdt.UpdateApk(Result) 'checks for newer apk, downloads it and asks the user to install it                                                
End Sub

4. Receive feedback from the lib
B4X:
Sub sep_UpdateComplete
   LogColor($"UpdateComplete - time: ${DateTime.Time(DateTime.Now)}"$, 0xFF556B2F)
   apkupdt.StopSplashScreen
   'too lazy to manage error conditions..check apkupdt.ERR_xxx codes if you like
   Select apkupdt.Status
       Case apkupdt.OK_CURVER
           EditText1.Text=$"${EditText1.Text}Running apk version: ${apkupdt.CurVN}${CRLF}"$
       Case apkupdt.OK_WEBVER
           EditText1.Text=$"${EditText1.Text}Webserver apk version: ${apkupdt.WebVN}${CRLF}"$
           EditText1.Text=$"${EditText1.Text}Optional Change Log data: ${apkupdt.WebChangeLog}${CRLF}"$
           EditText1.Text=$"${EditText1.Text}Optional FileSize Log data: ${apkupdt.WebFileSize}${CRLF}"$
       Case apkupdt.OK_NEWERAPK
           EditText1.Text=$"${EditText1.Text}Newer version available${CRLF}"$
       Case apkupdt.NO_NEWERAPK
           EditText1.Text=$"${EditText1.Text}No newer version available${CRLF}"$
       Case apkupdt.OK_DOWNLOAD
           EditText1.Text=$"${EditText1.Text}Newer version downloaded${CRLF}"$
       Case apkupdt.OK_INSTALL
           EditText1.Text=$"${EditText1.Text}User asked to install newer version${CRLF}"$
       Case apkupdt.ERR_NOPERM
           Log("No permission to install")
           EditText1.Text=$"${EditText1.Text}User gave no permission to install${CRLF}"$
       Case Else
           EditText1.Text=$"${EditText1.Text}Status: ${apkupdt.Status}${CRLF}"$
   End Select
End Sub
Note: sub name above is made up by a first part ("sep") choosen by you (see apkupdt.Initialize in Activity_Create) and a second mandatory substring ("_UpdateComplete") as coded in the class part of the lib.

That's all for blindingly using the lib as is (well, that's all for the coding aspect of it..)!
 

FrankDev

Active Member
Licensed User
Longtime User
i've tested the demo - Programm 'AU200_demo.zip'

B4X:
---- AppUpdating.DownloadApk
*** Service (httputils2service) Create ***
** Service (httputils2service) Start **
 new apk version downloaded and ready to install
UpdateComplete - time: 23:26:30
---- AppUpdating.InstallApk
 user asked to install new apk
Unexpected event (missing RaiseSynchronousEvents): update_updatecomplete
Check the unfiltered logs for the full stack trace.
** Activity (main) Pause, UserClosed = false **
Error occurred on line: 117 (Main)
java.lang.NullPointerException: Attempt to read from field 'anywheresoftware.b4a.BA b4a.example.appupdate2.main.activityBA' on a null object reference
 at b4a.example.appupdate2.main._update_updatecomplete(main.java:748)
 at java.lang.reflect.Method.invoke(Native Method)
 at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:710)
 at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:342)
 at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:249)
 at anywheresoftware.b4a.shell.Shell$2.run(Shell.java:313)
 at android.os.Handler.handleCallback(Handler.java:790)
 at android.os.Handler.dispatchMessage(Handler.java:99)
 at android.os.Looper.loop(Looper.java:171)
 at android.app.ActivityThread.main(ActivityThread.java:6633)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:823)
 

DonManfred

Expert
Licensed User
Longtime User

udg

Expert
Licensed User
Longtime User
Hi @FrankDev ,
unfortunately when cleaning up for final release I deleted a single important line of code. Now it should be fixed.
I uploaded amended version both for source (AU200_src) and compiled (AU200_lib).
Please try it again and thank you to make us aware of the inconvenience.
 

techknight

Well-Known Member
Licensed User
Longtime User
Perfect to see an update to this as I use it all the time! I am not targeting 26 yet so I havent had any issues, but I am sure its a matter of time before google "forces" it by switching to 64-bit only or something.

One feature I would like to see (I dont know how to implement it or I would), would be formatting the changelog in such a way that if your current app version is greater than a certain version, then it ONLY shows the changelog relevent to your version on forward. Or if your running an even newer, or even older version that was shows in the change log is more or less.

Because a person running V1.32 and the latest is V1.38 doesnt care about what happened in V1.25 But this is a speculative opinion on my part.

Just a suggestion. :)
 

udg

Expert
Licensed User
Longtime User
Hi technight,
thank you for your comments and suggestions. The spirit of the ChangeLog section is that of an area where you write whatever you feel significant for your user (not necessarily a real changelog).
A point to consider is that the info file relates 1-1 with the newer apk, so when you're ready with a new apk you could shorten the ChangeLog excluding those rows that you feel are outdated (in your example those about realese 1.25). Anyway, to accomodate your wishes it should suffice to structure a ChangeLog like the following:
<ChangeLog>
<ver>1.38
the lattest and best
</ver>
<ver>1.32
it was good enough
</ver>
<ver>1.25
initial release
</ver>
</ChangeLog>
AU function will still return all the text between the tags <ChangeLog> tags, but you can parse (regex? JSON?) what's in between and show only what you feel is right for the context.
Note: I mentioned JSON, but to use it you need to structure your free text between ChangeLog tags as a valid JSON string.

ps: I'm just thinking aloud, no plans about it for now.
 

rscheel

Well-Known Member
Licensed User
Longtime User
This allowed me to allow more numerical digits in the version

B4X:
If curver.Replace(".", "") < webver.Replace(".", "") Then  '#VersionName: 1.10.25
 

udg

Expert
Licensed User
Longtime User
Hi @rscheel,
sorry to reply so late to your message. In your code I see the same problem that affects my original setting: if the user doesn't strictly adhere to his/her own standard about versioning the code will fail.
As an example: from version 1.9.34 to versione 1.10.6
Your code will compare 1934 < 1106 --> false
My code 1.9.34 < 1.10.6 --> false (since the third char "9" is greater than the third on the right side "1").
In both cases the only way to have a correct result is to strictly adhere to an own standard representation of the Version Name. Adopting a scheme like x.yz.jk should work both for the original code and your variant (e.g. 1.09.34 < 1.10.06).

Note: I originally chose VersionName over VersionCode because the latter is number-only while the former let me use version names like 1.21b. Honestly, a few years later, it turned to be a no great idea... eheh
 

rscheel

Well-Known Member
Licensed User
Longtime User
Hi @rscheel,
sorry to reply so late to your message. In your code I see the same problem that affects my original setting: if the user doesn't strictly adhere to his/her own standard about versioning the code will fail.
As an example: from version 1.9.34 to versione 1.10.6
Your code will compare 1934 < 1106 --> false
My code 1.9.34 < 1.10.6 --> false (since the third char "9" is greater than the third on the right side "1").
In both cases the only way to have a correct result is to strictly adhere to an own standard representation of the Version Name. Adopting a scheme like x.yz.jk should work both for the original code and your variant (e.g. 1.09.34 < 1.10.06).

Note: I originally chose VersionName over VersionCode because the latter is number-only while the former let me use version names like 1.21b. Honestly, a few years later, it turned to be a no great idea... eheh


I had not realized it, you have all the reason, I'll see another way to solve the problem, perhaps extrallendo all values between the points and comparing them with each other.

It's just an idea, maybe it could be like that

B4X:
If newvesion1 > oldversion1 Then 'ex: 1 > 1
    'New version
Else
    If newvesion2 > oldversion2 Then 'ex: 10 > 09
        'New version
    Else
        If newvesion3 > oldversion3 Then 'ex 06 > 34
            'New version
        Else
            'there is no new version
        End If
    End If
End If
 
Last edited:

udg

Expert
Licensed User
Longtime User
Another way could be to treat the code as a "number" where the right-most group reprents units, the middle one hundreds and the left-most is for 10k.
Note: this work if each group can have at most two digits (and, obviously, they are digits).
1.9.34 --> 10934
1.10.6 --> 11006
10934 < 11006 --> True

Max version : 9.99.99 --> 99999
 

udg

Expert
Licensed User
Longtime User
Please note that a short snippet for the Manifest code (post #2 above) was changed due to a recent discovery.
Erel in its tutorial about Runtime Permissions now writes:
- Some Android 4.4 (API 19) devices do not allow access to RuntimePermissions.GetSafeDirDefaultExternal without explicit permission although they should. It it therefore recommended to set android:maxSdkVersion to 19 in the version based permission. It was previously set to 18.

So it now reads:
B4X:
AddManifestText(<uses-permission
   android:name="android.permission.WRITE_EXTERNAL_STORAGE"
   android:maxSdkVersion="19" />
)
 

udg

Expert
Licensed User
Longtime User
Hi @techknight ,
please read this thread. Unfortunately I still have to buy my B4A8.8 release so I couldn't try iy myself.
If the proposed solution works for you too, please post here a confirmation. thank you.
 

techknight

Well-Known Member
Licensed User
Longtime User
Oh, no no no. That isnt my issue. I usually have to recompile the lib all the time anyways between B4A versions, so that's usually my norm.

My issue is when I was running Android 8, opening 1 app or updating 1 app would launch ALL of my apps. Android 9, does not relaunch apps at all. After install, I have to manually go in and relaunch the app which is not a big deal, but it does mean the intent is not working.

Because it is an implicit intent.
 

udg

Expert
Licensed User
Longtime User
Oh, I see. The implicit broadcast model is annoying me since the beginning because waking up a number of "wrong" apps in order to meet the right one is really stupid.
I never had the time to study the subject but I can recall there's at list one lib in the forum that does support explicit broadcasts. So it should be a matter of cleaning up the Manifest (responsible for the implict message management), adding the magic lib and set up in it the needed broadcast message (MY_PACKAGE_REPLACED in this case).

What we really should do is to study the subject and integrate in AU a small part of the functionality of that lib. It should be a matter of "link" to a service gently offered by the OS; shouldn't be too difficult. Unfortunately I've got no time these days.

Edit: an interim solution may be this one.
 
Last edited:

techknight

Well-Known Member
Licensed User
Longtime User
thats what I did, was change the manifest to My_package_replaced. it didnt do anything. So I am thinking there is more to it.
 
Top