I am trying to use a camera on my android. the user will be prompt for the permission of the camera. previously I used startActivityForResult and onRequestPermissionRequest for them. recently I found out that they are deprecated, so I'm trying out with registerForActivity. I managed to change to startActivity but I'm stuck at the permission request. I am wondering do I have to create another permissionlauncher or can I do the permission inside my resultlauncher.
companion object{
private const val CAMERA_PERMISSION_CODE = 1
private const val CAMERA_REQUEST_CODE = 2
}
val checkpermission = Manifest.permission.CAMERA
var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
val DP: Bitmap = data!!.extras!!.get("data") as Bitmap
val image = findViewById<ImageView>(R.id.imageButtonVerifyPhoto)
image.setImageBitmap(DP)
}
}
val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()){
isGranted ->
if(isGranted){
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
resultLauncher.launch((intent))
Toast.makeText(this,"Permission is tested", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(this,"Permission is denied",Toast.LENGTH_SHORT).show()
}
}
var cameraButton = findViewById<Button>(R.id.buttonRetakePhoto) // can change later
cameraButton.setOnClickListener {
if(ContextCompat.checkSelfPermission(
this,
checkpermission
) == PackageManager.PERMISSION_GRANTED
){
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
resultLauncher.launch(intent)
}else{
permissionLauncher.launch(checkpermission)
}
}
}
below is my previous code for the onRequestPermission
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if(requestCode == CAMERA_REQUEST_CODE){
if(grantResults.isNotEmpty()&& grantResults[0] == PackageManager.PERMISSION_GRANTED){
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(intent, CAMERA_REQUEST_CODE)
}else{
Toast.makeText(this,"Permission is denied",Toast.LENGTH_SHORT).show()
}
}
}
Requesting runtime permissions is just a little more simplified
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
// do something
}
Now we can call this to get any type of permission you want
cameraButton.setOnClickListener {
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.CAMERA
) != PackageManager.PERMISSION_GRANTED
) {
// Pass any permission you want while launching
requestPermission.launch(Manifest.permission.CAMERA)
}
}
Make sure to add in build.gradle
implementation 'androidx.fragment:fragment-ktx:1.2.0' // or later
implementation 'androidx.activity:activity-ktx:1.3.0' // or later
If you want to understand how all this works check here
Latest soluton in 2022: (no more request code)
Create Helper Extension Functions (for use in Fragment):
fun Fragment.requestPermissions(request: ActivityResultLauncher<Array<String>>, permissions: Array<String>) = request.launch(permissions)
fun Fragment.isAllPermissionsGranted(permissions: Array<String>) = permissions.all {
ContextCompat.checkSelfPermission(requireContext(), it) == PackageManager.PERMISSION_GRANTED
}
Request Permissions in Fragment:
class FirstFragment : Fragment() {
companion object {
private val PERMISSIONS = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
private lateinit var binding: FragmentFirstBinding
private lateinit var permissionsRequest: ActivityResultLauncher<Array<String>>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_first, container, false)
permissionsRequest = getPermissionsRequest()
binding.grantButton.setOnClickListener {
requestPermissions(permissionsRequest, PERMISSIONS) //extension function
}
return binding.root
}
private fun getPermissionsRequest() = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
if (isAllPermissionsGranted(PERMISSIONS)) { //extension function
//do your stuff
} else {
//do your stuff
}
}
}
You could try Aaper, it allows to request for permissions using annotations like so:
Fist, add the permission to your manifest:
<uses-permission android:name="android.permission.CAMERA" />
Then, annotate a method that needs to use the camera:
#EnsurePermissions(permissions = [Manifest.permission.CAMERA])
fun takePhoto() {
Toast.makeText(this, "Camera permission granted", Toast.LENGTH_SHORT).show()
}
That's it, when you call the takePhoto method, it'll check for the camera permission, and if it's not available it will launch a permission request dialog. If the user approves it, then it will proceed to run takePhotos body.
Disclaimer, I'm the creator of Aaper
Related
i'm making login application with firebase, I enabled Email/password and google providers both are working perfectly on Api less than 31 but on levels above that google works but Email/password doesn't and i don't know the problem.
What i tried to do :
-updated all dependencies to last version.
-added implementation 'androidx.work:work-runtime-ktx:2.7.0'
-and every solution I found.enter image description here
class AuthenticationActivity : AppCompatActivity() {
private lateinit var binding: ActivityAuthenticationBinding
private val viewModel by viewModels<LoginViewModel>()
companion object {
const val TAG = "LoginFragment"
const val SIGN_IN_RESULT_CODE = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAuthenticationBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.authButton.setOnClickListener { launchSignInFlow() }
viewModel.authenticationState.observe(this) { authenticationState ->
when (authenticationState) {
LoginViewModel.AuthenticationState.AUTHENTICATED -> switchActivities()
else -> Log.e(
TAG,
"Authentication state that doesn't require any UI change $authenticationState"
)
}
}
}
private fun launchSignInFlow() {
val providers = arrayListOf(
EmailBuilder().build(), GoogleBuilder().build()
)
startActivityForResult(
AuthUI.getInstance().createSignInIntentBuilder().setAvailableProviders(providers)
.build(), AuthenticationActivity.SIGN_IN_RESULT_CODE
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SIGN_IN_RESULT_CODE) {
val response = IdpResponse.fromResultIntent(data)
if (resultCode == Activity.RESULT_OK) {
startActivity(Intent(this#AuthenticationActivity, MyApp::class.java))
finish()
return
} else {
if (response == null) {
Log.e("Login", "Login canceled by User")
return
}
if (response.error!!.errorCode == ErrorCodes.NO_NETWORK) {
Log.e("Login", "No Internet Connection")
return
}
if (response.error!!.errorCode == ErrorCodes.UNKNOWN_ERROR) {
Log.e("Login", "Unknown Error")
return
}
}
Log.e("Login", "Unknown sign in response")
}
}
private fun switchActivities() {
val switchActivityIntent = Intent(this, RemindersActivity::class.java)
startActivity(switchActivityIntent)
}
}
private fun sendNotification(triggeringGeofences: List<Geofence>) {
triggeringGeofences.forEach {
val requestId = it.requestId
val remindersRepository: ReminderDataSource by inject()
//Interaction to the repository has to be through a coroutine scope
CoroutineScope(coroutineContext).launch(SupervisorJob()) {
//get the reminder with the request id
val result = remindersRepository.getReminder(requestId)
if (result is Result.Success<ReminderDTO>) {
val reminderDTO = result.data
//send a notification to the user with the reminder details
sendNotification(
this#GeofenceTransitionsJobIntentService, ReminderDataItem(
reminderDTO.title,
reminderDTO.description,
reminderDTO.location,
reminderDTO.latitude,
reminderDTO.longitude,
reminderDTO.id
)
)
}
}
}
}
That was an issue on older versions of FirebaseUI for Android. It has been fixed in version 8.0.2. Please update the dependency in your build.gradle file:
implementation 'com.firebaseui:firebase-ui-auth:8.0.2'
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.
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.
I need to ask permission for contacts and when application starts I'm asking,in ViewModel part I need to call method which requires permission. I need to check permission is granted by user or not and then call, but for checking permission I need to have access Activity. while in my ViewModel I don't have a reference to Activity and don't want to have, How I can overcome, the problem?
I just ran into this problem, and I decided to use make use of LiveData instead.
Core concept:
ViewModel has a LiveData on what permission request needs to be made
ViewModel has a method (essentially callback) that returns if permission is granted or not
SomeViewModel.kt:
class SomeViewModel : ViewModel() {
val permissionRequest = MutableLiveData<String>()
fun onPermissionResult(permission: String, granted: Boolean) {
TODO("whatever you need to do")
}
}
FragmentOrActivity.kt
class FragmentOrActivity : FragmentOrActivity() {
private viewModel: SomeViewModel by lazy {
ViewModelProviders.of(this).get(SomeViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
......
viewModel.permissionRequest.observe(this, Observer { permission ->
TODO("ask for permission, and then call viewModel.onPermissionResult aftwewards")
})
......
}
}
I have reworked the solution. The PermissionRequester object is everything you need to request permissions from any point where you have at least an application context. It uses its helper PermissionRequestActivity to accomplish this job.
#Parcelize
class PermissionResult(val permission: String, val state: State) : Parcelable
enum class State { GRANTED, DENIED_TEMPORARILY, DENIED_PERMANENTLY }
typealias Cancellable = () -> Unit
private const val PERMISSIONS_ARGUMENT_KEY = "PERMISSIONS_ARGUMENT_KEY"
private const val REQUEST_CODE_ARGUMENT_KEY = "REQUEST_CODE_ARGUMENT_KEY"
object PermissionRequester {
private val callbackMap = ConcurrentHashMap<Int, (List<PermissionResult>) -> Unit>(1)
private var requestCode = 256
get() {
requestCode = field--
return if (field < 0) 255 else field
}
fun requestPermissions(context: Context, vararg permissions: String, callback: (List<PermissionResult>) -> Unit): Cancellable {
val intent = Intent(context, PermissionRequestActivity::class.java)
.putExtra(PERMISSIONS_ARGUMENT_KEY, permissions)
.putExtra(REQUEST_CODE_ARGUMENT_KEY, requestCode)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
callbackMap[requestCode] = callback
return { callbackMap.remove(requestCode) }
}
internal fun onPermissionResult(responses: List<PermissionResult>, requestCode: Int) {
callbackMap[requestCode]?.invoke(responses)
callbackMap.remove(requestCode)
}
}
class PermissionRequestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
requestPermissions()
}
}
private fun requestPermissions() {
val permissions = intent?.getStringArrayExtra(PERMISSIONS_ARGUMENT_KEY) ?: arrayOf()
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
when {
permissions.isNotEmpty() && requestCode != -1 -> ActivityCompat.requestPermissions(this, permissions, requestCode)
else -> finishWithResult()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionResults = grantResults.zip(permissions).map { (grantResult, permission) ->
val state = when {
grantResult == PackageManager.PERMISSION_GRANTED -> State.GRANTED
ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> State.DENIED_TEMPORARILY
else -> State.DENIED_PERMANENTLY
}
PermissionResult(permission, state)
}
finishWithResult(permissionResults)
}
private fun finishWithResult(permissionResult: List<PermissionResult> = listOf()) {
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
PermissionRequester.onPermissionResult(permissionResult, requestCode)
finish()
}
}
Usage:
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val cancelRequest: Cancellable = requestPermission()
private fun requestPermission(): Cancellable {
return PermissionRequester.requestPermissions(getApplication(), "android.permission.SEND_SMS") {
if (it.firstOrNull()?.state == State.GRANTED) {
Toast.makeText(getApplication(), "GRANTED", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(getApplication(), "DENIED", Toast.LENGTH_LONG).show()
}
}
}
override fun onCleared() {
super.onCleared()
cancelRequest()
}
}
I did something like this:
create an abstract class that extends AndroidViewModel which gives you access to the application context:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Now, create your view model by extending the BaseViewModel class and you will have access to the application context
class AdminViewModel(application: Application) : BaseViewModel(application) {
.....
}
Now you always have access to a Context that you can use to get access to resources.