The Code A is from offical sample code here.
The code val (currentSection, updateSection) = rememberSaveable { mutableStateOf(tabContent.first().section) } will create two variables, one is currentSection, another is updateSection.
According to the Hint of Android Studio, I find the following definition
val currentSection: Sections
val updateSection: (Sections) → Unit
I read the source code of both tabContent and rememberSaveable, but I can't understand why the rememberSaveable can destructure it to two different variables (Sections And (Sections) → Unit). Why can't the rememberSaveable destructure it to three different variables with other types?
Code A
#Composable
fun InterestsRoute(
interestsViewModel: InterestsViewModel,
isExpandedScreen: Boolean,
openDrawer: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
) {
val tabContent = rememberTabContent(interestsViewModel)
val (currentSection, updateSection) = rememberSaveable {
mutableStateOf(tabContent.first().section)
}
InterestsScreen(
tabContent = tabContent,
currentSection = currentSection,
isExpandedScreen = isExpandedScreen,
onTabChange = updateSection,
openDrawer = openDrawer,
scaffoldState = scaffoldState
)
}
#Composable
fun rememberTabContent(interestsViewModel: InterestsViewModel): List<TabContent> {
...
return listOf(topicsSection, peopleSection, publicationSection)
}
class TabContent(val section: Sections, val content: #Composable () -> Unit)
The destructing that you are referring to has actually nothing to do with rememberSaveable.
The rememberSaveable { mutableStateOf(...) } function returns a MutableState and this is what can be destructured.
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
Here you can see the two components that you are referring to ( T and (T) -> Unit)
Related
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) }
}
}
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 :)
I was told that MutableState just like MutableLiveData in Kotlin, and MutableState fit Compose, MutableLiveDataenter code here fit XML layout.
In Code A, I need to assign data to bb.value, but why do I assign directly to aa ?
Code A
private var aa by mutableStateOf(-1)
private var bb= MutableLiveData<Int>(-1)
fun onEditDone() {
aa = 2
bb.value = 2
}
It's because of Kotlin's delegation feature where you delegate values using by keyword.
Simple implementation for remember and mutableState, to display how it works when you build something similar to that, is as
// Delegation Functions for setting and getting value
operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
operator fun <T> MutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
/*
* State
*/
interface State<out T> {
val value: T
}
interface MutableState<T> : State<T> {
override var value: T
}
class MutableStateImpl<T>(value: T) : MutableState<T> {
override var value: T = value
}
fun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)
/*
* Remember
*/
inline fun <T> remember(calculation: () -> T): T {
return calculation()
}
And you can use it as
fun main() {
val isSelected: MutableState<Boolean> = remember { mutableStateOf(true) }
isSelected.value = false
var selected by remember { mutableStateOf(false) }
selected = false
}
I have an issue with MyApp function, content value is unresolved and for ContactContent() shows this error: #Composable invocations can only happen from the context of a #Composable function
#Composable
fun MyApp(navigateToProfile: (Contact) -> Unit){
Scaffold {
content = {
ContactContent(navigateToProfile = navigateToProfile)
}
}
}
ContactContent Snippet
#Composable
fun ContactContent(navigateToProfile: (Contact) -> Unit) {
val contacts = remember { DataProvider.contactList }
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
items(
items = contacts,
itemContent = {
ContactListItem(contact = it, navigateToProfile)
}
)
}
}
You are already in a Scaffold's body. U don't need to use content = {}
Change to:
#Composable
fun MyApp(navigateToProfile: (Contact) -> Unit){
Scaffold {
ContactContent(navigateToProfile = navigateToProfile)
}
}
content is a parameter of Scaffold If you want to use it:
#Composable
fun MyApp(navigateToProfile: (Contact) -> Unit){
Scaffold(
content = {
ContactContent(navigateToProfile = navigateToProfile)
}
)
}
both work the same way.
I would like to simplify this code by reducing it to just a lambda. The interface has only one function. I'm not sure how to replace the override part of the code with just a lambda expression:
interface ITextWatcher {
fun onTextChanged(text: String) {
}
}
val textChangeHandler = object: ITextWatcher {
override fun onTextChanged(text: String)
var t = text
}
}
I'm looking for something like this:
val textChangeHandler = object: ITextWatcher {text ->
}
But that won't compile.
The syntax is val textChangeHandler = ITextWatcher {text -> ... }, but it doesn't work for interfaces declared in Kotlin, only for Java ones (at least for now).
Use (String) -> Unit directly instead. Or declare a function to convert one to another:
inline fun ITextWatcher(crossinline f: (String) -> Unit) = object : ITextWatcher {
override fun onTextChanged(text: String) {
f(text)
}
}
val textChangeHandler = ITextWatcher {text -> ... }
if you want.