I made this example to familiarize myself with the Permission Dialog sequence and came to a maybe suboptimal implementation.
So my question is: Is there a substantial better way to do implement the use case?
Use Case:
Edit: Erel has answered the question in #3. Als usual he knows a far more effective way...
Use Case:
a) The App needs to have android:targetSdkVersion = 23 Or above
b) The App shall use a public folder so that the user has access to the data content
c) The permission request must be granted as soon as possible since the visible layout depends on the contents of data in the Public folder
b) The App shall use a public folder so that the user has access to the data content
c) The permission request must be granted as soon as possible since the visible layout depends on the contents of data in the Public folder
B4X:
#Region Project Attributes
#ApplicationLabel: aaa exmpl permission request
#VersionCode: 6
#VersionName:
'SupportedOrientations possible values: unspecified, landscape or portrait.
#SupportedOrientations: unspecified
#CanInstallToExternalStorage: False
#End Region
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' This is an example to familiarize yourself with the Permission Dialog sequence
' Here especially: PERMISSION_WRITE_EXTERNAL_STORAGE
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
'
' Use Case: a) The App needs To have android:targetSdkVersion = 23 Or above
' b) The App shall use a Public folder so that the user has access To the data content
' c) The permission request must be granted As soon As possible since the visible layout
' depends on the contents of data in the Public folder.
'
' The problem to solve was that "...CheckAndRequest is not a blocking method. The program flow will continue
' and only later Activity_PermissionsResult will be raised."
' So it had to be assured that the permission was granted before we can perform App-essential startup actions
' that need the permission.
'
' Public folder structure:
' /storage/emulated/0/APPFOLDER
' /LIVE
' campusdat.db
' /TRANS
'
' Remarks to the implementation:
' It looks a bit overcomplicated with the flags but it was the only way to accomplish the task with handling the
' Activity_Resume after the Permission dialog and avoiding "main._activity' on a null object" errors.
' -->Thanks goto: https://www.b4x.com/android/forum/threads/solved-dialogs-crash-when-activity_pause.67109/
'
' *** If there is are a substantial better way to do implement the use case
' *** please provide a working example in Zip format.
'
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' Before you start make sure you have read this: (press [ctl] on your keyboard and click on the link)
'
' --> https://www.b4x.com/android/forum/threads/runtime-permissions-android-6-0-permissions.67689/#content
' Erel: "This is an optional feature.
' It is only relevant if android:targetSdkVersion is set to 23 or above.
' If the targetSdkVersion Is lower than 23 Then
' the standard permissions system will be used on all devices including Android 6+."
'
' --> https://www.b4x.com/android/forum/threads/graphical-life-cycle-of-a-b4a-activity.40515/
'
' If you granted the permission in the dialog it is persistent until you uninstall the App.
' So if you want to see the dialog again just uninstall this app and install it again.
'
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
' Additional Info:
' If you upload your app to the Playstore with
' android:targetSdkVersion="xy"
' you later cannot upload the app with a lower targetSdkVersion
' under the same packagename.
' ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
#Region Activity Attributes
#FullScreen: False
#IncludeTitle: True
#End Region
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
End Sub
Sub Globals
'These global variables will be redeclared each time the activity is created.
'These variables can only be accessed from this module.
Dim rp As RuntimePermissions
End Sub
Sub Activity_Create(FirstTime As Boolean)
Starter.intCntAC = Starter.intCntAC +1
Log($"#- starter.intCntAC=${Starter.intCntAC} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)
If FirstTime Then
Starter.bolPermission_SDWRITEX = False
Starter.flg_PermissionDialogJustFinished = False
Starter.flg_PermissionDialogManualCall = False
End If
'Do not forget to load the layout file created with the visual designer. For example:
'Activity.LoadLayout("Layout1")
End Sub
Sub Activity_Resume
Starter.intCntAR = Starter.intCntAR +1
Log($"#- starter.intCntAR=${Starter.intCntAR} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)
Log($"#- starter.flg_PermissionDialogJustFinished=${Starter.flg_PermissionDialogJustFinished}"$)
If Starter.flg_PermissionDialogJustFinished Then
' This part is executed after the Permissionrequest is finished
Log("#- (do whatever needed in THIS Activity_Resume)")
Else
' Asks for SD WRITE EXTERNAL permission (it's not blocking the sequence of code, causes PAUSE and RESUME)
If Not(Starter.bolPermission_SDWRITEX) Then
Log("#- bolPermission_SDWRITEX=false")
CallSubDelayed(Me, "CheckPermissions")
Else
Log("#- bolPermission_SDWRITEX=TRUE")
End If
End If
' ...
End Sub
Sub Activity_Pause (UserClosed As Boolean)
Starter.intCntAP = Starter.intCntAP +1
Log($"#- starter.intCntAP=${Starter.intCntAP} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)
End Sub
Sub CheckPermissions
Log("#-CheckPermissions .. .. .. .. .. .. ..")
If rp.Check(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) Then
Log("#- rp.Check(..)=TRUE -->permission already GRANTED")
Starter.flg_PermissionDialogManualCall = True
Activity_PermissionResult(rp.PERMISSION_WRITE_EXTERNAL_STORAGE, True)
Else
Log("#- rp.Check(..)=false -->ask for permission")
Starter.flg_PermissionDialogManualCall = False
rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
End If
End Sub
Sub Activity_PermissionResult (Permission As String, Result As Boolean)
Log($"#-Activity_PermissionResult, Permission=${Permission}, Result=${Result}.. .. .. .."$)
Log($"#- starter.flg_PermissionDialogManualCall=${Starter.flg_PermissionDialogManualCall}"$)
If Starter.flg_PermissionDialogManualCall Then
Starter.flg_PermissionDialogJustFinished = False
Else
Starter.flg_PermissionDialogJustFinished = True
End If
Select Permission
Case rp.PERMISSION_WRITE_EXTERNAL_STORAGE
If Result Then
Starter.bolPermission_SDWRITEX = True
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' Finaly the "permission-needy" procedures can be called
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
CallSubDelayed(Me, "PrepareAppsPermissionDependantParts")
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
Else
Dim intIlResult As Int=Msgbox2("This App needs to write data to a public folder. Try again?", "Access to public folder" ,"Yes","","No",Null)
If intIlResult = DialogResponse.POSITIVE Then
Log("#- try again...")
CallSubDelayed(Me, "CheckPermissions")
Else
Starter.flg_PermissionDialogJustFinished = False
Log("#- activity finish x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-")
Activity.Finish
End If
End If
End Select
End Sub
' ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
Sub PrepareAppsPermissionDependantParts
Log("#-PrepareAppsPermissionDependantParts.. .. .. .. .. .. .. .. .. .. ..")
' App procedures that depend on the permission
' Make sure public folders exist
PrepWorkPaths
CreateWorkDirs
' Init public database files etc.
OpenDatabases
' ...
End Sub
Sub PrepWorkPaths
Log("#-PrepWorkPaths.. .. .. .. .. .. .. .. .. .. ..")
' For test purposes this creates a Random Foldername. For a real App here goes the name of the public folder (e.g. "AppXy_Data")
Starter.strWorkDir = $"_Test_${DateTime.GetMinute(DateTime.Now)}_${DateTime.GetSecond(DateTime.Now}"$ )
' Define folder startpoint
If File.ExternalWritable Then
Starter.strBasPath = File.DirRootExternal ' Public folder, easy to find for the user and other Apps
Else
Starter.strBasPath = File.DirInternal & "/" & Starter.strWorkDir
End If
Starter.strMyWorkPath = File.Combine(Starter.strBasPath, Starter.strWorkDir)
If File.Exists(Starter.strMyWorkPath, "") Then
Starter.bolWorkPathExists = True
End If
Dim bolInfoOn As Boolean = False ' <<-- set this to TRUE to see more info in the Log()
#region --- info ---
If bolInfoOn Then
'
'
' In order to Log() the "android:TargetSdkVersion" you need Informatix's "PackageUtils"
' (read this: https://www.b4x.com/android/forum/threads/probundle-chargeable.58754/ )
' ...
' PackageUtils v2.0
' This library replaces the PackageManager class of the Phone library.
' It gives plenty of informations on packages (activities, features, permissions, receivers, services, etc.)
' And can list the features available on the system (camera, gps, wifi, etc.).
' It allows experts To change the enabled state of components.
' An Application Is provided with the library To show what you can get with it.
' ...
'
' If you have the PackageUtils library, then you can uncomment the next 4 lines
' Dim PU As PackageUtils
' Dim AppInfo As PkgApplicationInfo = PU.GetApplicationInfo(PU.GetMyPackageName)
' Log($"#-MyPackageName = ${PU.GetMyPackageName}"$)
' Log($"#-TargetSdkVersion = ${AppInfo.TargetSdkVersion}"$)
'
Dim ph As Phone
Log($"#-ph.SdkVersion = ${ph.SdkVersion}"$)
Log("#- --- --- --- --- --- --- ")
Log($"#-File.ExternalWritable = ${File.ExternalWritable}"$)
Log($"#-File.DirInternal = ${File.DirInternal}"$)
Log($"#-File.DirRootExternal = ${File.DirRootExternal}"$)
Log($"#-file.DirDefaultExternal = ${File.DirDefaultExternal}"$)
Log("#- --- --- --- --- --- --- ")
Log($"#-GetSafeDirDefaultExternal('')= ${rp.GetSafeDirDefaultExternal("") }"$)
Dim strSafeDirExternal() As String = rp.GetAllSafeDirsExternal("")
For i=0 To strSafeDirExternal.Length -1
Log($"#- strSafeDirExternal(${i})= ${strSafeDirExternal(i)}"$)
Next
Log("#- --- --- --- --- --- --- ")
End If
#end region
End Sub
Sub CreateWorkDirs
Log("#-CreateWorkDirs .. .. .. .. .. .. ..")
' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °
' Create new folders, if needed
' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °
If Not(File.Exists(Starter.strMyWorkPath, "")) Then
File.MakeDir(Starter.strMyWorkPath, "")
End If
'
Starter.strDbPathLiveDats = File.Combine(Starter.strMyWorkPath, "LIVE")
If Not(File.Exists(Starter.strDbPathLiveDats, "")) Then
File.MakeDir(Starter.strDbPathLiveDats, "")
End If
'
Starter.strDbPathTRS = File.Combine(Starter.strMyWorkPath, "TRANS")
If Not(File.Exists(Starter.strDbPathTRS, "")) Then
File.MakeDir(Starter.strDbPathTRS, "")
End If
' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °
' Check for results
Log($"#- starter.strBasPath, exists=${File.Exists(Starter.strBasPath, "")} --> ${Starter.strBasPath}"$)
Log($"#-starter.strMyWorkPath, exists=${File.Exists(Starter.strMyWorkPath, "")} --> ${Starter.strMyWorkPath}"$)
Log($"#-starter.strDbPathLiveDats, exists=${File.Exists(Starter.strDbPathLiveDats, "")} --> ${Starter.strDbPathLiveDats}"$)
Log($"#-starter.strDbPathTRS, exists=${File.Exists(Starter.strDbPathTRS, "")} --> ${Starter.strDbPathTRS}"$)
End Sub
Sub OpenDatabases
Log("#-OpenDatabases .. .. .. .. .. .. ..")
If File.Exists(Starter.strDbPathLiveDats, Starter.strDbName1) = False Then
Try
File.Copy(File.DirAssets, Starter.strDbName1, Starter.strDbPathLiveDats, Starter.strDbName1 )
Catch
Log("cambrdg-DBP-C1-Badpath: " & Starter.strDbPathLiveDats)
Dim ph As Phone
Msgbox($"Bad database path: ${CRLF}${CRLF} ${Starter.strDbPathLiveDats}"$, $"System fail - SDK ${ph.SdkVersion} "$)
Activity.Finish
End Try
End If
Log("#- ...initialize SQL etc.")
End Sub
' ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴
B4X:
#Region Service Attributes
#StartAtBoot: False
#ExcludeFromLibrary: True
#End Region
Sub Process_Globals
'These global variables will be declared once when the application starts.
'These variables can be accessed from all modules.
Dim strWorkDir As String = "MyPublicFolder"
Dim strBasPath As String = ""
Dim strMyWorkPath As String = ""
Dim bolWorkPathExists As Boolean = False
Dim strDbPathLiveDats As String = ""
Dim strDbPathTRS As String = ""
Dim strDbName1 As String = "campusdat.db"
Dim bolPermission_SDWRITEX As Boolean = False
Dim flg_PermissionDialogJustFinished As Boolean = False
Dim flg_PermissionDialogManualCall As Boolean = False
Dim intCntAC As Int = 0 ' Just for test
Dim intCntAR As Int = 0 ' Just for test
Dim intCntAP As Int = 0 ' Just for test
End Sub
Sub Service_Create
'This is the program entry point.
'This is a good place to load resources that are not specific to a single activity.
End Sub
Sub Service_Start (StartingIntent As Intent)
End Sub
Sub Service_TaskRemoved
'This event will be raised when the user removes the app from the recent apps list.
End Sub
'Return true to allow the OS default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
Return True
End Sub
Sub Service_Destroy
End Sub
Attachments
Last edited: