Search code examples
androidkotlinmvvmandroid-room

Getting null whenever trying to collect data from SharedFlow


I have a SharedFlow in ViewModel, where I make a call to repository, to retrieve from the database a single record by some id. This is will be set whenever user will click on some record in RecyclerView. The problem is I constantly getting null, but if I hardcode id in repository parameter, then everything works fine.

DAO

@Query("SELECT * FROM employees WHERE id = :id")
fun getEmployeeById(id: Int?): Flow<EmployeeModel>

RepositoryInterface

fun getEmployeeById(id: Int?): Flow<EmployeeModel>

RepositoryImplementation

    override fun getEmployeeById(id: Int?): Flow<EmployeeModel> {
    return employeeDAO.getEmployeeById(id)
}

ViewModel

var employeeById: SharedFlow<DatabaseState<EmployeeModel>> = repository.get().getEmployeeById(employeeId.value?.toInt())
    .map {
        println("onCreateView in VM. ID ${employeeId.value}  |  data: $it")
        DatabaseState.Success(it) }
    .catch { DatabaseState.Error(it.message) }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 0)

Fragment

viewLifecycleOwner.lifecycleScope.launch {
        repeatOnLifecycle(Lifecycle.State.STARTED) {
           mViewModel.employeeById.collect{ employee ->
               when (employee){
                   is DatabaseState.Success -> {
                       Log.i(TAG, "onCreateView APDEJCIK: ${mViewModel.employeeId.value} | ${employee.data}")
                   }
                   is DatabaseState.Error -> {
                       Log.i(TAG, "onCreateView: Failed to retrieve data about employee in UpdateFragmentEmployee fragment")}
               }
           }
        }
    }

As you can see, I'm loging the ID from the ViewModel a couple of times, and it every times has correct id to the position I've clicked, so the ID should be ok.

Edit: Model class

    @Entity(tableName = "employees")
data class EmployeeModel(
    @PrimaryKey(autoGenerate = true)
    val id: Int,
    @ColumnInfo(name = "name")
    val name: String,
    @ColumnInfo(name = "surname")
    val surname: String,
    @ColumnInfo(name = "age")
    val age: Int,
    @ColumnInfo(name = "workplace")
    val workplace: String,
    @ColumnInfo(name = "salary")
    val salary: Double
)

Solution

  • I think these code below have a problem

    var employeeById: SharedFlow<DatabaseState<EmployeeModel>> = repository.get().getEmployeeById(employeeId.value?.toInt())
    .map {
        println("onCreateView in VM. ID ${employeeId.value}  |  data: $it")
        DatabaseState.Success(it) }
    .catch { DatabaseState.Error(it.message) }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 0)
    

    Because of this declaration, your employeeById will be created when your view model is created, so employeeId.value is still null. Then, because of SharingStarted.WhileSubscribed(). the map function will only get called when your flow has a subscriber on your repeatOnLifecycle(Lifecycle.State.STARTED). At this time, your employeeId.value is set the correct value. This is why you get a very weird log.

    To fix your issue, I think some things need to be changed.

    Your DAO

    @Query("SELECT * FROM employees WHERE id = :id")
    fun getEmployeeById(id: Int): Flow<EmployeeModel?>
    

    Your viewModel. I assume that you have a state flow for your employee. You should use flatMap to update your employeeById value whenever your employee changes.

    val employeeById: SharedFlow<DatabaseState<EmployeeModel>> = employeeId.filterNotNull().flatMapLatest {
       repository.get().getEmployeeById(it.toInt())
    }.map {
        if (it!= null) DatabaseState.Success(it) else DatabaseState.Error("NOT FOUND")
    }.catch { DatabaseState.Error(it.message) }
    .shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 0)
    

    Last thing, if you use employeeById to display data. Consider using StateFlow instead of SharedFlow