I am trying to set up the navigation back button following this SO thread, but can't make it work.
#ExperimentalFoundationApi
#Composable
fun LazyVerticalGridActivityScreen() {
val navController = rememberNavController()
val navigationIcon: (#Composable () -> Unit)? =
if (navController.previousBackStackEntry != null) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
}
} else {
null
}
Scaffold(
topBar = {
TopAppBar(title = { Text("Lazy Vertical Grid") }, navigationIcon = navigationIcon)
},
content = {
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details/{listId}") { backStackEntry ->
backStackEntry.arguments?.getString("listId")
?.let { DetailsScreen(it, navController) }
}
}
}
)
}
Can anyone please help to fix this? Thanks!
Changing navController state doesn't make it container recompose, that's why this navigationIcon stays null.
To make it recompose you need to use addOnDestinationChangedListener:
var canPop by remember { mutableStateOf(false) }
DisposableEffect(navController) {
val listener = NavController.OnDestinationChangedListener { controller, _, _ ->
canPop = controller.previousBackStackEntry != null
}
navController.addOnDestinationChangedListener(listener)
onDispose {
navController.removeOnDestinationChangedListener(listener)
}
}
val navigationIcon: (#Composable () -> Unit)? =
if (canPop) {
{
IconButton(onClick = { navController.popBackStack() }) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = null)
}
}
} else {
null
}
...
Related
What to do to manage one state in two functions? (openDialog)
How to transfer openDialog state from ProductDialog() to AppBar()?
I know I can make a separate class per state but I don't want to.
#Composable
fun ProductDialog() {
val openDialog = rememberSaveable() { mutableStateOf(true) }
val productNameState = remember { mutableStateOf("") }
AlertDialog(
onDismissRequest = { openDialog.value = false },
title = { Text(
modifier = Modifier.padding(bottom = 8.dp),
text = "Add new product",
fontSize = 20.sp,
fontWeight = FontWeight.Bold
) },
confirmButton = { Button(onClick = { openDialog.value = false }) {
Text(text = "Add") }
},
dismissButton = { Button(onClick = { openDialog.value = false }) {
Text(text = "Cancel")
}
},
text = {
Column() {
TextField(value = productNameState.value, onValueChange = {
})
TextField(value = productNameState.value, onValueChange = {})
}
}
)
}
#Composable
fun AppBar() {
val openDialog = rememberSaveable() { mutableStateOf(false) }
if (openDialog.value) ProductDialog()
Scaffold(
topBar = {
TopAppBar(
actions = { Icon(imageVector = Icons.Default.Menu, contentDescription = null) },
title = { Text(text = "Shopping List Compose") })
},
floatingActionButton = {
FloatingActionButton(modifier = Modifier.padding(10.dp), onClick = { openDialog.value = true }) {
Icon(Icons.Default.Add, contentDescription = null)
}}
) {
ListItems()
}
}
What to do to manage one state in two functions? (openDialog)
From the example you shared, it seems that the only use of openDialog in ProductDialog is to close the dialog. You can instead paas a lambda to ProductDialog to close the dialog.
#Composable
fun ProductDialog(closeDialog: () -> Unit) {
AlertDialog(
onDismissRequest = closeDialog,
title = { ... },
confirmButton = {
Button(onClick = closeDialog) {
Text(text = "Add")
}
},
dismissButton = {
Button(onClick = closeDialog) {
Text(text = "Cancel")
}
},
text = { ... }
)
}
#Composable
fun AppBar() {
val openDialog by rememberSaveable() { mutableStateOf(false) }
if (openDialog) ProductDialog { openDialog = false}
...
}
I'm learning Kotlin and Compose Desktop and I'm trying refresh the UI before fetch data from an API.
But the request is running inside a runBlocking, thus the UI freezes until request is completed.
This is my code, everything works.
val client = HttpClient(CIO)
#OptIn(ExperimentalComposeUiApi::class)
#Composable
#Preview
fun App() {
var text by remember { mutableStateOf("Test button") }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(50.dp)
) {
Button(
onClick = {
text = "Wait..."//How to refresh UI to display this text?
runBlocking {
delay(5000)//blocking test
val response: HttpResponse = client.request("https://myapi.com/") {
// Configure request parameters exposed by HttpRequestBuilder
}
if (response.status == HttpStatusCode.OK) {
val body = response.body<String>()
println(body)
} else {
println("Error has occurred")
}
}
text = "Test button"
},
modifier = Modifier.fillMaxWidth()
) {
Text(text)
}
OutlinedTextField(
value = "",
singleLine = true,
onValueChange = { text = it }
)
}
}
}
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
state = WindowState(size = DpSize(350.dp, 500.dp)),
title = "Compose test"
) {
App()
}
}
How to achieve that?
The problem here is that you are using runBlocking at all.
The most straightforward solution for your case would be to replace your runBlocking {} with a coroutine scope. At the top of your App() function, create your scope: val scope = rememberCoroutineScope(), then instead of runBlocking you can say scope.launch {}.
New code would be:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
#Preview
fun App() {
var text by remember { mutableStateOf("Test button") }
val scope = rememberCoroutineScope()
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(50.dp)
) {
Button(
onClick = {
text = "Wait..."//How to refresh UI to display this text?
scope.launch {
delay(5000)//blocking test
val response: HttpResponse = client.request("https://myapi.com/") {
// Configure request parameters exposed by HttpRequestBuilder
}
if (response.status == HttpStatusCode.OK) {
val body = response.body<String>()
println(body)
} else {
println("Error has occurred")
}
}
text = "Test button"
},
modifier = Modifier.fillMaxWidth()
) {
Text(text)
}
OutlinedTextField(
value = "",
singleLine = true,
onValueChange = { text = it }
)
}
}
}
I saw one comment say to use LaunchedEffect() but this won't work in your case since you can't use that in an onClick since it's not a Composable.
I am building a video call app . I have implemented refering to agora official sample repsoitoryRefer.
In my implementation call s getting connected , but the video of remote user is rendered for 2 seconds and it gets stuck . Is any thing wrong in my implemengtations?
This is my whole composable screen code
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun VideoCallScreenV2(
viewModel: CallViewModel,
navController: NavHostController,
onBack: () -> Unit = {}
) {
val context = LocalContext.current
val localSurfaceView: TextureView? by remember {
mutableStateOf(RtcEngine.CreateTextureView(context))
}
var remoteUserMap by remember {
mutableStateOf(mapOf<Int, TextureView?>())
}
var remoteUser = remember {
mutableStateOf<RemoteUser?>(null)
}
val permissionNotEnabled = remember {
mutableStateOf(!viewModel.hasPermissions)
}
var isInPictureInPicMode by remember {
mutableStateOf(false)
}
val screenState = remember {
viewModel.callScreenState
}
var networkQuality by remember {
mutableStateOf(-1)
}
PermissionHandler(visible = permissionNotEnabled,
permissions = viewModel.getRequiredPermissionList(),
permissionText = stringResource(
id = R.string.camera_audio_permission_text
),
permissionNotAvailable = {
context.showToast(context.getString(R.string.agora_video_perms_denied))
//If permissions are not given, video calls should not take place. Then navigate to chat screen
// navController.popBackStack(Routes.INBOX_V2, true)
}) {
permissionNotEnabled.value = false
viewModel.hasPermissions = true
}
val mEngine = remember(viewModel.callScreenState.rtcToken) {
initEngine(
context,
getRtcEngineEventHandler(
onUserJoined = { uid, elapsed ->
remoteUser.value = RemoteUser(uid)
},
onUserOffline = { uid, reason ->
remoteUser.value = null
context.findActivity()?.finish()
},
onNetworkQuality = { uid, txQuality, rxQuality ->
networkQuality = rxQuality
},
onLeaveChannel = {
Timber.e("hhp-CallScreen onLeaveChannel Success")
context.findActivity()?.finish()
}
),
viewModel.callScreenState.chatRoom?.channelName ?: "",
viewModel.userRole,
token = viewModel.callScreenState.rtcToken?.token ?: ""
)
}
if (viewModel.userRole == "Broadcaster") {
mEngine.setupLocalVideo(VideoCanvas(localSurfaceView, Constants.RENDER_MODE_FIT, 0))
}
var isRemoteFullScreen by remember {
mutableStateOf(true)
}
val remoteViewZindex by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1.0f else 1.5f)
val localViewZindex by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1.5f else 1.0f)
val remoteViewSize by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1f else .25f)
val localViewSize by animateFloatAsState(targetValue = if (isRemoteFullScreen) .35f else 1f)
var showControls by remember {
mutableStateOf(true)
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) {
RemoteView(
remoteListInfo = remoteUserMap,
remoteUser = remoteUser.value,
mEngine = mEngine,
modifier = Modifier
.padding(
end = if (!isRemoteFullScreen) 24.dp else 0.dp,
bottom = if (!isRemoteFullScreen) 54.dp else 0.dp
)
.fillMaxSize(remoteViewSize)
.zIndex(remoteViewZindex)
//.background(green)
.clickable {
if (!isRemoteFullScreen)
isRemoteFullScreen = true
else if (isRemoteFullScreen)
showControls = !showControls
}
)
localSurfaceView?.let { local ->
AndroidView(
factory = {
local
},
update = { it ->
it.setOnClickListener {
if (isRemoteFullScreen)
isRemoteFullScreen = false
else if (!isRemoteFullScreen)
showControls = !showControls
}
},
modifier = Modifier
.fillMaxSize(localViewSize)
.zIndex(localViewZindex)
.padding(
end = if (isRemoteFullScreen) 24.dp else 0.dp,
bottom = if (isRemoteFullScreen) 54.dp else 0.dp
)
)
NetworkQualityIndicator(quality = networkQuality)
}
AnimatedVisibility(visible = showControls, modifier = Modifier.zIndex(3f)) {
UserControls(
isMuted = viewModel.muted,
isVideoDisabled = viewModel.videoDisabled,
onEndCallClicked = {
viewModel.apply {
whatIf(
given = isUsingByDoctor(),
whatIf = {
onEvent(CallEvents.DoctorEndedCall(mEngine))
}, whatIfNot = {
onEvent(CallEvents.PatientEndedCall(mEngine))
}
)
}
}, onToggleMute = {
viewModel.muted = !viewModel.muted
mEngine.muteLocalAudioStream(viewModel.muted)
}, onToggleVideo = {
})
}
}
}
fun initEngine(
current: Context,
eventHandler: IRtcEngineEventHandler,
channelName: String,
userRole: String,
token: String
): RtcEngine =
RtcEngine.create(current, BuildConfig.AGORA_APPID, eventHandler).apply {
enableVideo()
setChannelProfile(1)
if (userRole == "Broadcaster") {
setClientRole(1)
} else {
setClientRole(0)
}
setDefaultAudioRoutetoSpeakerphone(true)
setVideoEncoderConfiguration(
VideoEncoderConfiguration(
VideoEncoderConfiguration.VideoDimensions(640, 480),
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
0,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
)
if (token.isNotEmpty() && channelName.isNotEmpty()){
joinChannel(token, channelName, "", 0)
Timber.e("hhp-CallScreen initEngine token $token channel name $channelName userRole $userRole")
}
}
#Composable
private fun RemoteView(
remoteListInfo: Map<Int, TextureView?>,
remoteUser: RemoteUser?,
mEngine: RtcEngine,
modifier: Modifier
) {
val context = LocalContext.current
Row(
modifier = modifier
) {
if (remoteUser != null) {
Timber.e("hhp-CallScreen Remote User $remoteUser")
val remoteTextureView = remoteUser.textureView ?: RtcEngine.CreateTextureView(context)
AndroidView(
factory = { remoteTextureView!! }
)
mEngine.setupRemoteVideo(
VideoCanvas(
remoteTextureView,
Constants.RENDER_MODE_HIDDEN,
remoteUser.uid
)
)
} else {
Timber.e("hhp-CallScreen Remote User Null")
}
}
}
#Composable
private fun UserControls(
isMuted: Boolean,
isVideoDisabled: Boolean,
onEndCallClicked: () -> Unit,
onToggleMute: (Boolean) -> Unit,
onToggleVideo: (Boolean) -> Unit
) {
val activity = (LocalContext.current as? Activity)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 50.dp),
Arrangement.SpaceEvenly,
Alignment.Bottom
) {
OutlinedButton(
onClick = {
onToggleMute(isMuted)
},
shape = CircleShape,
modifier = Modifier.size(50.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = if (isMuted) Color.Blue else Color.White)
) {
if (isMuted) {
Icon(
Icons.Rounded.MicOff,
contentDescription = "Tap to unmute mic",
tint = Color.White
)
} else {
Icon(Icons.Rounded.Mic, contentDescription = "Tap to mute mic", tint = Color.Blue)
}
}
OutlinedButton(
onClick = {
//mEngine.leaveChannel()
// activity?.finish()
onEndCallClicked()
},
shape = CircleShape,
modifier = Modifier.size(70.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = Color.Red)
) {
Icon(
Icons.Rounded.CallEnd,
contentDescription = "Tap to disconnect Call",
tint = Color.White
)
}
OutlinedButton(
onClick = {
//videoDisabled = !videoDisabled
//mEngine.muteLocalVideoStream(videoDisabled)
onToggleVideo(isVideoDisabled)
},
shape = CircleShape,
modifier = Modifier.size(50.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = if (isVideoDisabled) Color.Blue else Color.White)
) {
if (isVideoDisabled) {
Icon(
Icons.Rounded.VideocamOff,
contentDescription = "Tap to enable Video",
tint = Color.White
)
} else {
Icon(
Icons.Rounded.Videocam,
contentDescription = "Tap to disable Video",
tint = Color.Blue
)
}
}
}
}
#Composable
fun NetworkQualityIndicator(quality: Int) {
if (BuildConfig.DEBUG) {
when (updateNetworkStatus(quality)) {
CallQuality.Excellent -> QualityLabel(label = "Excellent", color = green)
CallQuality.Good -> QualityLabel(label = "Good", color = cream)
CallQuality.Poor -> QualityLabel(label = "Poor", color = lightRed)
CallQuality.Unknown -> QualityLabel(label = "Analysing", color = white)
}
}
}
#Composable
fun QualityLabel(
label: String,
color: Color
) {
Text(
text = "Call Quality $label", modifier = Modifier
.fillMaxWidth()
.zIndex(3f)
.background(
color
),
textAlign = TextAlign.Center
)
}
private fun updateNetworkStatus(quality: Int): CallQuality {
return if (quality in 1..2)
CallQuality.Excellent
else if (quality <= 4) CallQuality.Good
else if (quality <= 6) CallQuality.Poor
else CallQuality.Unknown
}
I have an BottomDrawer but i want that when BottomDrawer is closed the gesturesEnabled should be false else if its open then gesturesEnabled should be true
Here's the
code I know its not my but same
val scope = rememberCoroutineScope()
val drawerState = rememberBottomDrawerState(BottomDrawerValue.Closed)
val gesturesEnabled = !drawerState.isClosed
Column {
Row(
modifier = Modifier.fillMaxWidth()
) {
Checkbox(gesturesEnabled, null)
Text(text = if (gesturesEnabled) "Gestures Enabled" else "Gestures Disabled")
}
BottomDrawer(
gesturesEnabled = gesturesEnabled,
drawerState = drawerState,
drawerContent = {
Button(
modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 16.dp),
onClick = { scope.launch { drawerState.close() } },
content = { Text("Close Drawer") }
)
LazyColumn {
items(25) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
},
content = {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
val openText = if (gesturesEnabled) "▲▲▲ Pull up ▲▲▲" else "Click the button!"
Text(text = if (drawerState.isClosed) openText else "▼▼▼ Drag down ▼▼▼")
Spacer(Modifier.height(20.dp))
Button(onClick = { scope.launch { drawerState.open() } }) {
Text("Click to open")
}
}
}
)
}
I'm trying to create a dialog where you can pick a number. I use FlowRow from Accompanist. But it gives strange results when scrolling. I guess that I'm missing something here.
The code:
#Composable
fun AlertDialogErrorTest() {
var showDlg by remember { mutableStateOf(false)}
val scrollState = rememberScrollState()
if (showDlg) {
AlertDialog(
onDismissRequest = { showDlg = false },
title = { Text(text = "Pick something from the list") },
text = {
FlowRow(modifier = Modifier.verticalScroll(scrollState)) {
for (i in 1..100) {
Box (modifier = Modifier.size(48.dp).padding(8.dp).background(Color.LightGray), contentAlignment = Alignment.Center) {
Text (i.toString())
}
}
}
},
confirmButton = {
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
},
dismissButton = {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
}
)
}
Button(onClick = {showDlg = true}) {
Text("Show dialog")
}
}
The result when scrolling:
This is because AlertDialog does not currently support scrollable content. If you think this is a bug, you can report it in the Compose issue tracker.
Meanwhile, you can use a plain Dialog, which lays under the AlertDialog.
Dialog(
onDismissRequest = { showDlg = false },
content = {
Column(Modifier.background(Color.White)) {
Text(
text = "Pick something from the list",
style = MaterialTheme.typography.subtitle1,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
FlowRow(
modifier = Modifier
.verticalScroll(scrollState)
.weight(1f)
) {
for (i in 1..100) {
Box(
modifier = Modifier
.size(48.dp)
.padding(8.dp)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text(i.toString())
}
}
}
FlowRow(
mainAxisSpacing = 8.dp,
crossAxisSpacing = 12.dp,
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 8.dp, vertical = 2.dp)
) {
TextButton(onClick = { showDlg = false }) {
Text("Cancel")
}
TextButton(onClick = { showDlg = false }) {
Text("Done")
}
}
}
},
)
Result:
I have faced the same issue, and the work around is to pass null in both title , and text, and to put the whole content in the buttons, you might check the below example, and please forgive my naming :).
AlertDialog(
onDismissRequest = {
state.onDismiss(state.data)
},
title = null,
text = null,
buttons = {
Column {
val expandedState =
state.data.expandedState
expandedState?.let {
RenderFieldPerCorrespondingState(it.title)
LazyColumnViewGroupComposable(
viewGroupState = it.itemsState,
scrollTo = scrollTo
)
ViewGroupComposable(viewGroupState = it.actionsState)
}
}
}
)