Home > Software design >  How to retrieve data from Firestore with the MVVM pattern
How to retrieve data from Firestore with the MVVM pattern

Time:12-26

I am creating an android application following the MVVM patron with the goal of retrieving data from a Firebase collection.

Before applying this pattern, I did proof of concept and I was able to retrieve data from the Firebase collection. But once I apply MVVM, I am not able to get the data from that collection, my screen does not show anything. I am not able to return the data from the repository to be painted on the screen.

This is my code:

Model:

data class PotatoesData(
    val modifiedDate: String,
    var potatoes: List<Potato>
) 
data class Potato(
    val type: String,
    val site: String
)

State:

data class PotatoesState(
    val isLoading: Boolean = false,
    val potatoes: List<Potato> = emptyList(),
    val error: String = ""
)

ModelView:

@HiltViewModel
class PotatoesViewModel @Inject constructor(
    private val getPotatoesDataUseCase: GetPotatoesData
) : ViewModel() {

    private val _state = mutableStateOf(PotatoesState())
    val state: State<PotatoesState> = _state

    init {
        getPotatoes()
    }

    private fun getPotatoes() {

        getPotatoesDataUseCase().onEach { result ->
            when (result) {
                is Resource.Success -> {
                    _state.value = PotatoesState(potatoes = result.data?.potatoes ?: emptyList())
                }
                is Resource.Error   -> {
                    _state.value = PotatoesState(
                        error = result.message ?: "An unexpected error occurred"
                    )
                }
                is Resource.Loading -> {
                    _state.value = PotatoesState(isLoading = true)
                }
            }
        }.launchIn(viewModelScope)
    }
}

UseCase:

class GetPotatoesData @Inject constructor(
    private val repository: PotatoRepository
) {
    operator fun invoke(): Flow<Resource<PotatoesData>> = flow {
        try {
            emit(Resource.Loading())
            val potatoes = repository.getPotatoesData()
            emit(Resource.Success(potatoes))
        } catch (e: IOException) {
            emit(Resource.Error("Couldn't reach server. Check your internet connection."))
        }
    }
}

Repository implementation:

class PotatoRepositoryImpl : PotatoRepository {

    override suspend fun getPotatoesData(): PotatoesData {

        var potatoes = PotatoesData("TEST", emptyList())

        FirestoreProvider.getLastPotatoes(
            { potatoesData ->
                if (potatoesData != null) {
                    potatoes = potatoesData 
                }
            },
            { 
                potatoes 
            }
        )

        return potatoes 
    }
}

Firestore provider:

object FirestoreProvider {

    private val incidentsRef = FirebaseFirestore.getInstance().collection(FirestoreCollection.POTATOES.key)

    fun getLastPotatoes(
        success: (potatoesData: PotatoesData?) -> Unit,
        failure: () -> Unit
    ) {

        val query: Query = orderBy(FirestoreField.CREATED_DATE, Query.Direction.DESCENDING).limit(1)
        val querySnapshot: Task<QuerySnapshot> = query.get()

        querySnapshot
            .addOnSuccessListener {
                if (!querySnapshot.result.isEmpty) {
                    val document = querySnapshot.result.documents[0]
                    val potatoesDataDB: PotatoesDataDto? = document.toObject(PotatoesDataDto::class.java)
                    potatoesDataDB?.let {
                        success(potatoesDataDB.toPotatoesData())
                    } ?: run {
                        success(null)
                    }
                } else {
                    success(null)
                }
            }
            .addOnFailureListener {
                failure()
            }
    }

    private fun orderBy(field: FirestoreField, direction: Query.Direction): Query {

        return incidentsRef.orderBy(field.key, direction)
    }

}

I am thankful for any kind of help! Thanks in advance!

CodePudding user response:

I think the error is in the way of how you are handling Firestore callbacks. in FirestoreProvider: the callback will fire later than the function getLastPotatoes returns. Try to make that function suspend and use suspendCoroutine to wait for the callback and return it's result. It will look something like:

suspend fun getLastPotatoes() = suspendCoroutine <PotatoesData?> { continuation ->

    val query: Query = orderBy(FirestoreField.CREATED_DATE, Query.Direction.DESCENDING).limit(1)
    val querySnapshot: Task<QuerySnapshot> = query.get()

    querySnapshot
        .addOnSuccessListener {
            if (!querySnapshot.result.isEmpty) {
                val document = querySnapshot.result.documents[0]
                val potatoesDataDB: PotatoesDataDto? = document.toObject(PotatoesDataDto::class.java)
                potatoesDataDB?.let {
                    continuation.resume(potatoesDataDB.toPotatoesData())
                } ?: run {
                    continuation.resume(null)
                }
            } else {
                continuation.resume(null)
            }
        }
        .addOnFailureListener {
            continuation.resumeWithException(...)
        }
}

suspendCoroutine suspends coroutine in which it executed until we decide to continue by calling appropriate methods - Continuation.resume....

In your PotatoRepositoryImpl:

override suspend fun getPotatoesData(): PotatoesData {

    var potatoes = PotatoesData("TEST", emptyList())

    try {
        val potatoesData = FirestoreProvider.getLastPotatoes()
        if (potatoesData != null) {
            potatoes = potatoesData
        }
    } catch (e: Exception) {
        // handle Exception
    }
   
    return potatoes 
}
  • Related