Reputation: 1
I'm creating a modify edit pane (in a team tasks tracking app). I'd like to create and visualize some fields with empty values if no teamId is provided (it will be a new team) or existing values taken from a team list (saved in the View Model).
When i try to compose it i noticed (via log prints) that the same component is recomposed 4 times with the same state. This unexpected behavior leads to other strange results. Why does this happen?
Edit team Component
fun NewEditTeam(
viewModel: AppViewModel = viewModel(),
navHostController: NavHostController,
teamId: Int = -1
) {
val uiState = viewModel.editTeamUi
val randomNumber = Random.nextInt()
Log.d("NE", "Recomposed EditTeam view with state: $uiState")
var addRoleExpanded by remember {
mutableStateOf<TeamMember?>(null)
}
if (teamId != -1) {
val team = viewModel.getTeamById(teamId)
viewModel.updateValuesFromExistingTeam(team)
} else {
viewModel.addMember(viewModel.myProfile)
}
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = {
if (viewModel.validateAll()) {
viewModel.saveTeam(teamId = teamId)
navHostController.popBackStack()
viewModel.resetEditTeamUi()
}
content = {
Icon(
Icons.Default.Check,
contentDescription = "AddMembers",
modifier = Modifier.size(50.dp)
)
},
)
}
) { padding ->
Column {
NavBar {
viewModel.resetEditTeamUi()
navHostController.navigateUp()
}
TeamImage(
imageUri = uiState.image.value,
name = uiState.name.value,
setImage = { viewModel.updateTeamImage(it) },
)
TeamInfo(
uiState,
onNameChange = { viewModel.updateTeamName(it) },
onDescChange = { viewModel.updateTeamDescription(it) },
)
TeamMemberList(
members = uiState.members.value,
invitedMembers = uiState.invitedMembers.value,
addMemberOnClick = {
navHostController.navigate("editTeam/inviteMember/")
},
findProfileById = { viewModel.getProfileById(it)!! },
onDeleteRole = { tm, role ->
viewModel.deleteRole(tm, role)
},
onAdminSwitchChanged = { tm, isAdmin ->
viewModel.changeAdmin(tm, isAdmin)
},
onAddRole = { addRoleExpanded = it },
isErrorText = uiState.membersError.value,
myProfile = viewModel.myProfile
)
if (addRoleExpanded != null) {
AddRoleDialog(
assignedRoles = addRoleExpanded!!.roles,
removeRole = { viewModel.deleteRole(addRoleExpanded!!, it) },
addRole = { viewModel.addRole(addRoleExpanded!!, it) },
roleList = uiState.members.value.map { it.roles.toSet() }.flatten()
.toSet(),
onDismiss = { addRoleExpanded = null },
updateDialogStatus = {
val newTM = viewModel.findTeamMemberById(teamId, addRoleExpanded!!.profileId)
addRoleExpanded = newTM
}
)
}
}
}
}
View Model and Ui State classes
data class EditTeamUi(
val name: MutableState<String> = mutableStateOf(""),
val desc: MutableState<String> = mutableStateOf(""),
val members: MutableState<List<TeamMember>> = mutableStateOf(listOf()),
val image: MutableState<Uri?> = mutableStateOf(null),
val category: MutableState<String> = mutableStateOf(""),
val invitedMembers: MutableState<List<TeamMember>> = mutableStateOf(listOf()),
val nameError: MutableState<String> = mutableStateOf(""),
val membersError: MutableState<String> = mutableStateOf(""),
)
class AppViewModel : ViewModel() {
// data lists as state flows //
var editTeamUi = EditTeamUi()
fun updateTeamName(name: String) {
editTeamUi.name.value = name
}
fun updateTeamImage(uri: Uri) {
editTeamUi.image.value = uri
}
fun updateTeamDescription(desc: String) {
editTeamUi.desc.value = desc
}
fun deleteRole(tm: TeamMember, role: String) {
val updatedMember = tm.apply { roles -= role }
if (editTeamUi.members.value.contains(tm)) {
val newList = editTeamUi.members.value - tm + updatedMember
editTeamUi.members.value = newList
} else {
val newList = editTeamUi.invitedMembers.value - tm + updatedMember
editTeamUi.invitedMembers.value = newList
}
}
fun changeAdmin(tm: TeamMember, admin: Boolean) {
val updatedMember = tm.apply { isAdmin = admin }
if (editTeamUi.members.value.contains(tm)) {
val newList = editTeamUi.members.value - tm + updatedMember
editTeamUi.members.value = newList
} else {
val newList = editTeamUi.invitedMembers.value - tm + updatedMember
editTeamUi.invitedMembers.value = newList
}
}
fun updateValuesFromExistingTeam(teamToEdit: Team?) {
if (teamToEdit != null) {
editTeamUi.apply {
name.value = teamToEdit.teamName
desc.value = teamToEdit.teamDescription
image.value = teamToEdit.profilePic
members.value = teamToEdit.teamMembers
invitedMembers.value = teamToEdit.invitedMembers
category.value = teamToEdit.category
}
}
}
fun addRole(tm: TeamMember, role: String) {
val updatedMember = tm.apply { roles += role }
if (editTeamUi.members.value.contains(tm)) {
val newList = editTeamUi.members.value - tm + updatedMember
editTeamUi.members.value = newList
} else {
val newList = editTeamUi.invitedMembers.value - tm + updatedMember
editTeamUi.invitedMembers.value = newList
}
}
fun addInvitedProfiles(invited: List<Profile>) {
val inv = invited.map {
TeamMember(
profileId = it.id,
)
}
editTeamUi.members.value += inv
}
private fun validateName() {
editTeamUi.nameError.value = if (editTeamUi.name.value.isBlank())
"Team name cannot be empty"
else ""
}
private fun validateMembers() {
editTeamUi.membersError.value =
if (editTeamUi.members.value.size + editTeamUi.invitedMembers.value.size == 1)
"A team must contain at least one member"
else ""
}
fun validateAll(): Boolean {
validateName()
validateMembers()
return (editTeamUi.nameError.value.isBlank() && editTeamUi.membersError.value.isBlank())
}
fun addMember(profile: Profile) {
editTeamUi.members.value += TeamMember(profile.id)
}
fun saveTeam(teamId : Int) {
val updatedTeam = Team(
teamId = teamId,
invitedMembers = editTeamUi.invitedMembers.value,
category = editTeamUi.category.value,
teamDescription = editTeamUi.category.value,
teamName = editTeamUi.name.value,
teamMembers = editTeamUi.members.value,
creationDate = Date(),
profilePic = editTeamUi.image.value
)
if (updatedTeam.teamId == -1) {
var newId = 0
while (teamsList.value.map { it.teamId }.contains(newId)) {
newId = abs(Random.nextInt())
}
updatedTeam.teamId = newId
val newList = teamsList.value + updatedTeam
teamsList.value = newList
} else {
teamsList.value = teamsList.value.map {
if (updatedTeam.teamId == it.teamId) updatedTeam
else it
}
}
}
fun resetEditTeamUi() {
editTeamUi.apply {
name.value = ""
desc.value = ""
invitedMembers.value = listOf()
members.value = listOf()
image.value = null
category.value = ""
membersError.value = ""
nameError.value = ""
}
}
fun findTeamMemberById(teamId: Int, profileId: Int): TeamMember? {
val team = getTeamById(teamId)
return team?.teamMembers?.find { it.profileId == profileId }
}
}
Logged the Ui state at every recomposition. I got the same state for the last 3. In the first the fields has been not initialized yet.
2024-06-02 11:31:53.792 5813-5813 NE it.polito.lab4_manageteams D Recomposed EditTeam view with state: EditTeamUi(name=MutableState(value=)@94259335, desc=MutableState(value=)@233321396, members=MutableState(value=[])@215419869, image=MutableState(value=null)@119247698, category=MutableState(value=)@17622563, invitedMembers=MutableState(value=[])@45012768, nameError=MutableState(value=)@213747929, membersError=MutableState(value=)@222007966)
2024-06-02 11:31:54.155 5813-5813 NE it.polito.lab4_manageteams D Recomposed EditTeam view with state: EditTeamUi(name=MutableState(value=Backend Development)@94259335, desc=MutableState(value=Responsible for server-side logic, database management, and API integration.)@233321396, members=MutableState(value=[TeamMember(profileId=0, isAdmin=true, roles=[Developer, Designer]), TeamMember(profileId=1, isAdmin=false, roles=[Tester]), TeamMember(profileId=2, isAdmin=false, roles=[Developer]), TeamMember(profileId=3, isAdmin=true, roles=[Project Manager])])@215419869, image=MutableState(value=null)@119247698, category=MutableState(value=)@17622563, invitedMembers=MutableState(value=[TeamMember(profileId=4, isAdmin=false, roles=[Quality Assurance])])@45012768, nameError=MutableState(value=)@213747929, membersError=MutableState(value=)@222007966)
2024-06-02 11:31:54.822 5813-5813 NE it.polito.lab4_manageteams D Recomposed EditTeam view with state: EditTeamUi(name=MutableState(value=Backend Development)@94259335, desc=MutableState(value=Responsible for server-side logic, database management, and API integration.)@233321396, members=MutableState(value=[TeamMember(profileId=0, isAdmin=true, roles=[Developer, Designer]), TeamMember(profileId=1, isAdmin=false, roles=[Tester]), TeamMember(profileId=2, isAdmin=false, roles=[Developer]), TeamMember(profileId=3, isAdmin=true, roles=[Project Manager])])@215419869, image=MutableState(value=null)@119247698, category=MutableState(value=)@17622563, invitedMembers=MutableState(value=[TeamMember(profileId=4, isAdmin=false, roles=[Quality Assurance])])@45012768, nameError=MutableState(value=)@213747929, membersError=MutableState(value=)@222007966)
2024-06-02 11:31:54.879 5813-5813 NE it.polito.lab4_manageteams D Recomposed EditTeam view with state: EditTeamUi(name=MutableState(value=Backend Development)@94259335, desc=MutableState(value=Responsible for server-side logic, database management, and API integration.)@233321396, members=MutableState(value=[TeamMember(profileId=0, isAdmin=true, roles=[Developer, Designer]), TeamMember(profileId=1, isAdmin=false, roles=[Tester]), TeamMember(profileId=2, isAdmin=false, roles=[Developer]), TeamMember(profileId=3, isAdmin=true, roles=[Project Manager])])@215419869, image=MutableState(value=null)@119247698, category=MutableState(value=)@17622563, invitedMembers=MutableState(value=[TeamMember(profileId=4, isAdmin=false, roles=[Quality Assurance])])@45012768, nameError=MutableState(value=)@213747929, membersError=MutableState(value=)@222007966)
Upvotes: 0
Views: 40
Reputation: 878
This is because you're updating the state inside the composable. Any state updates should happen either outside of a composable scope or inside a side effect like LaunchedEffect
.
LaunchedEffect(teamId) {
if (teamId != -1) {
val team = viewModel.getTeamById(teamId)
viewModel.updateValuesFromExistingTeam(team)
} else {
viewModel.addMember(viewModel.myProfile)
}
}
Upvotes: 0