What is assigned to the variable updateSection in the offical sample code? - kotlin

The Code A is from offical sample code here.
val (currentSection, updateSection) = rememberSaveable { mutableStateOf(tabContent.first().section) } is a destructuring declaration.
It's a clear that currentSection is assigned by tabContent.first().section
What is assigned to the variable updateSection? Will it be assigned by tabContent.first().content ?
Code A
#Composable
fun InterestsRoute(
...
) {
val tabContent = rememberTabContent(interestsViewModel)
val (currentSection, updateSection) = rememberSaveable {
mutableStateOf(tabContent.first().section)
}
...
}
#Composable
fun rememberTabContent(interestsViewModel: InterestsViewModel): List<TabContent> {
val uiState by interestsViewModel.uiState.collectAsState()
val topicsSection = TabContent(Sections.Topics) {
val selectedTopics by interestsViewModel.selectedTopics.collectAsState()
TabWithSections(
sections = uiState.topics,
selectedTopics = selectedTopics,
onTopicSelect = { interestsViewModel.toggleTopicSelection(it) }
)
}
...
return listOf(topicsSection, peopleSection, publicationSection)
}
enum class Sections(#StringRes val titleResId: Int) {
Topics(R.string.interests_section_topics),
People(R.string.interests_section_people),
Publications(R.string.interests_section_publications)
}
class TabContent(val section: Sections, val content: #Composable () -> Unit)

updateSection is a lambda here used to update the value of the mutable state.
Consider this example:
#Composable
fun MyButton() {
val (count, updateCount) = remember { mutableStateOf(0) }
Button(
onClick = { updateCount(count+1) }
) {
Text(text = "$count")
}
}
Here count is Int and updateCount is (Int) -> Unit. updateCount takes an Int and updates the value of the MutableState (count) to the provided value (here count+1).
The above code updates the count text by one everytime the button is clicked.
Coming back to your code, rememberSaveable { mutableStateOf (...) } creates a new MutableState. Its initial value will be tabContent.first().section. currentSection stores the value of this MutableState (initially it will be tabContent.first().section). Now if you want to update the value of this MutableState you can use the updateSection lambda. Just invoke that lambda with the new value and currentSection will be updated automatically.

Related

why my data is coming as null or default in ui from view model in kotlin?

I set up mvvm structure and call two functions in viewmodel. Functions return values, but these values ​​always come as values ​​that I define as null or default.
Why can't I access my data in the view model in the UI?
I can see the data properly in the view model
hear is my code
my ui
if (viewModel.isDialogShown) {
AlertDialog(
onDismiss = {
viewModel.onDismissClick()
},
onConfirm = {
println("First"+viewModel.getFirstConversionRateByCurrency(viewModel.dropDownMenuItem1))
println("SECOND:"+viewModel.getSecondConversionRateByCurrency(viewModel.dropDownMenuItem2))
}
)
}
If the user says confirm in the alert dialog in my UI, these functions are called and I print the returned values ​​for testing purposes, but because I define null as default in the viewmodel, it always comes null.
my view model
#HiltViewModel
class ExchangeMainViewModel #Inject constructor(
private val getConversionRateByCurrencyUseCase: GetConversionRateByCurrencyUseCase
) : ViewModel() {
var second : Double?=null
var first : Double? = null
fun getFirstConversionRateByCurrency(currency:String) : String {
viewModelScope.launch {
first = getConversionRateByCurrencyUseCase.getConversionRateByCurrency(currency)
}
return first.toString()
}
fun getSecondConversionRateByCurrency(currency:String) :String {
viewModelScope.launch {
second = getConversionRateByCurrencyUseCase.getConversionRateByCurrency(currency)
}
return second.toString()
}
}
Also, I defined the viewmodel in ui as you can see the below like this, could it be because of this?
#Composable
fun DropDownMenu(
viewModel: ExchangeMainViewModel = hiltViewModel()
) {
The result in the console is as follows
All those functions your are calling are executing a coroutine from viewModelScope.launch{…} and at the time you launched them you immediately return a value from the method return.xxx.toString() where the xxx (first and second) doesn't have a value yet since what you are expecting is coming from a concurrent execution not a sequential one.
viewModelScope.launch { // launch me separately and wait for any value that will set the `first`
first = getConversionRateByCurrencyUseCase.getConversionRateByCurrency(currency)
}
// return from this function now with a null value, since it was initialized as null
return first.toString()
This is a rough implementation, though I'm not sure if this would work, but this should give you a headstart
Your new ViewModel
#HiltViewModel
class ExchangeMainViewModel #Inject constructor(
private val getConversionRateByCurrencyUseCase: GetConversionRateByCurrencyUseCase
) : ViewModel() {
var conversionValue by mutableStateOf<ConversionValues?>(null)
fun getFirstConversionRateByCurrency(currency:String) {
viewModelScope.launch {
val first = getConversionRateByCurrencyUseCase.getConversionRateByCurrency(currency)
val second = getConversionRateByCurrencyUseCase.getConversionRateByCurrency(currency)
conversionValue = ConversionValues(first, second)
}
}
}
the Data Class for first and second
data class ConversionValues(
val first : Double,
val second: Double
)
and your composable
#Composable
fun DropDownMenu(
viewModel: ExchangeMainViewModel = hiltViewModel()
) {
val conversionValue = vieModel.conversionValue
if (viewModel.isDialogShown) {
AlertDialog(
onDismiss = {
viewModel.onDismissClick()
},
onConfirm {
println("First" + conversionValue.first)
println("SECOND:"+conversionValue.second)
}
)
}
}

Kotlin: How to set the mutableState of an Integer in another composable function?

For readability purposes, I want to extract the NavigationBar composable in another function. Same with PreviousButton. Therefore I want to pass the mutableState of index to a these functions. But passing index as a parameter does not work, because I cannot update the state. What can I do?
#Composable
fun MyChickensScreen(){
val art: List<Art> = Datasource().loadArt()
var index: Int by remember { mutableStateOf(0) }
// IDE suggests making index a val,
// but I want to update the state in another composable.
//...
NavigationBar(index = index)
}
}
//NavigationBar passes index to the PreviousButton Composable
#Composable
private fun PreviousButton(index: Int) {
Button(
onClick = { index = handlePrevClick(index) }, //Error: Val cannot be reassigned for index
) {
//...
}
}
You can add a lambda function for updating to value on the mutable state to NavigationBar and PreviousButton:
#Composable
fun MyChickensScreen(){
val art: List<Art> = Datasource().loadArt()
var index: Int by remember { mutableStateOf(0) }
// IDE suggests making index a val,
// but I want to update the state in another composable.
//...
NavigationBar(
index = index,
updateIndex = { index = it }
)
}
#Composable
private fun PreviousButton(
index: Int,
updateIndex: (index: Int) -> Unit
) {
Button(
onClick = { updateIndex(handlePrevClick(index)) },
) {
//...
}
}
Now you can update index mutable state by passing new value to updateIndex lambda function.
There is a probably better solution, but what I have been doing are:
Put a variable inside viewmodel, and make an update method, pass the view model or method to composable
Or
Pass method down to update the index
NavigationBar(index = index,
update ={it->
index = it
})
}
#Composable
private fun PreviousButton(index: Int, update: (Int)-> Unit {
Button(
onClick = { update.invoke(index) },
) {
//...
}
}

How can I launch recomposition when a specified Flow changed in Jetpack Compose?

I know the Column(){...} will be recomposition when either b1 or b2 is changed.
If I hope that Column(){...} can be re-composed only when b2 is changed and Column(){...} doesn't be recomposed when b1 is changed, how can I do?
#Composable
fun ScreenDetail(
mViewMode: SoundViewModel
) {
val b1=mViewMode.a1.collectAsState(initial = 0)
val b2=mViewMode.a2.collectAsState(initial = 0)
Column() {
Text(" ${b1.value} ${b2.value}")
Text(Calendar.getInstance().time.toSeconds())
}
}
fun Date.toSeconds():String{
return SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US).format(this)
}
class SoundViewModel(): ViewModel() {
var i = 0
val a1: Flow<Int> = flow {
while (true) {
emit(i++)
delay(1000)
}
}
val a2: Flow<Int> = flow {
while (true) {
emit(i)
delay(2000)
}
}
}
You need to create scopes if you need to have scoped recompositions. By scopes i mean creating a Composable that is not inline unlike Column, Row or Box. You can check answer and articles in this link.
Compose recomposes closest scope/function that States are read. If you read
Text(" ${b1.value} ${b2.value}")
your Column will be recomposed when any of these states changes. But as mentioned above even if you read any of the they should have changed because Column doesn't create a scope
#Composable
fun ScreenDetail2(
mViewMode: SoundViewModel
) {
val b1=mViewMode.a1.collectAsState(initial = 0)
val b2=mViewMode.a2.collectAsState(initial = 0)
Column(modifier= Modifier.background(getRandomColor()).fillMaxWidth()) {
Text("${b1.value}")
}
Column(modifier= Modifier.background(getRandomColor()).fillMaxWidth()) {
Text("${b2.value}")
}
}
But if you create a function such as
#Composable
private fun MyColumn(counter:Int){
Column(modifier= Modifier.background(getRandomColor()).fillMaxWidth()) {
Text("$counter")
}
}
you will be having scopes for each value you read
#Composable
fun ScreenDetail3(
mViewMode: SoundViewModel
) {
val b1=mViewMode.a1.collectAsState(initial = 0)
val b2=mViewMode.a2.collectAsState(initial = 0)
MyColumn(b1.value)
MyColumn(b2.value)
}
As you can see in the gif ScreenDetail2 recomposes each Column when b1 or b2 changes but ScreenDetail3 only recomposes respective scope of function. I changed delay time of b1 to 300 and b2 to 2000 to make recomposition easy to observe visually.
2 tolumns on top is from ScreenDetail2, and the bottom is from ScreenDetail3
Recomposition can be observer in many ways, i add 2
class Ref(var value: Int)
// Note the inline function below which ensures that this function is essentially
// copied at the call site to ensure that its logging only recompositions from the
// original call site.
#Composable
inline fun LogCompositions(msg: String) {
val ref = remember { Ref(0) }
SideEffect { ref.value++ }
println("$msg, recomposition: ${ref.value}")
}
or changing colors
fun getRandomColor() = Color(
red = Random.nextInt(256),
green = Random.nextInt(256),
blue = Random.nextInt(256),
alpha = 255
)

Why needn't the author to wrap text.isNotBlank() with remember?

The Code A is from the official Sample project here.
1: I think I can wrap text.isNotBlank() withremember, so I think Code B is good, right?
BTW, I know the system will re-calculate when the text ( val (text, setText) = remember { mutableStateOf("") } ) is changed. So
2: In Code B, val (icon, setIcon) = remember { mutableStateOf(TodoIcon.Default)} and val iconsVisible = remember {text.isNotBlank() } will be re-launched when the text is changed (val (text, setText) = remember { mutableStateOf("") }) , right?
Code A
#Composable
fun TodoItemInput(onItemComplete: (TodoItem) -> Unit) {
val (text, setText) = remember { mutableStateOf("") }
val (icon, setIcon) = remember { mutableStateOf(TodoIcon.Default)}
val iconsVisible = text.isNotBlank()
Column {
Row( /* ... */ ) {
/* ... */
}
if (iconsVisible) {
AnimatedIconRow(icon, setIcon, Modifier.padding(top = 8.dp))
} else {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
Code B
#Composable
fun TodoItemInput(onItemComplete: (TodoItem) -> Unit) {
val (text, setText) = remember { mutableStateOf("") }
val (icon, setIcon) = remember { mutableStateOf(TodoIcon.Default)}
val iconsVisible = remember {text.isNotBlank() } //I add remember
Column {
...
}
}
If you use remember as in Code B, iconsVisible will be calculated only once and the same value will be used across all recompositions and not get updates when text changes, which is not what we want here.
If you want to use remember here, you should pass text as a key to it, remember(text) { text.isNotBlank() }. But as this is not a time consuming calculation, you can just skip the remember block and use it as in Code A. The .isNotBlank() function will be invoked in every recomposition but that doesn't matter much here.

Kotlin / What i need to pass to init: (index: Int)->Contact?

I have a question, what value I need to pass to "init: (index: Int)->Contact" as it expects:
The integer literal does not conform to the expected type (Int) ->
TypeVariable(T)
Snippet of the function
#Composable
fun ContactList(textVal: MutableState<TextFieldValue>, navigateToProfile: (Contact) -> Unit) {
var contacts = remember { DataProvider.contactList }
var contactList = contacts.toMutableList()
var filteredContacts: MutableList<Contact>
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
) {
val searchText = textVal.value.text
filteredContacts = if (searchText.isEmpty()){
contactList
}
else{
val resultList = MutableList<Contact>(10, "init: (inded: Int)->Contact")
for (contact in contactList) {
if (contact.contains(searchText.lowercase())) {
resultList.add(contact)
}
}
resultList
}
items(filteredContacts) {
ContactListItem(contact = it, navigateToProfile)
}
}
}
Snippet of the Contact
data class Contact(
val id: Int,
val name: String,
val description: String,
val recipe: String,
val ImageId: Int = 0
)
(index: Int)->Contact means: a function that receives Int and returns Contact. But I guess this does not really solve your problem.
The problem is that you use MutableList "constructor" (it is actually a function) which is intended to create a list with exactly 10 items. Then you need to provide these items and this is what init function is for. What you actually need here is to create an empty list and you can do this with:
val resultList = mutableListOf<Contact>()
However, if you just need to filter some collection and create another one, it is much easier to use filter():
val resultList = contactList.filter { it.contains(searchText.lowercase()) }
If Contact.contains() is an operator then we can also simplify it to:
val resultList = contactList.filter { searchText.lowercase() in it }