Changing the jetpack compose remember variable from within another function - kotlin

I am developing an application for android devices, using the jetpack compose library.
Example I created inside a compose function
var taskVeriable = remember {mutableStateOf("Hello World")}
I need to update the value of variable from another compose function. Is there any way to achieve this?
#Composable
fun TestComposeA(){
var taskVeriable = remember {mutableStateOf("Hello World")}
TestComposeB(taskVeriable)
}
#Composable
fun TestComposeB(taskVeriable : String){
taskVeriable = "New Value"
}
I want to know if there is a way to do this.

You can pass mutable state and change it value later:
#Composable
fun TestComposeA(){
val taskVeriable = remember {mutableStateOf("Hello World")}
TestComposeB(taskVeriable)
}
#Composable
fun TestComposeB(taskVeriable : MutableState<String>){
taskVeriable.value = "New Value"
}

You can pass a function that changes your state.
This might help you:
#Composable
fun CompA() {
var text by remember { mutableStateOf("") }
CompB(
text = text,
onChange = { newText ->
text = newText
}
)
}
#Composable
fun CompB(
text: String,
onChange: (String) -> Unit
) {
TextField(value = text, onValueChange = { onChange(it) })
}

Related

jetpack Compose row/column onClick navigation

Please bear with me I don't know how to properly phrase my question, but I'll try my best.
I made the screen above by putting muliple composables together.
I gave the composable carrying the row an onClick function;
#Composable
fun MenuGrid(
modifier: Modifier = Modifier,
onMenuCardClick: (Int) -> Unit = {},
) {
LazyHorizontalGrid(
rows = GridCells.Fixed(3),
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier
.height(200.dp)
) {
items(MenuData) { item ->
MenuCard(
drawable = item.drawable,
text = item.text,
modifier = Modifier
.height(56.dp)
.clickable { onMenuCardClick(item.drawable + item.text) }
)
}
}
}
Like I said earlier I put multiple composables together, to form the image above. I arranged the multiple composables in a composable, I called MenuContentScreen;
#Composable
fun MenuContentScreen(modifier: Modifier = Modifier) {
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)) {
MenuQuote()
MenuContentSection(title = R.string.favorite_collections) {
MenuGrid()
}
}
}
Then I referenced/called the MenuContentScreen on the main Composable of that screen MenuScreen. (The one defined in NavHost)
#Composable
fun MenuScreen() {
MenuContentScreen(Modifier.padding())
}
Which is where the problem is, since the onClick function was defined in another composable, I can't use the onClick function for the MenuScreen;
So MY QUESTION is there a way I can link the onClick function on MenuGrid to MenuScreen, maybe creating an parameter on MenuScreen and assigning it to the MenuGrid's onClick function (which I have tried and got val cannot be assigned), or Any thing at all.
I will greatly appreciate any help. I have been on this like forever. No information is too small please.
Thanks in Advance.
You need to carry onClick lambda till your MenuScreen Composable such as
#Composable
fun MenuContentScreen(modifier: Modifier = Modifier,
onMenuCardClick: () -> Unit
) {
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)) {
MenuQuote()
MenuContentSection(title = R.string.favorite_collections) {
MenuGrid(){
onMenuCardClick()
}
}
}
#Composable
fun MenuScreen(
onMenuCardClick: () -> Unit
) {
MenuContentScreen(Modifier.padding(), onClick= onMenuCardClick)
}
And in you nav graph without passing navController to MenuScreen
call
MenuContentScreen {
navController.navigate()
}

Can I wrap collectAsState with remember when I use Jetpack Compose?

I hope to share a parameter val isCanAddRecord by mViewMode.isCanAddRecord.collectAsState() among #Composable functions.
The Code A is based the article How can I share info among #Composable function in Android Studio?
I know collectAsState() is wrapped with remember, you can see the Source Code.
Now you will find the object watchState is wrapped with remember, and watchState.isCanAddRecord which is assiged to mViewMode.isCanAddRecord.collectAsState() is wrapped with remember again.
Will the Code A cause error?
Code A
#Composable
fun ScreenHome_Watch(
modifier: Modifier = Modifier,
mViewMode: SoundViewModel,
watchState:WatchState = rememberWatchState(mViewMode)
){
...
}
class WatchState(
val isCanAddRecord: State<Boolean>,
...
){
...
}
#Composable
fun rememberWatchState(mViewMode: SoundViewModel): WatchState {
val watchState = WatchState(mViewMode.isCanAddRecord.collectAsState())
return remember {
watchState
}
}
Source Code
#Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
if (context == EmptyCoroutineContext) {
collect { value = it }
} else withContext(context) {
collect { value = it }
}
}
#Composable
fun <T> produceState(
initialValue: T,
key1: Any?,
key2: Any?,
#BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
): State<T> {
val result = remember { mutableStateOf(initialValue) }
LaunchedEffect(key1, key2) {
ProduceStateScopeImpl(result, coroutineContext).producer()
}
return result
}
Of course you can remember your mutableState or any other remember or anything that is not Composable. You can remember measurePolicy or even another code block as lambda for drawing like Modifier.drawWithCache does. Jetnews App sample does what you about. This is a matter of preference, you can store anything that is not Composable inside your remember block.
/**
* Remembers the content for each tab on the Interests screen
* gathering application data from [InterestsViewModel]
*/
#Composable
fun rememberTabContent(interestsViewModel: InterestsViewModel): List<TabContent> {
// UiState of the InterestsScreen
val uiState by interestsViewModel.uiState.collectAsState()
// Describe the screen sections here since each section needs 2 states and 1 event.
// Pass them to the stateless InterestsScreen using a tabContent.
val topicsSection = TabContent(Sections.Topics) {
val selectedTopics by interestsViewModel.selectedTopics.collectAsState()
TabWithSections(
sections = uiState.topics,
selectedTopics = selectedTopics,
onTopicSelect = { interestsViewModel.toggleTopicSelection(it) }
)
}
val peopleSection = TabContent(Sections.People) {
val selectedPeople by interestsViewModel.selectedPeople.collectAsState()
TabWithTopics(
topics = uiState.people,
selectedTopics = selectedPeople,
onTopicSelect = { interestsViewModel.togglePersonSelected(it) }
)
}
val publicationSection = TabContent(Sections.Publications) {
val selectedPublications by interestsViewModel.selectedPublications.collectAsState()
TabWithTopics(
topics = uiState.publications,
selectedTopics = selectedPublications,
onTopicSelect = { interestsViewModel.togglePublicationSelected(it) }
)
}
return listOf(topicsSection, peopleSection, publicationSection)
}
val tabContent = rememberTabContent(interestsViewModel)
val (currentSection, updateSection) = rememberSaveable {
mutableStateOf(tabContent.first().section)
}
Remember lambda
fun Modifier.drawWithCache(
onBuildDrawCache: CacheDrawScope.() -> DrawResult
) = composed(
inspectorInfo = debugInspectorInfo {
name = "drawWithCache"
properties["onBuildDrawCache"] = onBuildDrawCache
}
) {
val cacheDrawScope = remember { CacheDrawScope() }
this.then(DrawContentCacheModifier(cacheDrawScope, onBuildDrawCache))
}
Remember layout policy which is widely used with layouts
#Composable
#UiComposable
fun BoxWithConstraints(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content:
#Composable #UiComposable BoxWithConstraintsScope.() -> Unit
) {
val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
SubcomposeLayout(modifier) { constraints ->
val scope = BoxWithConstraintsScopeImpl(this, constraints)
val measurables = subcompose(Unit) { scope.content() }
with(measurePolicy) { measure(measurables, constraints) }
}
}

Is there a way to reuse "common" things like DropdownMenu in Compose Multiplatform Project?

For the desktop, DropdownMenu is supplied by Gradle: org.jetbrains.compose.material:material-desktop:1.0.1-rc2; for Android it's in Gradle: androidx.compose.material:material:1.1.0-beta04#aar
I would have thought there would be a common API that both implemented.
I know that I could define my own interface/adapter and then plug in the device specific version, but I'm wondering if there is a clever idiomatic Kotlin way to do this.
I tried using 'expect' and 'actual', but I could figure out the syntax. (DropdownMenu doesn't have a simple signature like in the examples of expect/actual usage).
Here's an example of a Menu I'm using... the Android and Desktop versions look identical:
// TODO is there way that this can be moved to Common
#Composable
fun BellSoundMenu(model: SessionViewModel, files: List<SoundFile>) {
val selectedIndex = remember { mutableStateOf(0) }
DropdownMenu(
expanded = model.isBellMenuExpanded.value,
...
) {
files.forEachIndexed { index, sound: SoundFile ->
DropdownMenuItem(onClick = {
selectedIndex.value = index
...
}) {
Row {
val isSelected = sound == model.getBellFile()
Icon(
...
)
Text(
...
)
}
}
}
}
}
I'm looking for a way to move this to the Common folder and not duplicate code.
I ended up doing this; I created an interface:
interface MyDropdown {
#Composable
fun Menu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier,
content: #Composable (androidx.compose.foundation.layout.ColumnScope.() -> Unit)
)
#Composable
fun MenuItem(
onClick: () -> Unit,
content: #Composable (androidx.compose.foundation.layout.RowScope.() -> Unit)
)
}
And then a small object to do the forwarding:
val AndroidDropdown = object : MyDropdown {
#Composable
override fun Menu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier,
content: #Composable (androidx.compose.foundation.layout.ColumnScope.() -> Unit)
) {
return DropdownMenu(
expanded = expanded, onDismissRequest = onDismissRequest, modifier = modifier, content = content
)
}
#Composable
override fun MenuItem(
onClick: () -> Unit, content: #Composable RowScope.() -> Unit
) {
return DropdownMenuItem(onClick = onClick, content = content)
}
}
I'm still curious if there's a more Kotlin-esque way of doing it :)

Preview a "screen" in android jetpack compose navigation with a PreviewParameter NavController

I discovering android Cetpack Compose (and Navigation) and try to display a preview of a view with a navController as parameter.
To achieve, I use the PreviewParameter and I have no error, but nothing displayed in the Preview window.
Anyone know how pass a fake NavController instance to a Composable?
class FakeNavController : PreviewParameterProvider<NavController> {
override val values: Sequence<NavController>
get() {}
}
#Preview
#Composable
fun Preview(
#PreviewParameter(FakeNavController::class) fakeNavController: NavController
) {
HomeView(fakeNavController)
}
You don't have to make it nullable and pass null to it.
You just need to pass this: rememberNavController()
#Preview
#Composable
fun Preview() {
HomeView(rememberNavController())
}
PreviewParameter is used to create multiple previews of the same view with different data. You're expected to return the needed values from values. In your example you return nothing that's why it doesn't work(it doesn't even build in my case).
That won't help you to mock the navigation controller, as you still need to create it somehow to return from values. I think it's impossible.
Instead you can pass a handler, in this case you don't need to mock it:
#Composable
fun HomeView(openOtherScreen: () -> Unit) {
}
// your Navigation view
composable(Destinations.Home) { from ->
HomeView(
openOtherScreen = {
navController.navigate(Destinations.Other)
},
)
}
Finally, I declare a nullable NavController and it works.
#Composable
fun HomeView(navController: NavController?) {
Surface {
Column(
modifier = Modifier
.padding(all = 4.dp)
) {
Text(
text = "Home View",
style = MaterialTheme.typography.body2
)
Spacer(modifier = Modifier.height(8.dp))
Button(onClick = { navController?.navigate("lineRoute") }) {
Text(text = "nav to Line view")
}
}
}
}
#Preview
#Composable
fun Preview (){
HomeView(null)
}

Is a good way to use State<Boolean> in view model with Android Compose?

I have read some sample codes for learning Compose.
I find many sample projects use Code A to create a StateFlow in view model, then convert it to State in #Composable function, the UI will be updated automatically when drawerOpen is changed.
1: I think both Code B and Code C can do the same thing, right? Why does many projects seldom to use them?
2: Is Code A a good way ?
3: I needn't to add rememberSaveable for variable drawerOpen in #Composable fun myRoute(...) because view model will store data, right?
Code A
class MainViewModel : ViewModel() {
private val _drawerShouldBeOpened = MutableStateFlow(false)
val drawerShouldBeOpened: StateFlow<Boolean> = _drawerShouldBeOpened
...
}
#Composable
fun myRoute(
val drawerOpen by MainViewModel.drawerShouldBeOpened.collectAsState() //Do I need add rememberSaveable ?
...
}
Code B
class MainViewModel : ViewModel() {
private var _drawerShouldBeOpened = mutableStateOf(false)
val drawerShouldBeOpened: State<Boolean> = _drawerShouldBeOpened
...
}
#Composable
fun myRoute(
val drawerOpen = MainViewModel.drawerShouldBeOpened //Do I need add rememberSaveable ?
...
}
Code C
class MainViewModel : ViewModel() {
private var _drawerShouldBeOpened = false
val drawerShouldBeOpened: Boolean = _drawerShouldBeOpened
...
}
#Composable
fun myRoute(
val drawerOpen = rememberSaveable { mutableStateOf(MainViewModel.drawerShouldBeOpened)) //Can I remove rememberSaveable ?
}
There are multiple questions here.
Let me answer whatever is possible.
1. Where should you use remember / rememberSaveable? (Code A, B, or C)
Only in code C it is required.
(No issues in using in code A and B as well, but no advantages there)
Reason,
In code A and B - the state is maintained in the view model. Hence the value survives recomposition.
But in code C, the state is created and maintained inside the composable. Hence remember is required for the value to survive recomposition.
More details in Docs
2. Why Code C is not used much?
Composable recomposition happens whenever there is a change in state, not the value.
Given below is a simple example to demonstrate the same.
class ToggleViewModel : ViewModel() {
private val _enabledStateFlow = MutableStateFlow(false)
val enabledStateFlow: StateFlow<Boolean> = _enabledStateFlow
private val _enabledState = mutableStateOf(false)
val enabledState: State<Boolean> = _enabledState
private var _enabled = false
val enabled: Boolean = _enabled
fun setEnabledStateFlow(isEnabled: Boolean) {
_enabledStateFlow.value = isEnabled
}
fun setEnabledState(isEnabled: Boolean) {
_enabledState.value = isEnabled
}
fun setEnabled(isEnabled: Boolean) {
_enabled = isEnabled
}
}
#Composable
fun BooleanToggle(
viewmodel: ToggleViewModel = ToggleViewModel(),
) {
val enabledStateFlow by viewmodel.enabledStateFlow.collectAsState()
val enabledState by viewmodel.enabledState
val enabled by rememberSaveable {
mutableStateOf(viewmodel.enabled)
}
Column {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Text(text = if (enabledStateFlow) {
"Enabled"
} else {
"Disabled"
})
Button(onClick = { viewmodel.setEnabledStateFlow(!enabledStateFlow) }) {
Text("Toggle State Flow")
}
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Text(text = if (enabledState) {
"Enabled"
} else {
"Disabled"
})
Button(onClick = { viewmodel.setEnabledState(!enabledState) }) {
Text("Toggle State")
}
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Text(text = if (enabled) {
"Enabled"
} else {
"Disabled"
})
Button(onClick = { viewmodel.setEnabled(!enabled) }) {
Text("Toggle Value")
}
}
}
}
You can see that the third text will NOT update on clicking the button.
The reason is that the mutable state inside the composable was created using an initial value from the view model data. But further updates to that data will not be reflected in the composable.
To get updates, we have to use reactive data like Flow, LiveData, State, and their variants.
3. Using StateFlow vs State.
From the docs, you can see that compose supports Flow, LiveData and RxJava.
You can see in the usage that we are using collectAsState() for StateFlow.
The method converts StateFlow to State. So both can be used.
Use Flow if the layers beyond ViewModel (like repo) are the data sources and they use Flow data type.
Else MutableState should be fine.