why I always get empty documents when using pagination in Firestore - android

I am trying to use pagination by using document snapshot to define the query cursor.
when to fragment is opened for the first time, in onCreateView I use the code below to get 7 events from firestore
fun getAllSearchedEventsFromTheBeginning(startDate: Date, endDate: Date, selectedCity: String, selectedEventType: String, limit: Long, completion: (errorMessage:String?, events: ArrayList<Event>?, lastDocument: DocumentSnapshot?) -> Unit) {
// not only free events, paid events are also included
FirestoreCollectionReference.event.getReference()
.whereEqualTo(FIRESTORE_EVENT_FIELD_CITY,selectedCity)
.whereEqualTo(FIRESTORE_EVENT_FIELD_EVENT_TYPE, selectedEventType)
.whereEqualTo(FIRESTORE_EVENT_FIELD_HAS_BEEN_APPROVED,true)
.whereGreaterThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,startDate)
.whereLessThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,endDate)
.orderBy(FIRESTORE_EVENT_FIELD_DATE_START_TIME, Query.Direction.ASCENDING)
.limit(limit)
.get()
.addOnSuccessListener { snapshot ->
val lastDocument = snapshot.documents[snapshot.size() - 1]
val eventDocuments = snapshot.documents
val eventArray = ArrayList<Event>()
for (document in eventDocuments) {
val eventData = document.data
val event = Event(dataEvent = eventData)
eventArray.add(event)
}
completion(null,eventArray, lastDocument)
}.addOnFailureListener {
completion(it.localizedMessage,null,null)
}
}
I am using lamda expression, to send the lastVisible document, and that lastVisible document will be used as the starting point for my next query
after reaching the bottom of my recycler view, then I use the code below to get the next 7 documents from firestore
fun getAllSearchedEventsAfterLastDocument(startDate: Date, endDate: Date, selectedCity: String, selectedEventType: String, limit: Long, lastDocument: DocumentSnapshot?, completion: (errorMessage:String?, events: ArrayList<Event>?, lastDocument: DocumentSnapshot?) -> Unit) {
// not only free events, paid events are also included
FirestoreCollectionReference.event.getReference()
.whereEqualTo(FIRESTORE_EVENT_FIELD_CITY,selectedCity)
.whereEqualTo(FIRESTORE_EVENT_FIELD_EVENT_TYPE, selectedEventType)
.whereEqualTo(FIRESTORE_EVENT_FIELD_HAS_BEEN_APPROVED,true)
.whereGreaterThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,startDate)
.whereLessThan(FIRESTORE_EVENT_FIELD_DATE_START_TIME,endDate)
.orderBy(FIRESTORE_EVENT_FIELD_DATE_START_TIME, Query.Direction.ASCENDING)
.limit(limit)
.startAfter(lastDocument)
.get()
.addOnSuccessListener { snapshot ->
val eventDocuments = snapshot.documents
if (eventDocuments.isEmpty()) {
completion("Event is empty",null, null)
} else {
val lastDocument = snapshot.documents.last()
val eventArray = ArrayList<Event>()
for (document in eventDocuments) {
val eventData = document.data
val event = Event(dataEvent = eventData)
eventArray.add(event)
}
completion(null,eventArray, lastDocument)
}
}.addOnFailureListener {
completion(it.localizedMessage,null,null)
}
}
I am sure that I send the same parameters to both of those function,
and the last documents is also correct, it exactly the same as the last document that appears in my recycler view.
but I always get empty documents if called that second function getAllSearchedEventsAfterLastDocument
and this line below always triggered in getAllSearchedEventsAfterLastDocument.
if (eventDocuments.isEmpty()) {
completion("Event is empty",null, null)
}
please help me, I am confused.

I finally find the problem,
in .startAfter(lastDocument)
that lastDocument still in nullable type (DocumentSnapshot?), it shouldn't.

Related

Refresh Data in ViewModel when Navigating back - Android(Kotlin)

I have the following setup.
I have a screen with a list of items (PlantsScreen). When clicking on an item from the list I will be navigated to another screen (AddEditPlantScreen). After editing and saving the item and navigating back to the listScreen, I want to show the updated list of items. But the list is not displaying the updated list but the list before the edit of the item.
In order to have a single source of truth, I am fetching the data from a node.js Back-End and then saving it to the local repository (Room). I think I need to refresh the state in the ViewModel to fetch the updated list from my repository.
I know I can use a Job to do this, but it throws me an error. Is this the correct approach when returning a Flow?
If yes, how can I achieve this.
If not, what alternative approach do I have?
plantsListViewModel.kt
private val _state = mutableStateOf<PlantsState>(PlantsState())
val state: State<PlantsState> = _state
init {
getPlants(true, "")
}
private fun getPlants(fetchFromBackend: Boolean, query: String) {
viewModelScope.launch {
plantRepository.getPlants(fetchFromBackend, query)
.collect { result ->
when (result) {
is Resource.Success -> {
result.data?.let { plants ->
_state.value = state.value.copy(
plants = plants,
)
}
}
}
}
}
}
Here is my repository where I fetch the items in the list from.
// plantsRepository.kt
override suspend fun getPlants(
fetchFromBackend: Boolean,
query: String
): Flow<Resource<List<Plant>>> {
return flow {
emit(Resource.Loading(true))
val localPlants = dao.searchPlants(query)
emit(
Resource.Success(
data = localPlants.map { it.toPlant() },
)
)
val isDbEmpty = localPlants.isEmpty() && query.isBlank()
val shouldLoadFromCache = !isDbEmpty && !fetchFromBackend
if (shouldLoadFromCache) {
emit(Resource.Loading(false))
return#flow
}
val response = plantApi.getPlants().plants
dao.clearPlants()
dao.insertPlants(
response.map { it.toPlantEntity() }
)
emit(Resource.Success(
data = dao.searchPlants("").map { it.toPlant() }
))
emit(Resource.Loading(false))
}
}
The full code for reference can be found here:
https://gitlab.com/fiehra/plants
Thank you!
You actually have two sources of truth: One is the room database, the other the _state object in the view model.
To reduce this to a single source of truth you need to move the collection of the flow to the compose function where the data is needed. You will do this using the extension function StateFlow.collectAsStateWithLifecycle() from the artifact androidx.lifecycle:lifecycle-runtime-compose. This will automatically subscribe and unsubscribe the flow when your composable enters and leaves the composition.
Since you want the business logic to stay in the view model you have to apply it before the flow is collected. The idea is to only transform the flow in the view model:
class PlantsViewModel {
private var fetchFromBackend: Boolean by mutableStateOf(true)
private var query: String by mutableStateOf("")
#OptIn(ExperimentalCoroutinesApi::class)
val state: StateFlow<PlantsState> =
snapshotFlow { fetchFromBackend to query }
.flatMapLatest { plantRepository.getPlants(it.first, it.second) }
.mapLatest(PlantsState::of)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = PlantsState.Loading,
)
// ...
}
If you want other values for fetchFromBackend and query you just need to update the variables; the flow will automatically recalculate the state object. It can be as simple as just calling something like this:
fun requestPlant(fetchFromBackend: Boolean, query: String) {
this.fetchFromBackend = fetchFromBackend
this.query = query
}
The logic to create a PlantsState from a result can then be done somewhere else in the view model. Replace your PlantsViewModel.getPlants() with this and place it at file level outside of the PlantsViewModel class:
private fun PlantsState.Companion.of(result: Resource<List<Plant>>): PlantsState = when (result) {
is Resource.Success -> {
result.data?.let { plants ->
PlantsState.Success(
plants = plants,
)
} ?: TODO("handle case where result.data is null")
}
is Resource.Error -> {
PlantsState.Error("an error occurred")
}
is Resource.Loading -> {
PlantsState.Loading
}
}
With the PlantsState class replaced by this:
sealed interface PlantsState {
object Loading : PlantsState
data class Success(
val plants: List<Plant> = emptyList(),
val plantOrder: PlantOrder = PlantOrder.Name(OrderType.Descending),
val isOrderSectionVisible: Boolean = false,
) : PlantsState
data class Error(
val error: String,
) : PlantsState
companion object
}
Then, wherever you need the state (in PlantsScreen f.e.), you can get a state object with
val state by viewModel.state.collectAsStateWithLifecycle()
Thanks to kotlin flows state will always contain the most current data from the room database, and thanks to the compose magic your composables will always update when anything in the state object updates, so that you really only have one single source of truth.
Additionally:
PlantRepository.getPlants() should not be marked as a suspend function because it just creates a flow and won't block; long running data retrieval will be done in the collector.
You will need to manually import androidx.compose.runtime.getValue and the androidx.compose.runtime.setValue for some of the delegates to work.
After #Leviathan was able to point me in the right direction i refactored my code by changing the return types of my repository functions, implementing use cases and returning a Flow<List<Plant>> instead of Flow<Resource<List<Plant>>> for simplicity purposes.
Further removed the suspend marker of the functions in the PlantDao.kt and PlantRepository.kt as pointed out by Leviathan.
// PlantRepositoryImplementation.kt
override fun getPlants(
fetchFromBackend: Boolean,
query: String
): Flow<List<Plant>> {
return flow {
if (fetchFromBackend) {
val response = plantApi.getPlants().plants
dao.clearPlants()
dao.insertPlants(
response.map { it.toPlantEntity() }
)
val localPlants = dao.searchPlants(query)
localPlants.collect { plants ->
emit(plants.map { it.toPlant() })
return#collect
}
} else {
val localPlants = dao.searchPlants(query)
localPlants.collect { plants ->
emit(plants.map { it.toPlant() })
return#collect
}
}
}
}
I started using a Job and GetPlants usecase in my viewModel like this:
// PlantsViewModel.kt
private fun getPlants(plantOrder: PlantOrder, fetchFromBackend: Boolean, query: String) {
getPlantsJob?.cancel()
getPlantsJob = plantUseCases.getPlants(plantOrder, fetchFromBackend, query)
.onEach { plants ->
_state.value = state.value.copy(
plants = plants,
plantOrder = plantOrder
)
}.launchIn(viewModelScope)
I also had to remove the suspend in the PlantDao.kt
// PlantDao.kt
fun searchPlants(query: String): Flow<List<PlantEntity>>
This is the code for my GetPlants usecase:
// GetPlantsUsecase.kt
class GetPlants
(
private val repository: PlantRepository,
) {
operator fun invoke(
plantOrder: PlantOrder = PlantOrder.Name(OrderType.Descending),
fetchFromBackend: Boolean,
query: String
): Flow<List<Plant>> {
return repository.getPlants(fetchFromBackend, query).map { plants ->
when (plantOrder.orderType) {
is OrderType.Ascending -> {
// logic for sorting
}
}
is OrderType.Descending -> {
// logic for sorting
}
}
}
}
}

How to schedule an API request asynchronously for one composable screen from another composable screen? (Jetpack Compose)

I'm a junior Android developer and trying to build a Facebook-like social media app. My issue is that when I bookmark a post in Screen B and the action succeeds, (1) I want to launch an API request in Screen A while in Screen B and (2) update the bookmarked icon ONLY for that particular post.
For the second part of the issue, I tried these two solutions.
I relaunched a manual API request on navigating back to Screen A. This updates the whole list when there's only one small change, hence very inefficient.
I built another URL route to fetch that updated post only and launched it on navigating back to Screen A. But to insert the newly updated post at the old index, the list has to be mutable and I ain't sure this is a good practice.
Please help me on how to solve this issue or similar issues. I'm not sure if this should be done by passing NavArg to update locally and then some or by using web sockets. Thanks in advance.
data class ScreenAState(
val posts: List<Post> = emptyList(),
val isLoading: Boolean = false)
data class ScreenBState(
val post: PostDetail? = null,
val isBookmarked: Boolean? = null)
data class Post(
val title: String,
val isBookMarked: Boolean,
val imageUrl: String)
data class PostDetail(
val title: String,
val content: String,
val isBookMarked: Boolean,
val imageUrl: String)
I suggest you continue with using your logic that will update your list on return from screen B to screen A, but instead of using simple list, you could use:
https://developer.android.com/reference/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList
This list is designed for what you need I think. Update just that one element.
In mean time, you can change that item from list to some loading dummy item, if you want to have loading like view while you wait for API call to finish.
The problem is how to handle data consistency, which is not directly related to jetpack compose. I suggest you solve this problem at the model level. Return flow instead of static data in the repository, and use collectAsState in the jetpack compose to monitor data changes.
It's hard to give an example, because it depends on the type of Model layer. If it's a database, androidx's room library supports returning flow; if it's a network, take a look at this.
https://gist.github.com/FishHawk/6e4706646401bea20242bdfad5d86a9e
Triggering a refresh is not a good option. It is better to maintain an ActionChannel in the repository for each list that is monitored. use the ActionChannel to modify the list locally to notify compose of the update.
For example, you can make a PagedList if the data layer is network. With onStart and onClose, channels can be added or removed from the repository, thus giving the repository the ability to update all the observed lists.
sealed interface RemoteListAction<out T> {
data class Mutate<T>(val transformer: (MutableList<T>) -> MutableList<T>) : RemoteListAction<T>
object Reload : RemoteListAction<Nothing>
object RequestNextPage : RemoteListAction<Nothing>
}
typealias RemoteListActionChannel<T> = Channel<RemoteListAction<T>>
suspend fun <T> RemoteListActionChannel<T>.mutate(transformer: (MutableList<T>) -> MutableList<T>) {
send(RemoteListAction.Mutate(transformer))
}
suspend fun <T> RemoteListActionChannel<T>.reload() {
send(RemoteListAction.Reload)
}
suspend fun <T> RemoteListActionChannel<T>.requestNextPage() {
send(RemoteListAction.RequestNextPage)
}
class RemoteList<T>(
private val actionChannel: RemoteListActionChannel<T>,
val value: Result<PagedList<T>>?,
) {
suspend fun mutate(transformer: (MutableList<T>) -> MutableList<T>) =
actionChannel.mutate(transformer)
suspend fun reload() = actionChannel.reload()
suspend fun requestNextPage() = actionChannel.requestNextPage()
}
data class PagedList<T>(
val list: List<T>,
val appendState: Result<Unit>?,
)
data class Page<Key : Any, T>(
val data: List<T>,
val nextKey: Key?,
)
fun <Key : Any, T> remotePagingList(
startKey: Key,
loader: suspend (Key) -> Result<Page<Key, T>>,
onStart: ((actionChannel: RemoteListActionChannel<T>) -> Unit)? = null,
onClose: ((actionChannel: RemoteListActionChannel<T>) -> Unit)? = null,
): Flow<RemoteList<T>> = callbackFlow {
val dispatcher = Dispatchers.IO.limitedParallelism(1)
val actionChannel = Channel<RemoteListAction<T>>()
var listState: Result<Unit>? = null
var appendState: Result<Unit>? = null
var value: MutableList<T> = mutableListOf()
var nextKey: Key? = startKey
onStart?.invoke(actionChannel)
suspend fun mySend() {
send(
RemoteList(
actionChannel = actionChannel,
value = listState?.map {
PagedList(
appendState = appendState,
list = value,
)
},
)
)
}
fun requestNextPage() = launch(dispatcher) {
nextKey?.let { key ->
appendState = null
mySend()
loader(key)
.onSuccess {
value.addAll(it.data)
nextKey = it.nextKey
listState = Result.success(Unit)
appendState = Result.success(Unit)
mySend()
}
.onFailure {
if (listState?.isSuccess != true)
listState = Result.failure(it)
appendState = Result.failure(it)
mySend()
}
}
}
var job = requestNextPage()
launch(dispatcher) {
actionChannel.receiveAsFlow().flowOn(dispatcher).collect { action ->
when (action) {
is RemoteListAction.Mutate -> {
value = action.transformer(value)
mySend()
}
is RemoteListAction.Reload -> {
job.cancel()
listState = null
appendState = null
value.clear()
nextKey = startKey
mySend()
job = requestNextPage()
}
is RemoteListAction.RequestNextPage -> {
if (!job.isActive) job = requestNextPage()
}
}
}
}
launch(dispatcher) {
Connectivity.instance?.interfaceName?.collect {
if (job.isActive) {
job.cancel()
job = requestNextPage()
}
}
}
awaitClose {
onClose?.invoke(actionChannel)
}
}
And in repository:
val postListActionChannels = mutableListOf<RemoteListActionChannel<Post>>()
suspend fun listPost() =
daoFlow.filterNotNull().flatMapLatest {
remotePagingList(
startKey = 0,
loader = { page ->
it.mapCatching { dao ->
/* dao function, simulate network operation, return List<Post> */
dao.listPost(page)
}.map { Page(it, if (it.isEmpty()) null else page + 1) }
},
onStart = { postListActionChannels.add(it) },
onClose = { postListActionChannels.remove(it) },
)
}
suspend fun markPost(title: String) =
oneshot {
/* dao function, simulate network operation, return Unit */
it.markPost(title)
}.onSuccess {
postListActionChannels.forEach { ch ->
ch.mutate { list ->
list.map {
if (it.title == title && !it.isBookMarked)
it.copy(isBookMarked = true)
else it
}.toMutableList()
}
}
}

Firebase Firestore dynamic controllers for data models with kotlin for android

I want to make a dynamic controllers function but I don't sure how to work with it.
I just create a function below
fun <T> CollectionReference.getData(cls : Class<T>, id : String, callback : (d : T?, exception: Exception?)->Unit){
this.document(id).get().addOnSuccessListener(OnSuccessListener {
it?.let{
val v = it.toObject(cls)
callback(v, null)
}
}).addOnFailureListener(OnFailureListener{
callback(null, it);
})
}
want to use the function like:
val userRef = FirebaseFirestore.getInstance().collection("users")
val citiesRef = FirebaseFirestore.getInstance().collection("cities")
userRef.getData(cls = User::class.java, id = "123"){ user, exception ->
user?.let{
// user data for use
}
exception?.let{
it.printStacktrace()
}
}
citiesRef.getData(cls = City::class.java, id = "abc"){ city, exception ->
city?.let{
// city data for use
}
exception?.let{
it.printStacktrace()
}
}
If there's a better way to use please let me know.
Thanks in advance :)

Firestore startAt skips the snapshot given to it and behaves as startAfter instead

I have a PagingSource that pages through a firestore collection to return documents.
class ClipPageDataSource(mParams:Bundle, private val mAds:Boolean):PagingSource<QuerySnapshot, Clip>(), ClipDataSource {
var query : Query?= null
private val mFirestore = FirebaseFirestore.getInstance()
private var mBaseQuery = mFirestore.collection(SharedConstants.COLLECTION_CLIPS)
.orderBy("createdAt",Query.Direction.DESCENDING)
private var mLikedQuery = mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_USER_LIKES)
.orderBy("createdAt",Query.Direction.DESCENDING)
private var mSavedQuery = mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_SAVES)
.orderBy("createdAt",Query.Direction.DESCENDING)
val mine = mParams.getBoolean(ClipDataSource.PARAM_MINE)
val liked = mParams.getBoolean(ClipDataSource.PARAM_LIKED)
val saved = mParams.getBoolean(ClipDataSource.PARAM_SAVED)
val user = mParams.getString(ClipDataSource.PARAM_USER)
val first = mParams.getString(ClipDataSource.PARAM_FIRST)
val private = mParams.getBoolean(ClipDataSource.PARAM_PRIVATE)
override fun getRefreshKey(state: PagingState<QuerySnapshot, Clip>): QuerySnapshot? {
return null
}
override suspend fun load(params: LoadParams<QuerySnapshot>): LoadResult<QuerySnapshot, Clip> {
try {
query = when {
liked -> {
mLikedQuery
}
saved -> {
mSavedQuery
}
else -> {
mBaseQuery
}
}
if(mine){
query = query!!.whereEqualTo("createdBy.uid",Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
}else if(user!=null){
query = query!!.whereEqualTo("createdBy.uid",user)
}
query = if (private){
query!!.whereEqualTo("private",true)
}else{
query!!.whereEqualTo("private",false)
}
first?.let {
val item = mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
if (item!=null){
query = query!!.startAt(item)
}
Log.d(TAG,"the first item fetched is ${item.data!!["id"]}")
}
query = query!!.limit(15)
val currentPage = params.key ?: query!!.get().await()
if (currentPage.size() < 1)
return LoadResult.Page(emptyList(),null,null)
val lastDocumentSnapshot = currentPage.documents[currentPage.size() - 1]
val nextPage = query!!.startAfter(lastDocumentSnapshot).get().await()
val clips = currentPage.map {
it.toObject(Clip::class.java)
}
return LoadResult.Page(clips,null,nextPage)
}catch (e:Exception){
return LoadResult.Error(e)
}
}
companion object{
private const val TAG = "DataSource"
}
}
So, in the above code, I have three different collections to fetch data from, and the required one is selected based on the parameters passed.
Now, when I fetch data using the mBaseQuery, and passing an id in first parameter, it returns the data correctly.
But, when I fetch data using the mLikedQuery or the mSavedQuery, instead of returning data from the id passed in first parameter, it uses the next item as the first one. Basically, startAt works as startAfter.
I have checked the snapshot fetched using the id passed in first is correct. So, the block in first?.let, works correctly. But, when the final query is executed, it skips the first item passed in startAt and instead starts from the next item in list.
This only happens with mLikedQuery and mSavedQuery and not with mBaseQuery.
Anybody got any idea what's happening here?
The DocumentReference you are providing to startAt is always for a document from the collection SharedConstants.COLLECTION_CLIPS:
val item = mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
This works fine for your mBaseQuery because that query is querying the documents in the SharedConstants.COLLECTION_CLIPS collection, however your mLikedQuery and mSavedQuery are querying documents from different collections so providing a DocumentReference from the SharedConstants.COLLECTION_CLIPS collection as the startAt value here doesn't make sense, the query can't start at a document that doesn't exist in the collection you're querying.
Perhaps you need to set the item you provide to startAt based on which query is being used, e.g.:
...
first?.let {
val item = when {
liked -> {
mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_USER_LIKES).document(it).get().await()
}
saved -> {
mFirestore.collection(SharedConstants.COLLECTION_USERS)
.document(Prefs.getString(SharedConstants.PREF_SERVER_USER_ID,Firebase().getCurrentUserId()))
.collection(SharedConstants.SUB_COLLECTION_SAVES).document(it).get().await()
}
else -> {
mFirestore.collection(SharedConstants.COLLECTION_CLIPS).document(it).get().await()
}
}
...

Update single value inside map in Firestore DB

I have a Collection of Units, each unit have many fields. One of those fields is A map called Settings. The settings is <String,Any>: A->true, B->false, C->"Hello" etc.
I wish to update one of them, lets say I wish to set C to "World".
My code:
suspend fun updateData(unitID: String): Boolean = suspendCoroutine { cont ->
val firestore = FirebaseFirestore.getInstance()
firestore.collection("Units").document(unitID).get().addOnCompleteListener { it1 ->
if (it1.isSuccessful) {
val settings = it1.result.get("Settings") as? HashMap<String, Any>
if (settings != null) {
settings["C"] = "World"
val map = hashMapOf<String, Any>()
map["Settings"] = settings
firestore.collection("Units").document(unitID).update(map).addOnCompleteListener { it2->
if (it2.isSuccessful) cont.resume(true)
else cont.resumeWithException(it2.exception!!)
}
}
}
else cont.resumeWithException(it1.exception!!)
}
}
What am I doing? I am getting the map, updating the value and setting it back.
My question, is that the correct approach, can I just set the value without reading the data first?
its fine to update the data without reading it. it would save your read query limit. so don't have to do that unless there is a actual need to do.
Suggestion:
Don't name it1, it2 like that. use meaning full names.
Found it:
suspend fun updateData(unitID: String): Boolean = suspendCoroutine { cont ->
val firestore = FirebaseFirestore.getInstance()
val map = mapOf("Settings.C" to "World")
firestore.collection("Units").document(unitID).update(map).addOnCompleteListener { updateData ->
if (updateData.isSuccessful) cont.resume(true)
else cont.resume(false)
}
}

Categories

Resources