B4A Library AppUpdating 2.0 - update non-market apps

Discussion in 'Additional libraries, classes and official updates' started by udg, Oct 19, 2018.

  1. udg

    udg Expert Licensed User

    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.

    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:
    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.00 - initial release of AppUpdating 2.0
     

    Attached Files:

    Last edited: Oct 19, 2018
    fabricio, Xicu, AnandGupta and 14 others like this.
  2. udg

    udg Expert Licensed 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:
    Code:
    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.
    Code:
    '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: Nov 12, 2018
    Procesor, Almora, johndb and 2 others like this.
  3. udg

    udg Expert Licensed 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
    Code:
    Sub Globals
      ... 
    'your code here
      Dim apkupdt As cl_appupdate
    End Sub
    2. Add code to Activity_Create to properly initialize the lib
    Code:
    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
    Code:
    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
    Code:
    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..)!
     
    stu14t, Procesor, Almora and 5 others like this.
  4. FrankDev

    FrankDev Active Member Licensed User

    i've tested the demo - Programm 'AU200_demo.zip'

    Code:
    ---- 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)
     
  5. DonManfred

    DonManfred Expert Licensed User

    Please create a NEW Thread kin the questions forum instead of posting to this thread.
     
  6. udg

    udg Expert Licensed 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.
     
  7. techknight

    techknight Well-Known Member Licensed 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. :)
     
  8. udg

    udg Expert Licensed 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.
     
    techknight likes this.
  9. rscheel

    rscheel Well-Known Member Licensed User

    This allowed me to allow more numerical digits in the version

    Code:
    If curver.Replace(".""") < webver.Replace("."""Then  '#VersionName: 1.10.25
     
  10. udg

    udg Expert Licensed 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 and DonManfred like this.
  11. rscheel

    rscheel Well-Known Member Licensed User


    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

    Code:
    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: Oct 29, 2018
  12. udg

    udg Expert Licensed 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
     
    rscheel likes this.
  13. udg

    udg Expert Licensed 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:
    Code:
    AddManifestText(<uses-permission
       android:name=
    "android.permission.WRITE_EXTERNAL_STORAGE"
       android:maxSdkVersion=
    "19" />
    )
     
    Erel and DonManfred like this.
  14. techknight

    techknight Well-Known Member Licensed User

    PACKAGE_REPLACED is considered an implicit broadcast, and with android 8.0 and higher, is restricted so I have problems with it.

    https://developer.android.com/about/versions/oreo/background#services

    MY_PACKAGE_REPLACED is not, and listens only for the specific package. Not sure how to modify the system to get that to work properly though.
     
  15. udg

    udg Expert Licensed 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.
     
  16. techknight

    techknight Well-Known Member Licensed 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.
     
  17. udg

    udg Expert Licensed 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: Jan 29, 2019
  18. techknight

    techknight Well-Known Member Licensed 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.
     
  19. udg

    udg Expert Licensed User

  20. MbedAndroid

    MbedAndroid Active Member Licensed User

    appupdating 2.00 isnt compatible with the new b4a 8++ series. Attached a B4x appupdating lib which works well.
    Thanks for UDG for his great work!
    Dont forget to put this lib in the b4x folder
     

    Attached Files:

    stu14t, Erel and udg like this.
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice