How should be implemented requesting permission from Jetpack Compose View? I'm trying implement application accessing Camera with Jetpack Compose. I tried example from How to get Current state or context in Jetpack Compose Unfortunately example is no longer working with dev06.
fun hasPermissions(context: Context) = PERMISSIONS_REQUIRED.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
}
}
as compose_version = '1.0.0-beta04' and
implementation 'androidx.activity:activity-compose:1.3.0-alpha06'
you can do request permission as simple as this:
#Composable
fun ExampleScreen() {
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
// Permission Accepted: Do something
Log.d("ExampleScreen","PERMISSION GRANTED")
} else {
// Permission Denied: Do something
Log.d("ExampleScreen","PERMISSION DENIED")
}
}
val context = LocalContext.current
Button(
onClick = {
// Check permission
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.READ_EXTERNAL_STORAGE
) -> {
// Some works that require permission
Log.d("ExampleScreen","Code requires permission")
}
else -> {
// Asking for permission
launcher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
}
}
) {
Text(text = "Check and Request Permission")
}
}
Check out Google Accompanist's Jetpack Compose Permissions.
Bear in mind that, at the time of writing, the API is still considered experimental and will require the #ExperimentalPermissionsApi annotation when used.
Documentation and usage: https://google.github.io/accompanist/permissions/
Samples: https://github.com/google/accompanist/tree/main/sample/src/main/java/com/google/accompanist/sample/permissions
Google has a library called "Accompanist". It has many help libraries and one of them is the Permission Library.
Check:
Library: https://github.com/google/accompanist/
Documentation: https://google.github.io/accompanist/permissions/
Example:
Setup in build.gradle file:
repositories {
mavenCentral()
}
dependencies {
implementation "com.google.accompanist:accompanist-permissions:<latest_version>"
}
Implementation in Code
#Composable
private fun FeatureThatRequiresCameraPermission() {
// Camera permission state
val cameraPermissionState = rememberPermissionState(
android.Manifest.permission.CAMERA
)
when (cameraPermissionState.status) {
// If the camera permission is granted, then show screen with the feature enabled
PermissionStatus.Granted -> {
Text("Camera permission Granted")
}
is PermissionStatus.Denied -> {
Column {
val textToShow = if (cameraPermissionState.status.shouldShowRationale) {
// If the user has denied the permission but the rationale can be shown,
// then gently explain why the app requires this permission
"The camera is important for this app. Please grant the permission."
} else {
// If it's the first time the user lands on this feature, or the user
// doesn't want to be asked again for this permission, explain that the
// permission is required
"Camera permission required for this feature to be available. " +
"Please grant the permission"
}
Text(textToShow)
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Request permission")
}
}
}
}
}
/**
* Composable helper for permission checking
*
* onDenied contains lambda for request permission
*
* #param permission permission for request
* #param onGranted composable for [PackageManager.PERMISSION_GRANTED]
* #param onDenied composable for [PackageManager.PERMISSION_DENIED]
*/
#Composable
fun ComposablePermission(
permission: String,
onDenied: #Composable (requester: () -> Unit) -> Unit,
onGranted: #Composable () -> Unit
) {
val ctx = LocalContext.current
// check initial state of permission, it may be already granted
var grantState by remember {
mutableStateOf(
ContextCompat.checkSelfPermission(
ctx,
permission
) == PackageManager.PERMISSION_GRANTED
)
}
if (grantState) {
onGranted()
} else {
val launcher: ManagedActivityResultLauncher<String, Boolean> =
rememberLauncherForActivityResult(contract = ActivityResultContracts.RequestPermission()) {
grantState = it
}
onDenied { launcher.launch(permission) }
}
}
Don't forget to add
<uses-permission android:name="android.permission.CAMERA"/>
(when requesting camera permission) to your AndroidManifest.xml, otherwise it might end up with the permission denied state, when using the solutions others provided.
A little late but this might help as I had the problem today:
With ContextAmbient.current it is not guaranteed that you have an activity or fragment thus I created my own ambient for handling permissions.
val AmbientPermissionHandler: ProvidableAmbient<PermissionHandler> =
ambientOf { throw IllegalStateException("permission handler is not initialized") }
// Activity:
private val permissionHandler = PermissionHandler(this)
// onCreate:
setContent {
Providers(
AmbientPermissionHandler provides permissionHandler
) {/* Composable Contnent */}
Usage:
#Composable
fun PermissionHandler(
permissions: Array<out String>,
requestCode: Int,
granted: #Composable() () -> Unit,
denied: #Composable() () -> Unit,
deniedPermanently: (#Composable() () -> Unit)? = null,
rational: (#Composable() () -> Unit)? = null,
awaitResult: (#Composable() () -> Unit)? = null,
) {
val permissionHandler = AmbientPermissionHandler.current
val (permissionResult, setPermissionResult) = remember(permissions) { mutableStateOf<PermissionResult?>(null) }
LaunchedEffect(Unit) {
setPermissionResult(permissionHandler.requestPermissionsSuspend(requestCode, permissions))
}
when (permissionResult) {
is PermissionResult.PermissionGranted -> granted()
is PermissionResult.PermissionDenied -> denied()
is PermissionResult.PermissionDeniedPermanently -> deniedPermanently?.invoke()
is PermissionResult.ShowRational -> rational?.invoke()
null -> awaitResult?.invoke()
}
}
Implementation of PermissionHandler with dependency https://github.com/sagar-viradiya/eazypermissions
class PermissionHandler(
private val actualHandler: AppCompatActivity,
) {
suspend fun requestPermissionsSuspend(requestCode: Int, permissions: Array<out String>): PermissionResult {
return PermissionManager.requestPermissions(actualHandler, requestCode, *permissions)
}
fun requestPermissionsWithCallback(requestCode: Int, permissions: Array<out String>, onResult: (PermissionResult) -> Unit) {
actualHandler.lifecycleScope.launch {
onResult.invoke(PermissionManager.requestPermissions(actualHandler, requestCode, *permissions))
}
}
}
If you prefer a callback the second function works also.
private const val PERMISSIONS_REQUEST_CODE = 10
private val PERMISSIONS_REQUIRED = arrayOf(Manifest.permission.CAMERA)
#Composable
fun PermissionButton() {
val context = ContextAmbient.current
Button(onClick = {
if (!hasPermissions(context)) {
requestPermissions(
context as Activity,
PERMISSIONS_REQUIRED,
PERMISSIONS_REQUEST_CODE
)
}
}
) {}
}
fun hasPermissions(context: Context) = PERMISSIONS_REQUIRED.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
You can request multiples permissions.
class MainActivity : ComponentActivity() {
private val neededPermissions = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
#OptIn(ExperimentalMaterialApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MfmTheme {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestMultiplePermissions()
) { maps ->
val granted = maps.values.reduce { acc, next -> (acc && next) }
if (granted) {
// all permission granted
} else {
// Permission Denied: Do something
}
// You can check one by one
maps.forEach { entry ->
Log.i("Permission = ${entry.key}", "Enabled ${entry.value}")
}
}
val context = LocalContext.current
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background,
onClick = {
when {
hasPermissions(context, *neededPermissions) -> {
// All permissions granted
}
else -> {
// Request permissions
launcher.launch(neededPermissions)
}
}
}
) {
Greeting("Android")
}
}
}
}
private fun hasPermissions(context: Context, vararg permissions: String): Boolean =
permissions.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
#Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
MfmTheme {
Greeting("Android")
}
}
//define permission in composable fun
val getPermission = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
//permission accepted do somthing
} else {
//permission not accepted show message
}
}
//i used SideEffect to launch permission request when screen recomposed
//you can call it inside a button click without SideEffect
SideEffect {
getPermission.launch(Manifest.permission.READ_CONTACTS)
}
and if you wanted to request multiple permission use this:
ActivityResultContracts.RequestMultiplePermissions()
The rememberPermissionState(permission: String) API allows you to request a certain permission to the user and check for the status of the permission.
**Step1:**
A library which provides Android runtime permissions support for Jetpack Compose.
implementation 'com.google.accompanist:accompanist-permissions:0.24.13-rc'
..
**Step2:**
In our AndroidManifeastxml we need to declare permission (in this example we are going to request location permission)
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
..
**Step3:**
In our MainActivity.kt we are calling this permission request function
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Material3ComposeTheme {
RequestPermission(permission = Manifest.permission.ACCESS_FINE_LOCATION)
}
}
}
}
..
**Step4:**
In this SinglePermission.kt, we are going to request permission from user, if user already deny means we will show simple alert dialog info message otherwise will show custom full screen dialog.
package compose.material.theme
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import com.google.accompanist.permissions.*
#ExperimentalPermissionsApi
#Composable
fun RequestPermission(
permission: String,
rationaleMessage: String = "To use this app's functionalities, you need to give us the permission.",
) {
val permissionState = rememberPermissionState(permission)
HandleRequest(
permissionState = permissionState,
deniedContent = { shouldShowRationale ->
PermissionDeniedContent(
rationaleMessage = rationaleMessage,
shouldShowRationale = shouldShowRationale
) { permissionState.launchPermissionRequest() }
},
content = {
/* Content(
text = "PERMISSION GRANTED!",
showButton = false
) {}*/
}
)
}
#ExperimentalPermissionsApi
#Composable
fun HandleRequest(
permissionState: PermissionState,
deniedContent: #Composable (Boolean) -> Unit,
content: #Composable () -> Unit
) {
when (permissionState.status) {
is PermissionStatus.Granted -> {
content()
}
is PermissionStatus.Denied -> {
deniedContent(permissionState.status.shouldShowRationale)
}
}
}
#Composable
fun Content(showButton: Boolean = true, onClick: () -> Unit) {
if (showButton) {
val enableLocation = remember { mutableStateOf(true) }
if (enableLocation.value) {
CustomDialogLocation(
title = "Turn On Location Service",
desc = "Explore the world without getting lost and keep the track of your location.\n\nGive this app a permission to proceed. If it doesn't work, then you'll have to do it manually from the settings.",
enableLocation,
onClick
)
}
}
}
#ExperimentalPermissionsApi
#Composable
fun PermissionDeniedContent(
rationaleMessage: String,
shouldShowRationale: Boolean,
onRequestPermission: () -> Unit
) {
if (shouldShowRationale) {
AlertDialog(
onDismissRequest = {},
title = {
Text(
text = "Permission Request",
style = TextStyle(
fontSize = MaterialTheme.typography.headlineLarge.fontSize,
fontWeight = FontWeight.Bold
)
)
},
text = {
Text(rationaleMessage)
},
confirmButton = {
Button(onClick = onRequestPermission) {
Text("Give Permission")
}
}
)
}
else {
Content(onClick = onRequestPermission)
}
}
..
**Step 5:**
In this CustomDialogLocation.kt, we make custom dialog in android jetpack compose.
package compose.material.theme
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
/*
This example demonstrates how to make custom dialog in android jetpack compose in android.
* Button : https://www.boltuix.com/2021/12/button_25.html
* Clip Modifier : https://www.boltuix.com/2021/12/clip-modifier_24.html
* Alert Dialog : https://www.boltuix.com/2021/12/alert-dialog_25.html
* Column : https://www.boltuix.com/2021/12/column-layout_25.html
* Box : https://www.boltuix.com/2021/12/box-layout_25.html
* Type.kt : https://www.boltuix.com/2021/12/typography_27.html
* Color.kt : https://www.boltuix.com/2022/05/google-material-design-color.html
* Dialog : https://www.boltuix.com/2022/07/compose-custom-animating-dialog.html
* */
#Composable
fun CustomDialogLocation(
title: String? = "Message",
desc: String? = "Your Message",
enableLocation: MutableState<Boolean>,
onClick: () -> Unit
) {
Dialog(
onDismissRequest = { enableLocation.value = false}
) {
Box(
modifier = Modifier.padding(top = 20.dp, bottom = 20.dp)
// .width(300.dp)
// .height(164.dp)
.background(
color = MaterialTheme.colorScheme.onPrimary,
shape = RoundedCornerShape(25.dp,5.dp,25.dp,5.dp)
)
.verticalScroll(rememberScrollState())
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
//.........................Image: preview
Image(
painter = painterResource(id = R.drawable.permission_location),
contentDescription = null,
contentScale = ContentScale.Fit,
/* colorFilter = ColorFilter.tint(
color = MaterialTheme.colorScheme.primary
),*/
modifier = Modifier
.padding(top = 5.dp)
.height(320.dp)
.fillMaxWidth(),
)
//.........................Spacer
//.........................Text: title
Text(
text = title!!,
textAlign = TextAlign.Center,
modifier = Modifier
// .padding(top = 5.dp)
.fillMaxWidth(),
letterSpacing = 2.sp,
fontWeight = FontWeight.Bold,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.height(8.dp))
//.........................Text : description
Text(
text = desc!!,
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 10.dp, start = 25.dp, end = 25.dp)
.fillMaxWidth(),
letterSpacing = 1.sp,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
)
//.........................Spacer
Spacer(modifier = Modifier.height(24.dp))
//.........................Button : OK button
val cornerRadius = 16.dp
val gradientColors = listOf(Color(0xFFff669f), Color(0xFFff8961))
val roundedCornerShape = RoundedCornerShape(topStart = 30.dp,bottomEnd = 30.dp)
Button(
modifier = Modifier
.fillMaxWidth()
.padding(start = 32.dp, end = 32.dp),
onClick=onClick,
contentPadding = PaddingValues(),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Transparent
),
shape = RoundedCornerShape(cornerRadius)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.background(
brush = Brush.horizontalGradient(colors = gradientColors),
shape = roundedCornerShape
)
.padding(horizontal = 16.dp, vertical = 8.dp),
contentAlignment = Alignment.Center
) {
Text(
text ="Enable",
fontSize = 20.sp,
color = Color.White
)
}
}
//.........................Spacer
Spacer(modifier = Modifier.height(12.dp))
TextButton(onClick = {
enableLocation.value = false
}) { Text("Cancel", style = MaterialTheme.typography.labelLarge) }
Spacer(modifier = Modifier.height(24.dp))
}
}
}
}
Get source code & video: https://www.boltuix.com/2022/07/requesting-location-permission-in.html
There are two ways to get runtime permissions in jetpack compose.
Using activity result
Using the accompanist permissions library
Runtime permission using activity result
The first step is to define the permission in the manifest.xml file.
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera"/>
Create an activity result launcher to request the permission we defined. Once it’s launched it will return the result whether the permission is granted or not.
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) {
if (it) {
Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
cameraLauncher.launch(uri)
} else {
Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
}
}
Checking Permission
Before launching the request for permission, we need to check whether the permission is granted or not. If it’s already granted we can proceed with our regular flow. If permission is not provided, then we need to launch the permission request with the permission we wanted.
val permissionCheckResult = ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA)
if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
cameraLauncher.launch(uri)
} else {
permissionLauncher.launch(android.Manifest.permission.CAMERA)
}
finally, the code for the runtime permission using the activity result will be like the below,
val context = LocalContext.current
val file = context.createImageFile()
val uri = FileProvider.getUriForFile(
Objects.requireNonNull(context),
BuildConfig.APPLICATION_ID + ".provider", file
)
var capturedImageUri by remember {
mutableStateOf<Uri>(Uri.EMPTY)
}
val cameraLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
capturedImageUri = uri
}
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) {
if (it) {
Toast.makeText(context, "Permission Granted", Toast.LENGTH_SHORT).show()
cameraLauncher.launch(uri)
} else {
Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show()
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(12.dp)) {
Button(onClick = {
val permissionCheckResult = ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA)
if (permissionCheckResult == PackageManager.PERMISSION_GRANTED) {
cameraLauncher.launch(uri)
} else {
// Request a permission
permissionLauncher.launch(android.Manifest.permission.CAMERA)
}
}) {
Text(text = "Open Camera")
}
if (capturedImageUri.path?.isNotEmpty() == true) {
Image(
modifier = Modifier
.padding(16.dp, 8.dp)
.fillMaxWidth()
.size(400.dp),
painter = rememberImagePainter(capturedImageUri),
contentDescription = null
)
}
}
the output of the above code,
Related
I am working on trying to integrate Google Sign in API with my application. It is part of a university project. I followed the following website: https://proandroiddev.com/google-signin-compose-a9afa67b7519
But, when I try, it simply does not work. I have setup everything even in the Google Cloud APIs & Services, OAuth 2.0 Client IDs.
#HiltViewModel
class UserSessionViewModel #Inject constructor(
application: Application,
) : ViewModel() {
private val _user: MutableStateFlow<User?> = MutableStateFlow(null)
val user: StateFlow<User?> = _user
companion object {}
init {
verifySignedInUser(application.applicationContext)
}
fun signIn(email: String, name: String){
viewModelScope.launch {
_user.value = User(
email = email,
displayName = name
)
}
}
fun signOut(){}
private fun verifySignedInUser(applicationContext: Context){
val gsa = GoogleSignIn.getLastSignedInAccount(applicationContext)
if(gsa != null){
_user.value = User(
email = gsa.email.toString(),
displayName = gsa.givenName.toString()
)
Log.d("User", _user.value.toString())
}
}
}
class SignInGoogleViewModelFactory(
private val application: Application
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserSessionViewModel::class.java)) {
return UserSessionViewModel(application) as T
}
throw IllegalArgumentException("Unknown view-model class")
}
}
I have the following in JSON:
{"web":
{
"client_id":"",
"project_id":"",
"auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs",
"client_secret":""
}
}
EDIT:
It isn't clear, how I am supposed to use this from my Android Application. If, anyone has any suggestions please share. The tutorial doesn't mention anything about this, more precisely.
Login Screen:
#SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun LoginScreen(
rootNavHostController: NavHostController? = null) {
val coroutine = rememberCoroutineScope()
val systemUiController = rememberSystemUiController()
val context = LocalContext.current
val signInViewModel : UserSessionViewModel = viewModel(
factory = SignInGoogleViewModelFactory(context.applicationContext as Application)
)
(context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
val state = signInViewModel.user.collectAsState()
val user = state.value
val isError = rememberSaveable { mutableStateOf(false) }
SideEffect {
systemUiController.setStatusBarColor(
color = Color.Transparent,
darkIcons = false
)
systemUiController.setNavigationBarColor(
color = Color.Transparent,
darkIcons = false,
navigationBarContrastEnforced = false
)
}
val authResult = rememberLauncherForActivityResult(contract = GoogleApiContract()) { task ->
try {
val gsa = task?.getResult(ApiException::class.java)
if(gsa != null){
coroutine.launch {
signInViewModel.signIn(gsa.email.toString(), gsa.givenName.toString())
}
} else {
isError.value = true
}
} catch(e: ApiException){ Log.d("App", e.toString()) }
}
Scaffold(modifier = Modifier.fillMaxSize(),
content = {
Content(
onClick = { authResult.launch(1) },
isError = isError.value,
signInViewModel
)
}
)
user?.let { rootNavHostController?.navigate("Home") }
}
#Composable
fun Content(
onClick: () -> Unit,
isError: Boolean = false,
_signInViewModel: UserSessionViewModel
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.BottomCenter)
{
Image(
painter = painterResource(R.drawable.background),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillBounds
)
Box(modifier = Modifier
.fillMaxSize()
.padding(top = 70.dp, bottom = 0.dp),
contentAlignment = Alignment.TopCenter,
content = {
Column(modifier = Modifier
.fillMaxWidth()
.padding(0.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
content = {
Image(
painter = painterResource(R.drawable.app_logo),
contentDescription = null,
modifier = Modifier.padding(0.dp),
contentScale = ContentScale.None
)
Text(
text = stringResource(R.string.app_description),
modifier = Modifier.padding(16.dp),
color = Color.White,
fontSize = 14.sp,
fontStyle = FontStyle.Italic,
lineHeight = 22.sp
)
}
)
}
)
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.75f)) {
Row(
content = {
ElevatedButton(
onClick = { onClick() },
content = {
Text("Sign in with Google", color = Color.Black)
}
)
}
)
Row(content = {
Info(
text = stringResource(R.string.info),
)
})
}
}
}
GoogleApiContract:
class GoogleApiContract : ActivityResultContract<Int, Task<GoogleSignInAccount>?>(){
override fun createIntent(context: Context, input: Int): Intent {
val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken("")
.requestEmail()
.requestProfile()
.build()
val intent = GoogleSignIn.getClient(context, signInOptions)
return intent.signInIntent
}
override fun parseResult(resultCode: Int, intent: Intent?): Task<GoogleSignInAccount>? {
return when (resultCode) {
Activity.RESULT_OK -> {
GoogleSignIn.getSignedInAccountFromIntent(intent)
}
else -> null
}
}
}
EDIT: I think, the issue is here. But, I have no idea how to use the client_id and the client_secret it isn't clear from the tutorial nor the documentation.
Removing .requestIdToken("") completely resolved the issue. Wasn't required to invoke this method. Strangely.
Note: this .requestIdToken("") line, even with correct signature, it still didn't work.
I have some suggestions after reading your question and comments i think it will clear your issue if you use requireActivity() as context in your code other than application context and if still its not working then try to create new credentials with Web client (Auto-created for Google Sign-in) and use that in your code i think iam right if not please reply
I'm doing an application with Jetpack Compose and Kotlin. It is an app to locate an android device. I need to implement run time permissions to follow the jetpack filosofy.
I have a menu page where there is a switch that when is activate saves the location of the device but just activate the switch it is necessary to request permissions "fine_location", "coarse_location" and "back_groundlocation". This is my menu.kt code:
LazyColumn {
item {
Row {
Box(
modifier =
Modifier.fillMaxWidth(0.8f)
)
{
Text(
color = Color.Black,
text = stringResource(R.string.location_gps),
fontSize = 30.sp,
modifier = Modifier.padding(20.dp)
)
}
Box(
modifier =
Modifier.fillMaxSize(),
contentAlignment = Alignment.CenterEnd
) {
Switch(
checked = checkedStateGps.value,
onCheckedChange = { checkedStateGps.value = it },
modifier = Modifier
.padding(20.dp),
colors= SwitchDefaults.colors(
//color of switches
checkedThumbColor = Color(0xFF00CC99),
checkedTrackColor = Color(0xFF7BB661),
uncheckedThumbColor = Color(0xFF83010B),
uncheckedTrackColor = Color(0xFFBB4C4C)
)
)
}
}
I'd want to know how can I implement accompanist permissions for this.
In Compose you can use Google's Accompanist library to request permission at runtime, just with PermissionRequired.
This is an example with camera permission but you can request any permissions you have in your manifest file as android.Manifest.permission.*
var doNotShowRationale by rememberSaveable { mutableStateOf(false) }
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
PermissionRequired(
permissionState = cameraPermissionState,
permissionNotGrantedContent = {
if (doNotShowRationale) {
Text("Feature not available")
} else {
Column {
Text("The camera is important for this app. Please grant the permission.")
Spacer(modifier = Modifier.height(8.dp))
Row {
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Ok!")
}
Spacer(Modifier.width(8.dp))
Button(onClick = { doNotShowRationale = true }) {
Text("Nope")
}
}
}
}
},
permissionNotAvailableContent = {
Column {
Text(
"Camera permission denied. See this FAQ with information about why we " +
"need this permission. Please, grant us access on the Settings screen."
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = navigateToSettingsScreen) {
Text("Open Settings")
}
}
}
) {
Text("Camera permission Granted")
}
Request camera permission sample:
implementation "com.google.accompanist:accompanist-permissions:0.20.0"
The permission APIs are currently experimental and they could change
at any time. All of the APIs are marked with the
#ExperimentalPermissionsApi annotation.
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionRequired
import com.google.accompanist.permissions.rememberPermissionState
import pe.edu.upc.permissionscompose.ui.theme.PermissionsComposeTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PermissionsComposeTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
FeatureThatRequiresCameraPermission()
}
}
}
}
}
#OptIn(ExperimentalPermissionsApi::class)
#Composable
fun FeatureThatRequiresCameraPermission() {
var doNotShowRationale by rememberSaveable {
mutableStateOf(false)
}
val cameraPermissionState =
rememberPermissionState(permission = android.Manifest.permission.CAMERA)
val context = LocalContext.current
PermissionRequired(
permissionState = cameraPermissionState,
permissionNotGrantedContent = {
if (doNotShowRationale) {
Text("Feature not available")
} else {
PermissionNotGrantedUI(
onYesClick = {
cameraPermissionState.launchPermissionRequest()
}, onCancelClick = {
doNotShowRationale = true
})
}
},
permissionNotAvailableContent = {
PermissionNotAvailableContent(
onOpenSettingsClick = { context.openSettings() })
},
content = {
Text("Camera Permission Granted")
}
)
}
#Composable
fun PermissionNotAvailableContent(onOpenSettingsClick: () -> Unit) {
Column {
Text("Camera permission denied.")
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { onOpenSettingsClick() }) {
Text("Open settings")
}
}
}
#Composable
fun PermissionNotGrantedUI(onYesClick: () -> Unit, onCancelClick: () -> Unit) {
Column {
Text("Camera is important for this app. Please grant ther permission.")
Spacer(modifier = Modifier.height(8.dp))
Row {
Button(onClick = {
onYesClick()
}) {
Text("Yes")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
onCancelClick()
}) {
Text("Cancel")
}
}
}
}
#Composable
fun CategorySection(context: Context) {
Column(modifier = Modifier.padding(8.dp)) {
Text(
text = "Api Data",
style = MaterialTheme.typography.h5,
modifier = Modifier.padding(vertical = 7.dp, horizontal = 8.dp)
)
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch(Dispatchers.Main) {
val response = ApiPhoto.apiService.getPhotos()
if (response.isSuccessful && response.body() != null) {
val photos = response.body()
photos?.let {
CategoryItemSection(it)
}
} else {
Toast.makeText(
context,
"Error Occurred: ${response.message()}",
Toast.LENGTH_LONG
).show()
}
}
}
}
#Composable
fun CategoryItemSection(photos: List<Photo>) {
LazyColumn(
modifier = Modifier.fillMaxWidth()
){
items(photos.size){
CardView(photos[it])
}
}
}
You cannot call CategoryItemSection from inside a coroutine. Your coding practice here is totally wrong. You shouldn't be making API calls to your data sources from within your composables. Use a viewmodel and state hoisting:
Read up on state hoisting and uni-directional data flow:
https://developer.android.com/jetpack/compose/state
I want to call "onLogin" function and pass user but I can't access "onLogin" in ViewModel , I tried to use mutableLiveData but I couldn't,I don't know should I pass onLogin to viewmodel or this is a bad practice
there is button whose title is "Sign In" , it calls method in ViewModel called "Submit" use apollo (graphql) to get the user
SignInScreen
#Composable
fun SignInScreen(
onNavigateToSignUp:() -> Unit,
onLogin:(User) -> Unit
){
val viewModel:SignInViewModel = viewModel()
Scaffold(
bottomBar = {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(bottom = 10.dp)
.fillMaxWidth()
) {
Text(text = "Don't have an account?")
Text(
text = "Sign Up.",
modifier = Modifier
.padding(start = 5.dp)
.clickable { onNavigateToSignUp() },
fontWeight = FontWeight.Bold
)
}
}
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(it),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Instagram")
Spacer(modifier = Modifier.size(30.dp))
Input(viewModel.username,placeholder = "username"){
viewModel.username = it
}
Spacer(modifier = Modifier.size(20.dp))
Input(viewModel.password,placeholder = "Password"){
viewModel.password = it
}
Spacer(modifier = Modifier.size(30.dp))
Button(onClick = {viewModel.submit()},modifier = Modifier.fillMaxWidth()) {
Text(text = "Sign In")
}
}
}
}
ViewModel
class SignInViewModel(application:Application):AndroidViewModel(application) {
var username by mutableStateOf("")
var password by mutableStateOf("")
private val context = application.applicationContext
private val _user = MutableLiveData<User>(null)
val user:LiveData<User> get() = _user
fun submit(){
viewModelScope.launch {
val response = apolloClient.mutate(LoginMutation(username = Input.fromNullable(username),password = Input.fromNullable(password))).await()
_user.value = response.data?.login?.user as User
}
}
}
This is how I did it.
1. First I created this class to communicate from ViewModel to view(s) and to have stateful communication where the UI knows what to show with every update and through one live data.
sealed class UIState<out T>() {
class Idle() : UIState<Nothing>()
class Loading(val progress: Int = 0) : UIState<Nothing>()
class Success<out T>(val data: T?) : UIState<T>()
class Error(
val error: Throwable? = null,
val message: String? = null,
val title: String? = null
) : UIState<Nothing>()
}
2. Then Of course create the live data in ViewModel and also an immutable copy for the view:
private val _loginState by lazy { MutableLiveData<UIState<ResponseUser>>() }
val loginState: LiveData<UIState<ResponseUser>> = _loginState
fun performLogin(username: String, password: String) {
viewModelScope.launch {
_loginState.postValue(loading)
// your login logic here
if ("login was successful") {
_loginState.postValue(UIState.Success("your login response if needed in UI"))
} else {
_loginState.postValue(UIState.Error("some error here"))
}
}
}
3. Now in the UI I need to observe this live data as a state, which is pretty easy we have delegate literally called observeAsState. But here is the catch and that's if you are doing something like navigation, which you only want to happen only once:
#Composable
fun LoginScreen(viewModel: LoginViewModel) {
val loginState by viewModel.loginState.observeAsState(UIState.Idle())
val hasHandledNavigation = remember { mutableStateOf(false)}
if (loginState is UIState.Success && !hasHandledNavigation.value ) {
navigateToWelcomeScreen()
else {
LoginScreenUI(loginState) { username, password ->
viewModel.performLogin(username, password)
}
}
}
4. in the UI you want, among other things, two text fields and a button, and you want to remember the username and password that entered:
#Composable
fun LoginScreenUI(
state: UIState<ResponseUser>, onLoginButtonClicked: (username: String, password: String) -> Unit
) {
Column() {
var username by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = username,
onValueChange = { username = it },
)
var password by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = password,
onValueChange = { password = it },
)
Button(
onClick = {
onLoginButtonClicked(
username, password
)
}
) {
Text(text = "Login")
}
if (state is UIState.Error) {
AlertDialogComponent(state.title, state.message)
}
}
}
I hope I've covered everything :D
My solution is to use the LaunchedEffect because the Android developer documentation is mentioning showing SnackBar as an example which is a single time event, code example following the same as Amin Keshavarzian Answer
just change the part 3 to use LaunchedEffect instead of the flag state hasHandledNavigation
#Composable
fun LoginScreen(viewModel: LoginViewModel) {
val loginState by viewModel.loginState.observeAsState(UIState.Idle())
LaunchedEffect(key1 = loginState) {
if (loginState is UIState.Success)
navigateToWelcomeScreen()
}
LoginScreenUI(loginState) { username, password ->
viewModel.performLogin(username, password)
}
}
I want to get a camera view by compose androidView, but the following code seems not work for me.
#Composable
fun CameraPreviewScreen() {
val lifecycleOwner = LifecycleOwnerAmbient.current
val context = ContextAmbient.current
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
val previewView = remember { PreviewView(context) }
AndroidView(viewBlock = { previewView }) {
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
val preview = Preview.Builder().build()
preview.setSurfaceProvider(it.surfaceProvider)
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview)
} catch(exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(context))
}
}
The snapshot below.
The report error.
Permission Denial: can't use the camera
cannot open camera "0" without camera permission (code 1)
Since your problem seems related to camera permission, you can use Permissions APIs from Accompanist library to ask the user for permissions.
The docs show an example with camera
#Composable
private fun FeatureThatRequiresCameraPermission(
navigateToSettingsScreen: () -> Unit
) {
// Track if the user doesn't want to see the rationale any more.
var doNotShowRationale by rememberSaveable { mutableStateOf(false) }
val cameraPermissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
PermissionRequired(
permissionState = cameraPermissionState,
permissionNotGrantedContent = {
if (doNotShowRationale) {
Text("Feature not available")
} else {
Column {
Text("The camera is important for this app. Please grant the permission.")
Spacer(modifier = Modifier.height(8.dp))
Row {
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
Text("Ok!")
}
Spacer(Modifier.width(8.dp))
Button(onClick = { doNotShowRationale = true }) {
Text("Nope")
}
}
}
}
},
permissionNotAvailableContent = {
Column {
Text(
"Camera permission denied. See this FAQ with information about why we " +
"need this permission. Please, grant us access on the Settings screen."
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = navigateToSettingsScreen) {
Text("Open Settings")
}
}
}
) {
Text("Camera permission Granted")
}
}