Yarin Shitrit
Yarin Shitrit

Reputation: 327

Android collectAsStateWithLifecycle() not collecting inside composable

I am using compose LazyColumn with viewModel updating the list items by having inside my viewModel:

    data class ContactsListUiState(
       val contacts: MutableList<Contact>
    )
    @HiltViewModel
    class ContactsViewModel @Inject constructor(savedStateHandle: SavedStateHandle) : ViewModel() {
    private val _contactsListUiState = MutableStateFlow(ContactsListUiState(mutableListOf()))
    val contactsListUiState: StateFlow<ContactsListUiState> = _contactsListUiState.asStateFlow()

    private fun updateContactsList(newContacts: MutableList<Contact>) {
        _contactsListUiState.update{ currentState ->
            currentState.copy(
                contacts = newContacts
            )
        }
    }

such that my updateContactsList() function updates the stateFlow and I am supposed to collect the contacts list with collectAsStateWithLifecycle() inside my composable function inside MainActivity

@OptIn(ExperimentalLifecycleComposeApi::class, ExperimentalFoundationApi::class)
    @Composable
    fun ContactsListScreen(
        navController: NavController,
        modifier: Modifier = Modifier
    ) {
        Log.d("ViewModel", "ContactsListScreen recomposed")
        val uiState by contactsViewModel.contactsListUiState.collectAsStateWithLifecycle()
        Box(modifier = modifier) {
            val listState = rememberLazyListState()
            val scope = rememberCoroutineScope()
            Column {
                TextField(
                    value = searchText,
                    singleLine = true,
                    onValueChange = {
                        searchText = it
                        Log.d("MainActivity", "calling filter with $it")
                        contactsViewModel.filterContactsByString(it)
                    },
                    leadingIcon = if (searchText.isNotBlank()) searchLeadingIcon else null,
                    trailingIcon = if (searchText.isBlank()) searchTrailingIcon else null,
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.White)
                )
                LazyColumn(
                    Modifier.fillMaxWidth(),
                    state = listState,
                    contentPadding = PaddingValues(bottom = 80.dp)
                ) {                      
                        items(uiState.contacts) { contact ->
                            ContactListItem(
                                navController,
                                contact = contact,
                                modifier = Modifier.fillMaxWidth()
                            )
                            Divider(thickness = 0.5.dp, color = colorResource(id = R.color.blue))
                    }
                }
                 ...

where contactsListViewModel is declared on top of the activity which I inject using hilt :

@AndroidEntryPoint
class MainActivity : ComponentActivity(), LoaderManager.LoaderCallbacks<Cursor> {
    private val contactsViewModel: ContactsViewModel by viewModels()

For some reason uiState.contacts is empty inside the composable function, but it does contain items when I logged it inside the viewModel function and therefore my list stays empty..

Any suggestions on what could have gone wrong?

Thanks

Upvotes: 5

Views: 2785

Answers (2)

Heberth Deza
Heberth Deza

Reputation: 615

To prevent this issue, in general we need to follow these rules:

  1. Inside "data class" that represents States of the UI, it is a good practice to declare the attributes with "val" instead of "var".
    data class UserProfileState(
        val dni: String,
        ...
    )
  1. Inside the ViewModel class, use .copy() function instead of modify attributes of the object when trying to assign it to the "value" of the "StateFlow" (Declare with "var" the variable to use)
@HiltViewModel
class UserProfileViewModel @Inject constructor(
    private val userRepository: UserRepository,
) : ViewModel() {

    private val _userProfileState: MutableStateFlow<UserProfileState> = MutableStateFlow(UserProfileState())
        val userProfileState: StateFlow<UserProfileState> = _userProfileState
    
    init {
          viewModelScope.launch {
              withContext(Dispatchers.IO) {
                 var userProfileState = UserProfileState(...)
                 userRepository.getUserProfile()
                        .collect { userProfile ->
                             userProfileState = userProfileState.copy(dni = userProfile.dni, ...)
                            _userProfileState.value = userProfileState
                        }
              }
          }
    }
}

Upvotes: 0

z.g.y
z.g.y

Reputation: 6277

Please use SnapshotStatelist/(mutableStateListOf()) instead of an ordinary list (mutableList).

private val _contactsListUiState = MutableStateFlow(ContactsListUiState(mutableStateListOf()))

Also please check this post this one and this one

Upvotes: 3

Related Questions