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!
Related
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?
I am trying to make a phone call from the dialog fragment which is inside another fragment. However, my onRequestPermissionsResult() is not being called, whenever I choose allow or deny, it doesn't react. Here is my code:
private val PHONE_REQUEST_CODE = 100;
private lateinit var phonePermission: Array<String>
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btnOk.setOnClickListener(this)
btnCancel.setOnClickListener(this)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
phonePermission = arrayOf(android.Manifest.permission.CALL_PHONE)
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.btnOk -> {
dismiss()
makePhoneCall()
}
R.id.btnCancel -> {
dismiss()
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == PHONE_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + "111 111 111"))
startActivity(intent)
}
}
}
private fun makePhoneCall(){
if (ContextCompat.checkSelfPermission(
requireContext(),
android.Manifest.permission.CALL_PHONE
) != PackageManager.PERMISSION_GRANTED
) {
requestPermissions(
phonePermission,
PHONE_REQUEST_CODE
)
} else {
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + "111 111 111"))
startActivity(intent)
}
}
}
I have tried several solutions offered in stackoverflow for similar problems, such as replacing ActivityCompat.requestPermissions() with just requestPermissions(). But still it didn't work.
Thanks in advance
Fragment's "onRequestPermissionsResult" will be called if your hosting activity passed the onRequestPermissionsResult call to base activity.
On fragment parent activity's onRequestPermissionsResult make sure to call super.onRequestPermissionsResult
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.
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
Symptoms: upon first start my app crashes with java.lang.SecurityException: Lacking privileges to access camera service. I am getting "Unfortunately your app has crashed" dialog, clicking "OK", under this dialog there are two dialogs asking for the necessary permissions. I say "OK" to both and from now on my app works. Next starts are without the crash.
Causes: After some time of reading and debugging I came to understanding that my app problem is that it wants to do some Camera related logic before it obtains the required permissions (onSurfaceTextureAvailable callback in my CameraHandler class) or before camera surface view comes to foreground.
There's a lot of questions regarding similar errors on SO and Github, but still it's hard for me to figure out.
I tried to go through this answer, but my setup is a bit different, i.e. I have my Camera logic inside a different class that isn't an Activity, and I would really like to keep it that way not to clutter my CameraActivity class. Is there a good way to deal with this?
How to make sure that when onSurfaceTextureAvailable in my CameraHandler class is fired, the permissions are already granted, so that I am not getting java.lang.SecurityException: Lacking privileges to access camera service on the first run?
This is my SurfaceTextureListener located in CameraHandler class:
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
openCamera(width, height) //this line here makes my app crash
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
configureTransform(width, height)
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}
}
My CameraActivity onCreate, onResume() and onPause():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!canAccessCamera() || !canRecordAudio()) {
requestPermissions(INITIAL_PERMISSIONS, INITIAL_REQUEST)
}
}
}
override fun onResume() {
super.onResume()
cameraHandler.startHandler()
}
override fun onPause() {
cameraHandler.stopHandler()
super.onPause()
}
permission checking inside CameraActivity
#RequiresApi(api = Build.VERSION_CODES.M)
private fun canAccessCamera() : Boolean {
return (hasPermission(android.Manifest.permission.CAMERA))
}
#RequiresApi(api = Build.VERSION_CODES.M)
private fun canRecordAudio() : Boolean {
return (hasPermission(android.Manifest.permission.RECORD_AUDIO))
}
#RequiresApi(api = Build.VERSION_CODES.M)
private fun hasPermission(perm : String) : Boolean{
return (PackageManager.PERMISSION_GRANTED == checkSelfPermission(perm))
}
#RequiresApi(Build.VERSION_CODES.M)
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == INITIAL_REQUEST) {
if (canAccessCamera() && canRecordAudio()) {
recordButton2.setOnClickListener {
if (isRecording) {
cameraHandler.endRecording()
} else {
currentFileName = generateTimestampName()
createCSVFile(currentFileName)
cameraHandler.startStopRecording()
}
isRecording = !isRecording
}
} else {
Toast.makeText(this, "We need it to perform magic", Toast.LENGTH_SHORT).show()
}
}
}
I've written myself a little PermissionRequestHandler for exactly this purpose, you can find it here: https://gist.github.com/Hikaru755/0ae45a4184bdbc28dcc5c1af659b4508
You simply create an instance of it in your Activity, and make sure that any calls to onRequestPermissionsResult are passed to it, like in the example BaseActivity in the gist. Then you can pass it around to other classes, request permission through it and get callbacks where you need them without cluttering your Activity. Be careful not to create memory leaks by holding it longer than the Activity lives, though!