I invoke #Composable from the context of a #Composable function but still recieve an error - kotlin

Basically the main issue is in the header, here is my code snippet:
#Composable
fun CustomRadioGroupItemComponent(
radioOptions: List<String>,
onSelectBehavior: (optionIndex: Int, onOptionSelected: (Int) -> Unit) -> Unit,
imageStateCheck: (selectedOption: Int) -> Boolean
): Int {
if (radioOptions.isNotEmpty()) {
val (selectedOption, onOptionSelected) = remember {
mutableStateOf(0)
}
RadioGroup {
radioOptions.forEachIndexed { index, item ->
RadioGroupItem(
selected = false,
onSelect = {
onSelectBehavior(index, onOptionSelected)
}
) {
Row(modifier = Modifier.padding(10.dp)) {
if (imageStateCheck(selectedOption)) {
Image(
painter = painterResource(
id =
R.drawable.ic_radio_button_checked
), contentDescription = null
)
} else {
Image(
painter = painterResource(
id =
R.drawable.ic_radio_button_unchecked
), contentDescription = null
)
}
ClickableText(
text = AnnotatedString(item),
style = TextStyle(fontFamily = FontFamily(Font(R.font.nunito_light))),
onClick = {
onSelectBehavior(index, onOptionSelected)
}
)
}
}
}
}
return selectedOption
} else {
return 0
}
}
However, when I try to compile, I recieve this error near Row(modifier = Modifier.padding(10.dp)) { line, which located right under the RadioGroupItem and for each loop:
#Composable invocations can only happen from the context of a #Composable function
Is there any workaround? I stuck on this heavily

RadioGroup and RadioGroupItem were deprecated and removed over a year ago:
Previously deprecated RadioGroup and RadioGroupItems have been removed. Use Row and RadioBotton instead
Don't use tutorials older than February 24, 2021 (that's the day compose went into beta and stabilized the API), because they likely contain obsolete code.
Make sure you're using latest stable version of Compose. Right now it's 1.0.1.
I suggest you study modern documentation and tutorials

Related

How can I get progress indicator to show up before the data being loaded completely

This particular code dosent seems to work the way intended
#Composable
fun MainScreen(
navController: NavController,
mainViewModel: MainViewModel = hiltViewModel(),
city:String?
) {
Log.d("TAG", "MainScreen: $city")
val weatherData =
produceState<DataOrException<Weather, Boolean, Exception>>(
initialValue = DataOrException()
) {
value = mainViewModel.getWeatherData(city = city.toString())
}.value
if (weatherData.loading == true) {
CircularProgressIndicator()
}
if (weatherData.data != null ) {
MainScaffold(weather = weatherData.data!!, navController)
}
}
This particular code dosent seems to work the way intended

Why rememberCoroutineScope in jetpack compose doesn't have any launch method?

I am using jetpack compose version 1.3.2 and I am trying to call a suspend function using coroutine
but when I using rememberCoroutineScope function to create scope to launch it have no launch function.
Can anyone help?
this is my code
#OptIn(ExperimentalPagerApi::class)
#Composable
fun YearPicker(viewDate: JalaliCalendar, state: DatePickerState, pagerState: PagerState) {
val gridState = rememberLazyGridState(viewDate.year - state.yearRange.first)
val scope = rememberCoroutineScope()
LazyVerticalGrid(
columns = GridCells.Fixed(3),
state = gridState,
// modifier = Modifier.background(state.dialogBackground)
) {
itemsIndexed(state.yearRange.toList()) { _, item ->
val selected = remember { item == viewDate.year }
YearPickerItem(year = item, selected = selected, colors = state.colors) {
if (!selected) {
scope.launch{
pagerState.scrollToPage(
pagerState.currentPage + (item - viewDate.year) * 12
)
}
}
state.yearPickerShowing = false
}
}
}
}

Compose slider should respond to manual changes and value changes from external source

I want to create a slider in Jetpack Compose that can be manipulated manually and stores the value in a DB. At the same time, the slider should react to changes made by a coroutine when the value in the DB is changed.
I get either one or the other. Here is the version that reacts to the slider change (and updates the DB and external source), but doesn't update the slider when the external source has new values. How can I also set the slider to the new value if the external source writes a new value to the DB?
#Composable
fun showAllLights(lightsViewModel: LightsViewModel = hiltViewModel()) {
val allLights = lightsViewModel.lightsAndGroups.collectAsState(initial = emptyList())
val allLightsByGroupType = allLights.value.filter { it.groupType == "Room" }
val allGroups = allLightsByGroupType.distinctBy { it.groupId }
val allLightsGrouped = allLights.value.groupBy {
it.groupId ?: -1L
}
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
items(allGroups) { currentGroup ->
val currentGroupId = currentGroup.groupId ?: -1L
val lightsByKey = allLightsGrouped.filterKeys { it == currentGroupId }
LazyRow(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
lightsByKey.forEach { (key, value) ->
items(value) { light ->
singleLightCard(
light
)
}
}
}
}
}
}
#Composable
fun singleLightCard(light: ViewLights, lightsViewModel: LightsViewModel = hiltViewModel()) {
var initialBrightness = ((light.brightness * 100).toFloat() / 254).roundToInt().toFloat()
var valueSlider by remember { mutableStateOf(initialBrightness) }
Card(
modifier = Modifier
.height(128.dp)
.width(144.dp),
) {
Column(
) {
ConstraintLayout(
) {
val (lightBrightnessSlider) = createRefs()
Slider(
modifier = Modifier
.constrainAs(lightBrightnessSlider) {
bottom.linkTo(
parent.bottom
)
}
.height(15.dp),
value = valueSlider,
onValueChange = {
valueSlider = it
val errorMessage = lightsViewModel.onLightBrightnessChange(light, it)
},
valueRange = 1f..100f,
)
}
}
}
}
The solution was to replace
var valueSlider by remember { mutableStateOf(initialBrightness) }
with
var valueSlider by remember(initialBrightness) { mutableStateOf(initialBrightness) }
Since you are updating the database with the latest brightness value and the database emits whenever there is a change in the data, you don't need to create a valueSlider state. You can simply use initialBrightness as the value of the slider and each time DB is updated(by manual or external action) the lightsViewModel.lightsAndGroups will emit a new data set and the sliders of light brightness will be updated with the latest data.
PS: There can only be one source of truth for the slider. So we move that to the DB. If you really want to use valueSlider state, you can convert it to a val and then use derivedStateOf instead of mutableStateOf

Jetpack compose custom snackbar material 3

How to achieve a custom snackbar in compose using material 3? I want to change the alignment of the snackbar. Also I want dynamic icon on the snackbar either on left or right side.
You can customize your snackbar using SnackBar composable and can change alignment using SnackbarHost alignment inside a Box if that's what you mean.
val snackState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
Box(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
Column(modifier = Modifier.fillMaxSize()) {
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
coroutineScope.launch {
snackState.showSnackbar("Custom Snackbar")
}
}) {
Text("Show Snackbar")
}
}
SnackbarHost(
modifier=Modifier.align(Alignment.BottomStart),
hostState = snackState
) { snackbarData: SnackbarData ->
CustomSnackBar(
R.drawable.baseline_swap_horiz_24,
snackbarData.visuals.message,
isRtl = true,
containerColor = Color.Gray
)
}
}
#Composable
fun CustomSnackBar(
#DrawableRes drawableRes: Int,
message: String,
isRtl: Boolean = true,
containerColor: Color = Color.Black
) {
Snackbar(containerColor = containerColor) {
CompositionLocalProvider(
LocalLayoutDirection provides
if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
) {
Row {
Icon(
painterResource(id = drawableRes),
contentDescription = null
)
Text(message)
}
}
}
}
Validated answer does not answer the question properly because the icon is provided in a static way, not dynamic. The icon is not passed to showSnackbar.
You can do it by having your own SnackbarVisuals :
// Your custom visuals
// Default values are the same than SnackbarHostState.showSnackbar
data class SnackbarVisualsCustom(
override val message: String,
override val actionLabel: String? = null,
override val withDismissAction: Boolean = false,
override val duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
// You can add custom things here (for you it's an icon)
#DrawableRes val drawableRes: Int
) : SnackbarVisuals
// The way you decide how to display your custom visuals
SnackbarHost(hostState = snackbarHostState) {
val customVisuals = it.visuals as SnackbarVisualsCustom
Snackbar {
// Here is your custom snackbar where you use your icon
}
}
// To display the custom snackbar
snackbarHostStateScope.launch {
snackbarHostState.showSnackbar(
SnackbarVisualsCustom(
message = "The message",
drawableRes = R.drawable.your_icon_id
)
)
}

Select all text of TextField in Jetpack Compose

I'm using TextField component in Jetpack Compose.
How to select all text when it receive focus?
In this case you should use TextFieldValue as state of your TextField, and when it receive focus, you set the selection using the TextFieldValue state.
val state = remember {
mutableStateOf(TextFieldValue(""))
}
TextField(
value = state.value,
onValueChange = { text -> state.value = text },
modifier = Modifier
.onFocusChanged { focusState ->
if (focusState.isFocused) {
val text = state.value.text
state.value = state.value.copy(
selection = TextRange(0, text.length)
)
}
}
)
Here's the result:
Notice that depending on you're touching the cursor goes to the touched position instead of select the entire text. You can try to figure it out if this is a bug or a feature :)
#nglauber solution doesn't seems to work anymore.
Debugging shows that onFocusChanged is called before onValueChange and within one view life cycle. A selection changed during onFocusChanged has no effect on TextField, since it is overridden during onValueChange.
Here's a possible workaround:
var state by remember {
mutableStateOf(TextFieldValue("1231"))
}
var keepWholeSelection by remember { mutableStateOf(false) }
if (keepWholeSelection) {
// in case onValueChange was not called immediately after onFocusChanged
// the selection will be transferred correctly, so we don't need to redefine it anymore
SideEffect {
keepWholeSelection = false
}
}
TextField(
value = state,
onValueChange = { newState ->
if (keepWholeSelection) {
keepWholeSelection = false
state = newState.copy(
selection = TextRange(0, newState.text.length)
)
} else {
state = newState
}
},
modifier = Modifier
.onFocusChanged { focusState ->
if (focusState.isFocused) {
val text = state.text
state = state.copy(
selection = TextRange(0, text.length)
)
keepWholeSelection = true
}
}
)
I think it should be possible to make it easier, so I created this question on Compose issue tracker.
I didn't have 100% success with #nglauber answer. You should add a small delay and it works great. For example:
val state = remember {
mutableStateOf(TextFieldValue(""))
}
// get coroutine scope from composable
val scope = rememberCoroutineScope()
TextField(
value = state.value,
onValueChange = { text -> state.value = text },
modifier = Modifier
.onFocusChanged {
if (it.hasFocus) {
// start coroutine
scope.launch {
// add your preferred delay
delay(10)
val text = state.value.text
state.value = state.value.copy(
selection = TextRange(0, text.length)
)
}
}
}
)
I wrote a Modifier extension function that works in spite of bug pointed out by #Pylyp
fun Modifier.onFocusSelectAll(textFieldValueState: MutableState<TextFieldValue>): Modifier =
composed(
inspectorInfo = debugInspectorInfo {
name = "textFieldValueState"
properties["textFieldValueState"] = textFieldValueState
}
) {
var triggerEffect by remember {
mutableStateOf<Boolean?>(null)
}
if (triggerEffect != null) {
LaunchedEffect(triggerEffect) {
val tfv = textFieldValueState.value
textFieldValueState.value = tfv.copy(selection = TextRange(0, tfv.text.length))
}
}
Modifier.onFocusChanged { focusState ->
if (focusState.isFocused) {
triggerEffect = triggerEffect?.let { bool ->
!bool
} ?: true
}
}
}
usage
#Composable
fun SelectAllOnFocusDemo() {
var tfvState = remember {
mutableStateOf(TextFieldValue("initial text"))
}
TextField(
modifier = Modifier.onFocusSelectAll(tfvState),
value = tfvState.value,
onValueChange = { tfvState.value = it },
)
}
I want to add to the Phil's answer I wanted to update the state dynamically and I ended up with this:
var state by remember(textVal) {
mutableStateOf(TextFieldValue(text = textVal, selection = TextRange(textVal.length)))
}
It does two things, first it updates the field if your textVal changes, also puts the cursor at the end.