Jetpack Compose load Firestore Data with Flow

Create a class to handle data state (loading, error, success), as I believe it is impossible to handle exception within composable.

enum class Status {    SUCCESS,    ERROR,    LOADING}// https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/vo/Resource.ktdata class Resource<out T>(val status: Status, val data: T?, val message: String?) {    companion object {        fun <T> success(data: T?): Resource<T> {            return Resource(Status.SUCCESS, data, null)        }        fun <T> error(msg: String, data: T?): Resource<T> {            return Resource(Status.ERROR, data, msg)        }        fun <T> loading(data: T?): Resource<T> {            return Resource(Status.LOADING, data, null)        }    }}

Data Class

data class Post(var id: String, var created: Timestamp? = null, var title: String? = null) {  companion object {    fun toObject(doc: DocumentSnapshot): Post? {        val item = doc.toObject<Post>()        item?.id = doc.id        return item    }  }}

ViewModel to load data from Firestore using Flow

class MainViewModel : ViewModel() {    // consider using Dependency injection with Hilt    private val auth: FirebaseAuth = Firebase.auth    private val db = Firebase.firestore    fun fetchPosts() = callbackFlow {        val collection = db.collection("posts")        val snapshotListener = collection.orderBy("created", Query.Direction.DESCENDING).addSnapshotListener { value, error ->            val response = if (error == null && value != null) {                val data = value.documents.map { doc ->                    Post.toObject(doc)                }                Resource.success(data)            } else {                Timber.e(error)                Resource.error(error.toString(), null)           }            offer(response)        }        awaitClose() {            snapshotListener.remove()        }    }}

Composable

@Composablefun JourneyApp(viewModel: MainViewModel = viewModel()) {    val postsResource by viewModel.fetchPosts().collectAsState(initial = Resource.loading(null))    val posts = postsResource.data ?: emptyList()    if (postsResource.status == Status.ERROR) {        Text("Error: ${postsResource.message}")    }    else if (postsResource.status == Status.LOADING) {        Text("Loading ....")    }    else {        LazyColumn {            items(posts) { post ->                Text(post.title)            }        }    }}

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.