I'm having a Firebase Handle like this:
private var typeIndicatorHandle: DatabaseHandle?
self.typeIndicatorHandle = self.dbRef.child("chats").child(chatId).child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { (snapshot) in
print("new value")
})
somewhere else I do this:
if let typeIndicatorHandle = self.typeIndicatorHandle {
self.dbRef.removeObserver(withHandle: typeIndicatorHandle)
}
Now the problem is the observer still gets new values. How is that possible?
You need to remove the observer on original children where you attached it.
For Example:
private var typeIndicatorHandle: DatabaseHandle?
private var dbRef:DatabaseReference?
self.childRef= self.dbRef.child("chats").child(chatId).child("typingIndicator").queryOrderedByValue().queryEqual(toValue: true)
self.typeIndicatorHandle = childRef.observe(.value, with: { (snapshot) in
print("new value")
})
To Remove the listener:
if let typeIndicatorHandle = self.typeIndicatorHandle {
self.childRef.removeObserver(withHandle: typeIndicatorHandle)
}
Sorry for my bad syntax. I don't know swift that much in case of any correctness correct it.
But you need to remove the listener on the DatabaseReference on which you have added listener.
Related
firebase method is working on worker thread automatically. but I have used coroutine and callbackflow to implement firebase listener code synchronously or get return from the listener.
below is my code that I explained
coroutine await with firebase for one shot
override suspend fun checkNickName(nickName: String): Results<Int> {
lateinit var result : Results<Int>
fireStore.collection("database")
.document("user")
.get()
.addOnCompleteListener { document ->
if (document.isSuccessful) {
val list = document.result.data?.get("nickNameList") as List<String>
if (list.contains(nickName))
result = Results.Exist(1)
else
result = Results.No(0)
//document.getResult().get("nickNameList")
}
else {
}
}.await()
return result
}
callbackflow with firebase listener
override fun getOwnUser(): Flow<UserEntity> = callbackFlow{
val document = fireStore.collection("database/user/userList/")
.document("test!!!!!")
val subscription = document.addSnapshotListener { snapshot,_ ->
if (snapshot!!.exists()) {
val ownUser = snapshot.toObject<UserEntity>()
if (ownUser != null) {
trySend(ownUser)
}
}
}
awaitClose { subscription.remove() }
}
so I really wonder these way is good or bad practice and its reason
Do not combine addOnCompleteListener with coroutines await(). There is no guarantee that the listener gets called before or after await(), so it is possible the code in the listener won't be called until after the whole suspend function returns. Also, one of the major reasons to use coroutines in the first place is to avoid using callbacks. So your first function should look like:
override suspend fun checkNickName(nickName: String): Results<Int> {
try {
val userList = fireStore.collection("database")
.document("user")
.get()
.await()
.get("nickNameList") as List<String>
return if (userList.contains(nickName)) Results.Exist(1) else Results.No(0)
} catch (e: Exception) {
// return a failure result here
}
}
Your use of callbackFlow looks fine, except you should add a buffer() call to the flow you're returning so you can specify how to handle backpressure. However, it's possible you will want to handle that downstream instead.
override fun getOwnUser(): Flow<UserEntity> = callbackFlow {
//...
}.buffer(/* Customize backpressure behavior here */)
This is my database:
I want to keep fetching 2 document in the list on click of a button, I tried this code, but it didn't work. Like on
1st click, fetch doc_0 and doc_1,
2nd click, fetch doc_2 and doc_3, so forth and so on.
class _FetchState extends State<Fetch> {
final List<DocumentSnapshot> _list = [];
DocumentSnapshot _lastDoc;
void _fetch() async {
Firestore.instance.collection("collection")
.limit(2)
.startAtDocument(_lastDoc)
.snapshots()
.listen((snapshot) {
_list.addAll(snapshot.documents);
_lastDoc = _list.last;
print("length = ${_list.length}");
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: _fetch),
);
}
}
I think this could be solved with only two very simple steps without changing much of your code,
Add a field to your doc to sort items with( a string, int or a timestamp).
Then in your own code just add orderBy(yourValue).
Thats it !
What you're looking for is called Firestore Pagination in Flutter.
Basically you need to call the first page without the startAtDocument once, then save the last_document_reference, then start using startAtDocument fore all other pages with updating the last_document_reference. You can see an example here
EDIT: Here is part of your code modified to what you need:
void _fetch() async {
if(_lastDoc == null) {
Firestore.instance.collection("collection")
.orderBy("name") //you can change the key "name" to whatever field key you have
.limit(2)
.snapshots()
.listen((snapshot) {
_list.addAll(snapshot.documents);
_lastDoc = _list.last;
print("length = ${_list.length}");
});
}
else {
Firestore.instance.collection("collection")
.orderBy("name") //you can change the key "name" to whatever field key you have
.startAtDocument(_lastDoc)
.limit(2)
.snapshots()
.listen((snapshot) {
_list.addAll(snapshot.documents);
_lastDoc = _list.last;
print("length = ${_list.length}");
});
}
}
Note 1: You have to manage those listeners! With every call you'll be creating new listeners which you should some how manage. It's out of scope to talk about it here. And in case of documents being updated you have to manage the trigger accordingly.
Note 2: You should be using setState((){ _list.addAll(snapshot.documents) }); to update the build (or use other means to do so).
Edit 2:
Thinking about it, you only need a listener on the new messages, but the old ones should be fixed data without any modifications (no edits or deletes); So here the second part without any listeners:
else {
Firestore.instance.collection("collection")
.orderBy("name") //you can change the key "name" to whatever field key you have
.startAtDocument(_lastDoc)
.limit(2)
.getDocuments()
.then((snapshot) {
_list.addAll(snapshot.documents);
_lastDoc = _list.last;
print("length = ${_list.length}");
});
}
I haven't tested the code, but it should be something like that.
Now you only have one listener which should update with the new documents. You might still need to handle the changes.
I'm not used to firestore documents and all. But just in case you would like to add some more code this might help.
Initialize a variable button_clicks = 0;
When the button is clicked, try to fetch the file with name file_0 and then increasing the value of our variable, fetch the file_1.
Something like:
// on button click:
{
fetch_file('file_'+button_clicks); //Function which fetches file_0.. The firestore instance code..
button_clicks++ ; //Increment in variable.
fetch_file('file_'+button_clicks); //Function which fetches file_1
button_clicks++ ; //Increment in variable.
}
Now the next time you click the button , the same thing will start from file_2 because your variable button_clicks = 2.
I would strongly suggest to store this value somewhere in firebase-realtime-database separate for each user or as per your needs if you want the user to download from file_0 on app restart.
This logic should help. :-)
I'm trying to optimise the performances in my app and I noticed that I do not remove Firestore listeners from my repository.
My repository has a number of functions that return a LiveData, that is then observed via Transformations from ViewModels and then the views.
One-time operations work absolutely fine (upload, delete etc.) but permanent listeners don't get garbage collected when the activity finishes.
Right now the function inside the repository looks like this:
// [...]
class Repository {
// [...]
fun retrieveCode() {
val observable = MutableLiveData<Code>()
val reference =
FirebaseFirestore.getInstance().collection(/**/).document(/**/)
reference
.addSnapshotListener { snapshot, exception ->
if(exception != null) {
observable.value = null
}
if(snapshot != null {
observable.value = snapshot.//[convert to object]
}
}
return observable
}
}
I found a workaround which is to create a custom LiveData object that handles the listener removal when it becomes inactive, like this:
class CodeLiveData(private val reference: DocumentReference):
LiveData<Code>(), EventListener<DocumentSnapshot>{
private var registration: ListenerRegistration? = null
override fun onEvent(snapshot: DocumentSnapshot?,
exception: FirebaseFirestoreException?) {
if(exception != null) {
this.value = null
}
if(snapshot != null) {
this.value = snapshot.//[convert to object]
}
}
override fun onActive() {
super.onActive()
registration = reference.addSnapshotListener(this)
}
override fun onInactive() {
super.onInactive()
registration?.remove()
}
}
Is there a way to solve this problem without creating a custom class, but rather by improving a function similar to the first example?
Thanks,
Emilio
There are two ways in which you can achieve this. The first one would be to stop listening for changes and this can be done in your onStop() function by calling remove() function on your ListenerRegistration object like this:
if (registration != null) {
registration.remove();
}
The approach would be to you pass your activity as the first argument in the addSnapshotListener() function, so Firestore can clean up the listeners automatically when the activity is stopped.
var registration = dataDocumentReference
.addSnapshotListener(yourActivity, listener)
I have a small application where users can upvote or downvote players based on their recent performance in various sports.
When the upvote button is currently clicked, the UID of the voter (logged in via Google) is supposed to get pushed into the database of the corresponding player who was voted on. Like this:
However, instead, it's currently doing this:
Here is the code which is doing the Firebase work. I believe it is one of these lines that I have wrong.
this.database.child(playerId).transaction
or
ref = firebase.database().ref('players/voters');
The code:
this.database = firebase.database().ref().child('players');
upvotePlayer(playerId) {
this.state.user ?
this.database.child(playerId).transaction(function (player) {
if (player) {
console.log("UID: " + uid)
var ref = firebase.database().ref('players/voters');
ref.child(uid).set(1);
}
return player;
})
:
console.log("Must be logged in to vote.")
}
you're storing the votes in this path players/voters while it should be in this path players/$playerId/voters
checkout this
this.database = firebase.database().ref().child('players');
upvotePlayer(playerId) {
this.state.user ?
this.database.child(playerId).transaction(function (player) {
if (player) {
console.log("UID: " + uid)
var ref = firebase.database().ref('players/' + playerId + '/voters');
ref.child(uid).set(1);
}
return player;
})
:
console.log("Must be logged in to vote.")
}
transaction method have transactionUpdate function as argument which should return the new value and you didn't return any , also I think better solution is not use transaction
Edit : solution without transaction
upvotePlayer(playerId)
{
if(this.state.user)
{
let ref = firebase.database().ref('players/' + playerId + '/voters');
ref.child(uid).set(1);
}
else
{
console.log("Must be logged in to vote.")
}
}
The problem is because of this:
var ref = firebase.database().ref('players/voters');
it is adding the voters under the players node and not under the push id node.
So you have to retrieve the pushid from the database and then do this:
var ref=firebase.database().ref().child('players').child(playerId).child('voters');
so the voters will become one of the pushid child.
For Future Viewers:
It is better to use transactions since:
Using a transaction prevents upvote counts from being incorrect if multiple users upvote the same post at the same time or the client had stale data
Actually you are pushing the value to payers node with player id. you have push the data to child node of players id voters. Try this code:-
this.database = firebase.database().ref().child('players');
upvotePlayer(playerId) {
this.state.user ?
this.database.child(playerId).transaction(function (player) {
if (player) {
console.log("UID: " + uid)
player.ref.child(voters).push(uid);
}
return player;
})
console.log("Must be logged in to vote.")
}
Problem is your are actually taking the reference of players node and creating new node at same level of playerid named voters. Below line just create a new node voters at same level to playerid.
var ref = firebase.database().ref('players/voters');
ref.child(uid).set(1);
as you can see there is no node named voters in players root node. But you are setting the value so it is been created with new data using set() function
I have several charts built with dc.js. I can achieve the desired functionality by attaching a callback to each dc.js chart's .on("filterted", function(chart) {}) but this is annoying because I have to attach the same callback to each chart. And error prone because as new charts are added, someone has to remember to attach an event hander. I would prefer to just attach a callback to the underlying crossfilter. Is that possible?
Is there a way to optimize this...
var ndx = crossfilter(data);
var dimAlpha = ndx.dimension(function(d) {return d.alpha});
var dimBeta = ndx.dimension(function(d) {return d.beta});
var groupAlpha = dimAlpha.group().reduceSum(function(d) {return 1;});
var groupBeta = dimBeta.group().reduceSum(function(d) {return 1;});
dc.pieChart(myDomId1)
.dimension(dimAlpha)
.group(groupAlpha)
.on("filtered", function(chart) {
//do stuff
});
dc.pieChart(myDomId2)
.dimension(dimBeta)
.group(groupBeta)
.on("filtered", function(chart) {
//do stuff
});
into something like this...
var ndx = crossfilter(data);
var dimAlpha = ndx.dimension(function(d) {return d.alpha});
var dimBeta = ndx.dimension(function(d) {return d.beta});
var groupAlpha = dimAlpha.group().reduceSum(function(d) {return 1;});
var groupBeta = dimBeta.group().reduceSum(function(d) {return 1;});
dc.pieChart(myDomId1)
.dimension(dimAlpha)
.group(groupAlpha);
dc.pieChart(myDomId2)
.dimension(dimBeta)
.group(groupBeta);
ndx.on("filtered", function() {
//do stuff
})
If you've got a million charts and don't want to have to attach the event listener to each one manually, you could iterate through the chart registry and add them that way. Ex:
dc.chartRegistry.list().forEach(function(chart) {
chart.on('filtered', function() {
// your event listener code goes here.
});
});
Note that this code must go after the charts have instantiated to work.
In the absence of a way to attach the callback once globally, one thing you could do to mitigate the risk from duplicate code is to define the callback function once and pass in a reference instead of defining it inline on each chart.
function my_func() {
// do stuff
}
dc.pieChart(myDomId2)
.dimension(dimBeta)
.group(groupBeta)
.on("filtered", my_func);
chart and filter can also be passed to the filter function something like:
function my_func(chart,filter) {
// do stuff
}
dc.pieChart(myDomId2)
.dimension(dimBeta)
.group(groupBeta)
.on("filtered", my_func);