Is there some way to override a function of superclass either from extension functions or from interfaces in kotlin? - android

In android to do some tasks we require the user to grant related permissions. Complete process of checking and requesting permission involves 3 steps: checking permission, requesting permission and checking result. If the permission is denied, then don't need to run that task. So to simplify this, I wrote the following interface and extension function in ActivityExtensions.kt
private val permissionTaskSource = HashMap<Int, TaskCompletionSource<Boolean>>()
interface PermissionHandler {
#RequiresApi(Build.VERSION_CODES.M)
fun Activity.permissionRequest(requestCode: Int, vararg permissions: String): Task<Boolean> {
requestPermissions(permissions, requestCode)
val tcs = TaskCompletionSource<Boolean>()
permissionTaskSource[requestCode] = tcs
return tcs.task
}
fun permissionResult(requestCode: Int, grantResults: IntArray) {
permissionTaskSource[requestCode]!!.setResult(grantResults.all { it == PERMISSION_GRANTED })
permissionTaskSource.remove(requestCode)
}
}
suspend fun <T> T.requiresPermission(vararg permissions: String): Unit? where T : Activity, T : PermissionHandler {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
return Unit // If version lower than M then no need of runtime permissions.
if (permissions.all { ContextCompat.checkSelfPermission(this, it) == PERMISSION_GRANTED })
return Unit // If already all permissions granted, then no need to ask again.
val granted = permissionRequest(System.currentTimeMillis().toInt(), *permissions).await()
if (granted)
return Unit // Permission granted now
return null // Permission denied now
}
Whenever required some permissions to do a task in an activity or fragment, I can use this line:
private suspend fun someTask() {
requiresPermission(/* list of required permissions */) ?: run{ /*permission denied. handle properly*/ return }
// Inside fragment this has to be called with requiresActivity() receiver.
...
}
This whole thing is working fine.. but in every activity/fragment I have to write these 4 lines of code.
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<out String>, grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionResult(requestCode, grantResults)
}
Is there some way I can move these codes also to ActivityExtensions.kt file to make it much neater and avoid repetitive code?

Related

How to move boilerplate code for permission from activity/fragment to other class

I have a lot of confusion with when can you send a context as a parameter and when can you not.
In most of our applications we require at least some runtime permissions, I want to move that code out of the Activity/fragment class and move to let's say a RuntimePermissionManager.
I don't want to move it to the BaseActivity or BaseFragment class, because requesting permissions and performing actions based on the result should have a single class following single responsibility principle.
Below is the code for requesting permissions:
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_CODE_LOCATION_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Perform action
} else {
// Show description
}
}
}
}
private fun permissionCheck(permission: Array<String>, requestCode: Int, permissionGranted:() -> Unit) {
if(ContextCompat.checkSelfPermission(applicationContext,
permission[0]) == PackageManager.PERMISSION_GRANTED) {
permissionGranted()
} else {
ActivityCompat.requestPermissions(this,
permission,
requestCode)
}
}

Why doesn't camerax sample project put constant inside a class?

The Code A is from camerax sample project.
PERMISSIONS_REQUEST_CODE is a constant, I think it would be put inside a class, just like Code B.
I think Code B can reduce the couple and more clear
Is the Code A better Code B?
Code A
private const val PERMISSIONS_REQUEST_CODE = 10
private val PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
class PermissionsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!hasPermissions(requireContext())) {
// Request camera-related permissions
requestPermissions(PERMISSIONS_REQUIRED, PERMISSIONS_REQUEST_CODE)
} else {
// If permissions have already been granted, proceed
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSIONS_REQUEST_CODE) {
if (PackageManager.PERMISSION_GRANTED == grantResults.firstOrNull()) {
// Take the user to the success fragment when permission is granted
Toast.makeText(context, "Permission request granted", Toast.LENGTH_LONG).show()
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
} else {
Toast.makeText(context, "Permission request denied", Toast.LENGTH_LONG).show()
}
}
}
...
}
Code B
private val PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
class PermissionsFragment : Fragment() {
private val PERMISSIONS_REQUEST_CODE = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!hasPermissions(requireContext())) {
// Request camera-related permissions
requestPermissions(PERMISSIONS_REQUIRED, PERMISSIONS_REQUEST_CODE)
} else {
// If permissions have already been granted, proceed
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSIONS_REQUEST_CODE) {
if (PackageManager.PERMISSION_GRANTED == grantResults.firstOrNull()) {
// Take the user to the success fragment when permission is granted
Toast.makeText(context, "Permission request granted", Toast.LENGTH_LONG).show()
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
} else {
Toast.makeText(context, "Permission request denied", Toast.LENGTH_LONG).show()
}
}
}
...
}
By adding request code as compile-time constant, it can be accessed without keeping an instance of the class(like static modifier in Java).
Properties the value of which is known at compile time can be marked as compile time constants using the const modifier. Such properties need to fulfil the following requirements:
Top-level, or member of an object declaration or a companion object.
Initialized with a value of type String or a primitive type
No custom getter
Such properties can be used in annotations
The code sample makes a fragment with sole responsibility of handling Camera permission. While onActivityResult() is handled inside fragment, there might be a scenario later on when you want to do some work in Parent Activity based on the result, like for example removing this fragment when the request is complete. In order to do that, you would need access to this request code in Activity. By adding request code as compile-time constant, it can be accessed without keeping an instance of the fragment.
Other than that, there is no merit/demerit in either approaches since you will only making one instance of this fragment throughout your app anyway.
Another option would be to put the constant inside a companion object. This will allow you to make the PERMISSIONS_REQUEST_CODE constant AND statically accessible without the need to have an instant of PermissionsFragment.
Like this:
class PermissionsFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!hasPermissions(requireContext())) {
// Request camera-related permissions
requestPermissions(PERMISSIONS_REQUIRED, PERMISSIONS_REQUEST_CODE)
} else {
// If permissions have already been granted, proceed
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
}
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == PERMISSIONS_REQUEST_CODE) {
if (PackageManager.PERMISSION_GRANTED == grantResults.firstOrNull()) {
// Take the user to the success fragment when permission is granted
Toast.makeText(context, "Permission request granted", Toast.LENGTH_LONG).show()
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigate(
PermissionsFragmentDirections.actionPermissionsToCamera())
} else {
Toast.makeText(context, "Permission request denied", Toast.LENGTH_LONG).show()
}
}
}
companion object {
private const val PERMISSIONS_REQUEST_CODE = 10
}
}
Outside of PermissionsFragment you would be able to access it by writing PermissionsFragment.PERMISSIONS_REQUEST_CODE if you remove the private visibility modifier.

ActivityCompat onRequestPermissionsResult() not Called

I'm trying to write a small class to handle permission checking. Only problem is that onRequestPermissionsResult() never gets called:
class PermissionHelper(val activity: Activity) : ActivityCompat.OnRequestPermissionsResultCallback{
interface OnPermissionRequested {
fun onPermissionResponse(isPermissionGranted: Boolean)
}
private var listener: OnPermissionRequested? = null
fun isPermissionGranted(permission: String) : Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return activity.applicationContext.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED
}
return true
}
fun requestPermission(permissions: Array<String>,
requestCode: Int,
listener: OnPermissionRequested) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
this.listener = listener
activity.requestPermissions(permissions, requestCode)
}
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) {
val isPermissionGranted = grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED
listener?.onPermissionResponse(isPermissionGranted)
}
companion object {
const val PERMISSIONS_REQUEST_READ_CALENDAR = 200
const val PERMISSIONS_REQUEST_WRITE_CALENDAR = 201
const val PERMISSIONS_REQUEST_CALENDAR = 101
const val PERMISSIONS_REQUEST_LOCATION = 102
const val READ_CALENDAR = Manifest.permission.READ_CALENDAR
const val WRITE_CALENDAR = Manifest.permission.WRITE_CALENDAR
const val ACCESS_FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION
}
}
The system dialog to request the permission pops up, but the response is never handled. (i.e. the break point inside onRequestPermissionsResult() is never hit). Nothing is null, and all values are correct.
Thanks for any help.
onRequestPermissionsResult will be called on your Activity. There is nothing in this code that is going to call the onRequestPermissionsResult of your PermissionHelper.
You need this in your activity
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray) {
helper.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
My suggestion is use this library for handle permissions:
https://github.com/Karumi/Dexter
If you don't want to use Dexter library, I suggest use ActivityCompat.requestPermissions() instead of activity.requestPermissions()
You can find more info in this answer:
https://stackoverflow.com/a/56761238/5661680

App not asking for permission upon installing

Why is this not working? I've modified the Manifest too.
It is not asking for permission as I've coded. It's behaving as if I'm not even requesting for any permission.
I've made a splash screen and I want it to ask for permission before it goes to the MainActivity.
Splash screen should last for 1sec.
class SplashActivity : AppCompatActivity() {
var permissionsString = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.MODIFY_AUDIO_SETTINGS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.PROCESS_OUTGOING_CALLS,
Manifest.permission.RECORD_AUDIO)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
if (hasPermissions(this#SplashActivity,*permissionsString)){
ActivityCompat.requestPermissions(this#SplashActivity,permissionsString,131)
}else{
Handler().postDelayed({
val startAct = Intent(this#SplashActivity,MainActivity::class.java)
startActivity(startAct)
this.finish()},1000)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when(requestCode){
131 ->{
if (grantResults.isNotEmpty()
&& grantResults[0]==PackageManager.PERMISSION_GRANTED
&& grantResults[1]==PackageManager.PERMISSION_GRANTED
&& grantResults[2]==PackageManager.PERMISSION_GRANTED
&& grantResults[3]==PackageManager.PERMISSION_GRANTED
&& grantResults[4]==PackageManager.PERMISSION_GRANTED){
Handler().postDelayed({
val startAct = Intent(this#SplashActivity,MainActivity::class.java)
startActivity(startAct)
this.finish()},1000)
}else{
Toast.makeText(this#SplashActivity,"Grant it Please",Toast.LENGTH_SHORT).show()
this.finish()
}
return
}
else->{
Toast.makeText(this#SplashActivity,"Something wrong",Toast.LENGTH_SHORT).show()
this.finish()
return
}
}
}
fun hasPermissions(context: Context,vararg permissions: String): Boolean{
var hasAllPermissions = true
for (permission in permissions){
val res = context.checkCallingOrSelfPermission(permission)
if (res!=PackageManager.PERMISSION_GRANTED){
hasAllPermissions = false
}
}
return hasAllPermissions
}
}
Thats because your if condition is wrong. It should on include NOT operator to ask for permission as below:
if (!hasPermissions(this#SplashActivity,*permissionsString)) {
//Ask for permission
}
Ask for permission if you don't have the permission. In your case, you are checking if you have permission, then ask for permission which is wrong.
Check your
targetSdkVersion
in project gradle file.
It should be >= 23 to call run-time permission, otherwise will take permission automatically.

Android callbacks and code re-usage

In a fragment, I have a downloading code. and I'm sure I need the download function in the other fragments too.
So I want to make it separate file as a library from the fragment, but the code contains some android callback methods which stacked on the Activity and I don't know how to handle it if I move it to another file (Class).
The download code in the fragment,
private fun beforeDownload() {
// check permission
val externalPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (externalPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_ID_STORAGE_PERMISSION)
} else {
onDownload()
}
}
/** Android call-back method after requesting permission **/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
beforeDownload()
}
private fun onDownload() {
if (media >= 100000000) {
Toast.makeText(activity, "The media is over 100Mb", Toast.LENGTH_SHORT).show()
} else {
downloadMediaJob = launch(UI) { downloadMedia() }
}
}
// Android receiver when download completed
private val onDownloadComplete = object : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Toast.makeText(activity, R.string.download_complete_msg, Toast.LENGTH_SHORT).show()
}
}
suspend private fun downloadMedia() {
downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
...
downloadedFileId = downloadManager.enqueue(request)
}
and the callback methods are
onRequestPermissionsResult
onDownloadComplete
How can I move them to MediaDownload class so that making it reusable?
Each Fragment needs to implement it's own lifecycle callback, but that call back can simply delegate to a method on an instance of an object.
For example in your code above:
private fun beforeDownload() {
// check permission
val externalPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (externalPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_ID_STORAGE_PERMISSION)
} else {
onDownload()
}
}
/** Android call-back method after requesting permission **/
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
beforeDownload()
}
You should just move the beforeDownload() method to some "model" class, create or inject an instance and then call the beforeDownload() method on that instance.
class SomeModel() {
fun beforeDownload() {
...
}
}
Each Fragment still needs the lifecycle method, but the main part of the code can be shared in the SomeModel class
override fun onRequestPermissionsResult(requestCode: Int, permissions:
Array<out String>, grantResults: IntArray) {
instanceOfSomeModel.beforeDownload()
}
The only way to complete remove the redundancy of having to implement even the minimal lifecycle method, would be to subclass the Fragment and add the call you your method in the override in the subclass, but you don't want to do that!

Categories

Resources