firebase / firebase-unity-sdk

The Firebase SDK for Unity
http://firebase.google.com
Apache License 2.0
215 stars 35 forks source link

Remove Listeners not working #371

Open LukePenkava opened 4 years ago

LukePenkava commented 4 years ago

Please fill in the following fields:

Unity editor version: 2019.3 Unity Firebase SDK: 6.9.0 Firebase plugins in use: Database Platform you are using the Unity editor on: Mac Platform you are targeting: iOS, Android

Please describe the issue here:

I have basic listeners getting data from database. Looking like this

  void StartListener () {

        FirebaseDatabase.DefaultInstance
            .GetReference ("Main")
            .ValueChanged += ListenForServerTimestamp;
    }

I am removing them here

    void OnDisable () {

        FirebaseDatabase.DefaultInstance
            .GetReference ("Main")
            .ValueChanged -= ListenForServerTimestamp;
    }

The issue is that removing is not working. Actualy removing listeners like this is adding them again instead of removing them. When i run the code like this and go to next scene and back i receive 3 calls. When i remove the OnDisable, i get the call only 2 times. Any idea why removing listeners is not working? I am not sure if its affected by the fact that i am loading other scene and then going back.

Also, only way i am able to disable the listeners to constantly fire even when the editor is not playing is by forcing Firebase to go completely offline with this

FirebaseDatabase.DefaultInstance.GoOffline ();

I realy dont know how else to handle it. I could not find anything on how to properly handle listeners in the samples. It would be great if there was some example, how to add/remove listeners over multiple scenes. I also did not find any example how to work with Firebase in Unity in general over multiple scenes. It seems the example always consider only one scene, while Firebase seems to be very persistent over all scenes and even when the app is disabled ( or editor is turned off ).

Thanks

google-oss-bot commented 4 years ago

This issue does not seem to follow the issue template. Make sure you provide all the required information.

paulinon commented 4 years ago

Thanks for reporting this issue @LukePenkava. Could you share a minimal, reproducible example of your project so I could identify how to resolve your issue?

LukePenkava commented 4 years ago

@paulinon Thanks for getting back to me so quickly. Here are steps to reproduce minimal example

1) Create new Unity project with Unity version 2019.3 2) Switch platform to iOS 3) Set api in player settings to NET 4.x 4) Import Firebase SDK 6.9.0, import Database package only ( Edit: Net4 ) 5) Create two scenes in Unity with simple button to load scene2 from scene1 and button to load scene1 from scene2 ( just be able to go back and forth between scenes while editor is in play mode ) 6) In Scene1 add this code and have basic FB Database setup, its enough to have node called "Main" and some value in it like timestamp for example. You can then change this value to different number to check if listeners are working.

using System;
using System.Collections;
using System.Collections.Generic;
using Firebase;
using Firebase.Database;
using Firebase.Extensions;
using Firebase.Unity.Editor;
using UnityEngine;
using UnityEngine.UI;

public class ServerManager : MonoBehaviour {   
    string dbUrl = "Your DB URL";    
    DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;

    void Start () {    
        FirebaseApp.CheckAndFixDependenciesAsync ().ContinueWithOnMainThread (task => {   
            dependencyStatus = task.Result;
            if (dependencyStatus == DependencyStatus.Available) {                
                InitializeFirebase ();
            } else {
                Debug.LogError (
                    "Could not resolve all Firebase dependencies: " + dependencyStatus);
            }
        });        
    }

    void OnDisable () {
        Debug.Log("Removing Listeners");        
        RemoveListeners();          
    }

    public void RemoveListeners() {
        FirebaseDatabase.DefaultInstance
            .GetReference("Missions")
            .ValueChanged -= ListenForMissionObjects;

        FirebaseDatabase.DefaultInstance
            .GetReference("Main")
            .ValueChanged -= ListenForServerTimestamp;
    }

    void InitializeFirebase () {
        Debug.Log("Server Initialize");

        FirebaseApp app = FirebaseApp.DefaultInstance;
        app.SetEditorDatabaseUrl (dbUrl);

        if (app.Options.DatabaseUrl != null) {
            app.SetEditorDatabaseUrl (app.Options.DatabaseUrl);           
        }  

        StartListener ();
    }

    void StartListener () {
        Debug.Log("Start Listener");

        FirebaseDatabase.DefaultInstance
            .GetReference("Missions")
            .ValueChanged += ListenForMissionObjects;

        FirebaseDatabase.DefaultInstance
            .GetReference("Main")
            .ValueChanged += ListenForServerTimestamp;
    }

    void ListenForMissionObjects (object sender, ValueChangedEventArgs args) {
        if (args.DatabaseError != null) {
            Debug.LogError (args.DatabaseError.Message);
            return;
        }
        Debug.Log ("Mission Data Received");       
    }

    void ListenForServerTimestamp (object sender, ValueChangedEventArgs args) {
        if (args.DatabaseError != null) {
            Debug.LogError (args.DatabaseError.Message);
            return;
        }
        Debug.Log("Listen Server Time");       
    }   
}

7) Enter play mode. In Scene1, everything should work fine. if you manualy update timestamp in the DB, listener should get update and print "Listen Server Time" for example. Now go with the button to scene2 and back to scene1. First the listeners should fire once as they are registered again, all good. Once you update your db manually, they will fire twice instead of one time. If you go again to scene2 and back to scene1, they will fire 3 times, instead of 1 time and so on. Every time you go to scene2 and back, additional call is added and no listeners are removed as it seems.

8) Second issue is that when you turn off the play mode, the events will still keep firing. IE they were not removed in Disable method and are firing eventho editor is not in play mode. So it seems there is no way of disabling the listeners. Its probably same issue as first one, which is causing the second issue with events firing eventho editor is not in play mode.

Thanks for looking into it.

LukePenkava commented 4 years ago

@paulinon I got a solution from other user on StackOverflow. The continuous adding of listeners for each visit of other scene got fixed by setting reference to the variable and then using only the variable for listeners.

  DatabaseReference DBRef_Main;
  DBRef_Main = FirebaseDatabase.DefaultInstance.GetReference("Main");

I have one more related issue tho, which i am not sure what to do about. Now there is always one call per listener except for one case. When i setup listeners in Scene1 ( as in previous message code ), then go to Scene2 and update the DB with some value and go back to Scene1, now it fires the listener function twice. Only at this scenario. If i update the DB now again, back in Scene1 ( without turning off the editor ), it gets called only once. It seems like if the initialization happens and then on top of it the DB update is handled?

paulinon commented 4 years ago

Hi @LukePenkava. I can't seem to figure out how to introduce the change you made from Stack Overflow without encountering issues. Could you send your updated code (or an MCVE of your project) so I can reproduce your issue? Thanks!

patm1987 commented 4 years ago

Hi,

If you're referring to this Stack Overflow question, I'm the person who gave the current answer! Thanks for following up here, it's a little easier (I think) to share code in a thread in this context.

I already made sure to file a bug for your first issue. As with all things, I can't make any estimates on when the fix will go through, but it's good to know that the workaround is working so far.

For this second issue you're experiencing, I know it's hard to reproduce in the quickstart since it's a single scene project. If you could give us one or two standalone MonoBehaviours and describe how to configure the quickstart to reproduce the the exact issue you're seeing, it will help a great deal in making sure that we see the same issue you are and ensuring that the engineers can validate any fixes.

Also, I'd be curious if you were running into this behavior: https://firebase.google.com/docs/database/unity/retrieve-data#value-events

This event is triggered once when the listener is attached and again every time the data, including children, changes.

Does it sound like you might be getting two calls because of an (expected) server sync or does it definitely seem bug-like to you?

LukePenkava commented 4 years ago

@patm1987 Hi, thanks again for the answer on SO. Essentially these two things solved both issues for me.

1) Not being able to detach listeners was solved by storing references to the DB in variables as you suggested:

//Had to store in variable otherwise removing listeners does not work
DBRef_Main = FirebaseDatabase.DefaultInstance.GetReference("Main");
DBRef_Missions = FirebaseDatabase.DefaultInstance.GetReference("Missions");

2) When reattaching listeners coming back to the first scene from second scene ( scene1 attaches listeners, removes them when going to scene2 ), i had to add KeepSynced, otherwise previous data were always logged as well as new data. Ie i would get two updates, one with old data and one with current data and i wanted to receive only new data.

DBRef_Main.KeepSynced(true);
DBRef_Missions.KeepSynced(true);

Hope this help someone else in the future.

AnnMic commented 2 years ago

Resurfacing this really old post, is this issue going to be fixed? I am now on version 9.0.0 and this issue seems to still exist, also the docs are not updated stating you have to store the db reference to make any child listener work. Very annoying to have to store every db reference to be able remove listeners.

Here is an example of what works/ not works:


                //Not working
                teamDb = FirebaseDatabase.DefaultInstance.GetReference("teams");
                teamDb.Child("teamName").Child("players").ChildChanged += PlayerChildChanged;
                teamDb.Child("teamName").Child("players").ChildChanged -= PlayerChildChanged;

                //Not working
                teamDb.Child("teamName").ChildChanged += PlayerChildChanged;
                teamDb.Child("teamName").ChildChanged -= PlayerChildChanged;

                //Works
                teamNameDb = FirebaseDatabase.DefaultInstance.GetReference("teams").Child("teamName");
                teamNameDb.ChildChanged += PlayerChildChanged;
                teamNameDb.ChildChanged -= PlayerChildChanged;

                //Works
                playerDb = teamDb.Child("teamName").Child("players");
                playerDb.ChildChanged += PlayerChildChanged;
                playerDb.ChildChanged -= PlayerChildChanged;
ghost commented 2 years ago

I also ran into this weird problem - only if you store the DatabaseReference into a variable, you can actually unsubscribe from the event. Otherwise the unsubscription does not actually unsubscribe and changes in the database path will keep firing the event handler.

Will this be fixed at some point? Just follow your own docs to create MCVE =)

Im a also on version 9.0.0.

ghost commented 2 years ago

@cynthiajoan any progress on this? I would guess no, since this ticked is not assigned to nobody. It is a bit annoying to have to code extra workaround mechanism because of this bug..

Awais6 commented 11 months ago

This wasted my 2 days

hiro094 commented 7 months ago

I am currently facing the exact same situation myself. It seems to be a very common issue where unregistering functions through the Child function from saved variables is not possible.