I've been building an app that allows the user to retrieve and delete some of his own itens registered on Firebase Database, that is my function that allows that (Thanks, Zeeshan):
override suspend fun getAllOnline(): MutableStateFlow<ResourceState<List<DocModel>>> {
auth = FirebaseAuth.getInstance()
val docList: MutableList<DocModel> = suspendCoroutine { continuation ->
database
.child(auth.currentUser!!.uid)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val docList: MutableList<DocModel> = mutableListOf()
for (docs in snapshot.children) {
val doc = docs.getValue(DocModel::class.java)
docList.add(doc!!)
}
continuation.resume(docList) << Line 34 where the error happens
}
override fun onCancelled(error: DatabaseError) {
continuation.resume(emptyList<DocModel>() as MutableList<DocModel>)
}
})
}
return if (docList.isNotEmpty()) {
MutableStateFlow(ResourceState.Success(docList))
} else {
MutableStateFlow(ResourceState.Empty())
}
}
The problem is that I'm not able to delete a file without the app crashing. The error thrown is:
2023-01-01 19:45:12.816 5637-5637/com.tods.docreminder E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.tods.docreminder, PID: 5637
java.lang.IllegalStateException: Already resumed
at kotlin.coroutines.SafeContinuation.resumeWith(SafeContinuationJvm.kt:44)
at com.tods.docreminder.feature.doc.data.repository.remote.DocFirebaseRepositoryImpl$getAllOnline$docList$1$1.onDataChange(DocFirebaseRepositoryImpl.kt:34)
at com.google.firebase.database.core.ValueEventRegistration.fireEvent(ValueEventRegistration.java:75)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7844)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
As I understood, the problem is that I'm deleting something from web and It's not updating realtime, so it's not able to display the changes on my recycler view (the item is deleted using swipe, so the UI should update it automatically to show the new result to the user).
How would I be able to implement this realtime update from this function?
Any needed code, just tell me and I'll update it asap.
Thanks for your support.
Thanks, Alex!
I used this repository as example: repository.
This is the updated result of the repository:
override suspend fun getAllOnline(): MutableStateFlow<ResourceState<List<DocModel>>> {
auth = FirebaseAuth.getInstance()
val docList = mutableListOf<DocModel>()
val docs = database.child(auth.currentUser!!.uid).get().await()
for(document in docs.children) {
val doc = document.getValue(DocModel::class.java)
docList.add(doc!!)
}
return if (docList.isNotEmpty()) {
MutableStateFlow(ResourceState.Success(docList))
} else {
MutableStateFlow(ResourceState.Empty())
}
}
Now that I'm able to delete, I'll manage the exceptions properly using the Resource State message to send the answer to the user.
Edited:
Now receiving the exception:
override suspend fun getAllOnline(): MutableStateFlow<ResourceState<List<DocModel>>> {
auth = FirebaseAuth.getInstance()
val docState: MutableStateFlow<ResourceState<List<DocModel>>>
val docList = mutableListOf<DocModel>()
docState = try {
val docs = database.child(auth.currentUser!!.uid).get().await()
for(document in docs.children) {
val doc = document.getValue(DocModel::class.java)
docList.add(doc!!)
}
if(docList.isNotEmpty()) {
MutableStateFlow(ResourceState.Success(docList))
} else {
MutableStateFlow(ResourceState.Empty())
}
} catch(e: StorageException) {
MutableStateFlow(ResourceState.Error(e.message))
}
return docState
}
And my ResourceState class if it helps:
sealed class ResourceState<T>(
val data: T? = null,
val message: String? = null
) {
class Success<T>(data: T?): ResourceState<T>(data)
class Error<T>(message: String?, data: T? = null): ResourceState<T>(data, message)
class Loading<T>: ResourceState<T>()
class Empty<T>: ResourceState<T>()
}
Hope it helps =)
the error comes from this line of code:
continuation.resume(docList) << Line 34 where the error
Coroutines are oneShot resume , so if you receive data you resume the first time, second time it will crash, Use Flow or Channel to receive multiple responses
Related
Unable to extract information from the datasnapshot received from firebase.
Currently, I am able to get the dataSnapshot from firebase, but I am having problems extracting the information from it.
In the example below I have a lobby with the code "81MUB" and inside I have a list of players (only using one player in the example). Data from FireBase
{
"81MUB": [
{
"name": "Alejandro",
"points": 0
}
]
}
Data Class
data class Player(
val name: String,
val points: Int
)
Listener
fun getCode(): String {
val index = ('A'..'Z') + ('1'..'9')
var code = ""
for (i in 0..4){
code += index[Random().nextInt(index.size)]
}
return code
}
class MviewModel : ViewModel() {
private val _Players: MutableLiveData<MutableList<Player>> =
MutableLiveData(mutableListOf<Player>(Player("Alejandro", 0)))
private var _LobbyCode: String = ""
private val dataBase = FirebaseDatabase.getInstance()
fun getPlayer(): MutableLiveData<MutableList<Player>> = _Players
fun createLobby() {
_LobbyCode = getCode()
}
fun listener() {
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
}
}
dataBase.reference.child(_LobbyCode).addValueEventListener(postListener)
}
}
Any tips?
Each time you call getCode() you are generating a new random code. When reading data, you always use the exact same code that exists in the database. So in code, it should look like this:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val name = snapshot.child("name").getValue(String::class.java)
val points = snapshot.child("points").getValue(Long::class.java)
Log.d("TAG", "$name/$points")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
The result in the logcat will be:
Alejandro/0
If you however want to map the 81MUB node into an object of type Player, then your data class should look like this:
data class Player(
val name: String? = null,
val points: Int? = null
)
And in code:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val player = snapshot.getValue(Player::class.java)
Log.d("TAG", "${player.name}/${player.points}")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
Which will produce the exact same output as above.
You might also take into consideration, using the DatabaseReference#push() method which:
Create a reference to an auto-generated child location. The child key is generated client-side and incorporates an estimate of the server's time for sorting purposes.
Instead of using your codes.
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()
}
}
}
So I use Firebase Realtime Database for messaging feature and when I send a message, the following error comes out, and the app crash. Later on, the chatting activity keeps crashing, and need to delete the message on Firebase manually to be able to reopen the chatting activity.
2022-07-19 22:56:27.078 21525-21525/com.example.gesit E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.gesit, PID: 21525
com.google.firebase.database.DatabaseException: Can't convert object of type java.lang.String to type com.example.gesit.ChatMessage
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertBean(CustomClassMapper.java:436)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.deserializeToClass(CustomClassMapper.java:232)
at com.google.firebase.database.core.utilities.encoding.CustomClassMapper.convertToCustomClass(CustomClassMapper.java:80)
at com.google.firebase.database.DataSnapshot.getValue(DataSnapshot.java:203)
at com.example.gesit.Chatting$listenforMessages$1.onChildAdded(Chatting.kt:86)
at com.google.firebase.database.core.ChildEventRegistration.fireEvent(ChildEventRegistration.java:79)
at com.google.firebase.database.core.view.DataEvent.fire(DataEvent.java:63)
at com.google.firebase.database.core.view.EventRaiser$1.run(EventRaiser.java:55)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Reading message code:
private fun listenforMessages() {
val fromId = FirebaseAuth.getInstance().uid
val toId = toUser?.uid
val ref = FirebaseDatabase.getInstance().getReference("/pesan-pengguna/$fromId/$toId")
ref.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val chatMessage = snapshot.getValue(ChatMessage::class.java)
if (chatMessage != null) {
Log.d(TAG, chatMessage?.text!!)
if (chatMessage.fromId == FirebaseAuth.getInstance().uid) {
val currentUser = MainScreen.currentUser
adapter.add(ChatItemRight(chatMessage.text, currentUser ?: return))
} else {
adapter.add(ChatItemLeft(chatMessage.text, toUser!!))
}
}
}
override fun onCancelled(error: DatabaseError) {
}
override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
}
})
}
JSON DB for messages:
{
"pesan-pengguna": {
"P6scAzLXLcOsDiChjMAXOgnBnar1": {
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7Lf8PyUg9jWKmAv6zr",
"text": "Test 1 2 3",
"timestamp": 1658237785,
"toId": "P6scAzLXLcOsDiChjMAXOgnBnar1"
}
},
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"P6scAzLXLcOsDiChjMAXOgnBnar1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7Lf8PyUg9jWKmAv6zr",
"text": "Test 1 2 3",
"timestamp": 1658237785,
"toId": "P6scAzLXLcOsDiChjMAXOgnBnar1"
},
"nrX4w334X4R4kOyXHfp6rSFPWYv1": {
"fromId": "nrX4w334X4R4kOyXHfp6rSFPWYv1",
"id": "-N7LfGGFTKerqT3Lma8Y",
"text": "Test 1 2 3",
"timestamp": 1658237818,
"toId": "nrX4w334X4R4kOyXHfp6rSFPWYv1"
}
}
}
}
Code for ChatMessage:
class ChatMessage(val id: String, val text: String, val fromId: String, val toId: String, val timestamp: Long) {
constructor() : this("","","","", -1)
}
When you're using the following reference:
val ref = FirebaseDatabase.getInstance().getReference("/pesan-pengguna/$fromId/$toId")
And when you attach a ChildEventListener on it, it means that you're trying to read all children that exist within that reference. Now, inside the onChildAdded method you're trying to convert each element into an object of type ChatMessage, which is actually not possible since the children under that node are strings, hence that error. If you want to read a ChatMessage object, please use addListenerForSingleValueEvent, as you can see in the following lines of code:
val valueEventListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val chatMessage = snapshot.getValue(ChatMessage::class.java)
Log.d("TAG", chatMessage.text)
}
override fun onCancelled(error: DatabaseError) {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
ref.addListenerForSingleValueEvent(valueEventListener)
The result in the logcat will be:
Test 1 2 3
Always remember, that Firebase Realtime Database queries work on a flat list. The objects that you want to read must be in a fixed location under each direct child node. If you keep your actual database schema, you won't be able to read the messages that belong to a particular formId. However, this topic was covered several times before, here on StackOverflow. So you should consider creating a schema where all messages exist under a direct node.
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 :)
So, my roommate and I are trying to develop an app to help students living on campus at our school keep track of their laundry. However, we are having trouble creating new laundry loads.
Our addLoad function is supposed to add a LaundryHolder object to Firebase (containing the machine number, whether it is a washer or dryer, who owns the load, and how many seconds are left for the load), whereas the LaundryLoad object contains a LaundryHolder, observer function (notifyDataSetChanged() for the LaundryLoadFragment), and timer (with time form LaundryHolder).
In Firebase, each clothingItem has a load ID with which to identify which load it is in on the user side. For our implementation to work, we need to fetch the ID which Firebase gives our LaundryHolder, which is why we are adding an onSuccessListener to a temporary query. The issue arises, however, when the query doesn't succeed or fail, and we can't figure out what is going on here.
This is the error we get:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: edu.rosehulman.roselaundrytracker, PID: 11847
kotlin.UninitializedPropertyAccessException: lateinit property load has not been initialized
at edu.rosehulman.roselaundrytracker.model.LaundryLoadViewModel.addLoad(LaundryLoadViewModel.kt:42)
at edu.rosehulman.roselaundrytracker.adapter.AddLoadAdapter.addLoad(AddLoadAdapter.kt:67)
at edu.rosehulman.roselaundrytracker.fragment.AddLoadFragment.onCreateView$lambda-1(AddLoadFragment.kt:32)
at edu.rosehulman.roselaundrytracker.fragment.AddLoadFragment.$r8$lambda$lIyFvxsLH_bCt-kHzadMjy2Ls_Y(Unknown Source:0)
at edu.rosehulman.roselaundrytracker.fragment.AddLoadFragment$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7455)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
at android.view.View.performClickInternal(View.java:7432)
at android.view.View.access$3700(View.java:835)
at android.view.View$PerformClick.run(View.java:28810)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7842)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Does anyone have any idea?
class LaundryLoadViewModel: ViewModel() {
private var loads = ArrayList<LaundryLoad>()
private var curPos = 0
lateinit var ref: CollectionReference
lateinit var uid: String
private var onlyOwned = true
private val subscriptions = HashMap<String, ListenerRegistration>()
fun getPreference() = onlyOwned
fun addLoad(machineNumber: Int, machineType: String, contents: ArrayList<ClothingItem>, time: Long, observer: () -> Unit){
val holder = LaundryHolder(machineNumber, machineType.lowercase(Locale.getDefault()) == "dryer", time * LaundryLoadFragment.SEC_TO_MIN, uid)
// val load = LaundryLoad(holder, observer)
// loads.add(load)
ref.add(holder)
lateinit var load: LaundryLoad
val query = ref
.whereEqualTo("machineNumber",machineNumber)
.whereEqualTo("owner",uid)
query.get().addOnSuccessListener { snapshot ->
snapshot.documents.forEach {
Log.d(Constants.TAG,"Retrieving load from Firebase")
load = LaundryLoad.from(it, observer)
}
}
query.get().addOnFailureListener {
Log.d(Constants.TAG,"Retrieval failed due to $it")
}
// val query = ref.whereEqualTo("machineNumber",machineNumber).whereEqualTo("dryer",machineType.lowercase(Locale.getDefault())=="dryer")
load.addMany(contents)
loads.add(load)
}
fun addListener(fragmentName: String, observer: () -> Unit) {
lateinit var subscription: ListenerRegistration
loads.clear()
val auth = Firebase.auth
val user = auth.currentUser!!
val clothes = ArrayList<ClothingItem>()
uid = user.uid
ref = Firebase.firestore.collection(LaundryLoad.COLLECTION_PATH)
val ref2 = Firebase.firestore.collection(ClothingItem.COLLECTION_PATH)
val inLoadQuery = ref2.whereNotEqualTo("load","")
inLoadQuery.addSnapshotListener { snapshot: QuerySnapshot?, error: FirebaseFirestoreException? ->
error?.let {
Log.d(Constants.TAG, "Error: $it")
return#addSnapshotListener
}
snapshot?.documents?.forEach {
clothes.add(ClothingItem.from(it))
}
}
if(onlyOwned) {
val query = ref.whereEqualTo("owner",uid)
subscription = query
.addSnapshotListener { snapshot: QuerySnapshot?, error: FirebaseFirestoreException? ->
error?.let {
Log.d(Constants.TAG, "Error: $it")
return#addSnapshotListener
}
retrieveLoads(snapshot, clothes, observer)
}
} else {
subscription = ref
.addSnapshotListener { snapshot: QuerySnapshot?, error: FirebaseFirestoreException? ->
error?.let {
Log.d(Constants.TAG, "Error: $it")
return#addSnapshotListener
}
retrieveLoads(snapshot, clothes, observer)
}
}
subscriptions[fragmentName] = subscription
observer()
}
private fun retrieveLoads(snapshot: QuerySnapshot?, clothes: ArrayList<ClothingItem>, observer: () -> Unit) {
snapshot?.documents?.forEach {
loads.add(LaundryLoad.from(it, observer))
}
for (load in loads) {
for (item in clothes) {
if (item.load == load.getId()) {
load.addToLoad(item)
}
}
}
}
fun removeListener(fragmentName: String) {
for(load in loads) {
ref.document(load.getId()).set(load.laundryHolder)
}
subscriptions[fragmentName]?.remove()
subscriptions.remove(fragmentName)
}
fun togglePreference() {
onlyOwned = !onlyOwned
}
}
It looks like ref has not been initialized when you ref.add(holder) in addLoad. It's impossible for us to say why that is, as the code that calls addLoad seems to be missing, but the stack trace should point you pretty directly to where the problem is.