step-up-labs / firebase-database-dotnet

C# library for Firebase Realtime Database.
MIT License
672 stars 168 forks source link

Listening to List is duplicating values #180

Closed bdbehrho closed 5 years ago

bdbehrho commented 5 years ago

I have an Object with a list of integers. I want to listen to the list so I can see when a value is added. The Updates come in at the right time, but every time the list receives an update, it has duplicated all of the values.

(Initial setup, list exists and has 10 values)

  1. Start listening to the list
  2. Add a value, I receive an updated list with 11 values
  3. Add another value, I receive an updated list with 23 values, the 12 were appended instead of just the 1

Example Code:

public class ParentObject {
  public List<int> Values {get; set;} = new List<int>();
}

public async void ListenAndUpdate() {
    // Assume a valid ParentObject exists with this key at collection 'parentobjects'
    var testId = "testkey";

    var parentObject = await GetSingle(testId);

    // start listening for changes to "testkey"
    ListenForChangesToValues(testId);

    for(int i = 0; i < 10; i++)
    {
        parentObject.Values.Add(1);
        await Update(testId, parentObject);
        Thread.Sleep(5000);
    }
}

public void ListenForChangesToValues(string id){
    firebaseClient
        .Child("parentobjects")
        .Child(id)
        .Child(nameof(ParentObject.Values))
        .AsObservable<List<int>>(elementRoot: nameof(ParentObject.Values))
        // The first time this comes back, the length is correct, subsequent times it has duplicated values
        .Subscribe(newValues => Console.WriteLine(newValues.Object.Count));
}

public async Task Update(string id, ParentObject item) {
    await firebaseClient
        .Child("parentobjects")
        .Child(id)
        .PutAsync<ParentObject>(item);
}

public async Task<ParentObject> GetSingle(string id){
    var model = await firebaseClient
        .Child("parentobjects")
        .Child(id)
        .OnceSingleAsync<ParentObject>();
    return model;
}

It is worth noting that the values are not being duplicated in firebase, only the returned values passed into the Subscribe Action. Seems like a bug, but possible I'm just misusing the library.

Thanks for your time! Overall a huge fan of this project.

kormemis commented 5 years ago

got same issue , subscribe function receive multiple same values even one thing changed. I expect to listen only one value by one change on the firebase

cabauman commented 5 years ago

Yeah, I can confirm this behavior with the following unit test:

[TestMethod]
public void AddedItemShouldBeAppendedToCollection()
{
    var cache = new FirebaseCache<List<long>>();
    var original = @"[1, 2, 3]";
    var incoming = @"[1, 2, 3, 4]";
    var expectation = new []
    {
        new FirebaseObject<List<long>>("updates", new List<long>() { 1, 2, 3, 4 })
    };

    cache.PushData("updates/", original).ToList();
    var entities = cache.PushData("updates/", incoming).ToList();

    entities.Should().BeEquivalentTo(expectation);
}

unittest

I initially thought this would be covered in this fix, but that's for observing an item that contains a list, while this particular use case is observing a list directly. This line in FirebaseCache.cs JsonConvert.PopulateObject(data, obj, this.serializerSettings); appends to the list instead of replacing it.

I did end up getting the correct behavior by making sure collections (anything that implements IEnumerable) are assigned via the primitiveObjSetter rather than JsonConvert.PopulateObject. I do this by adding an extra condition in the if statement:

if ((valueType.GetTypeInfo().IsPrimitive || valueType == typeof(string) || typeof(IEnumerable).IsAssignableFrom(valueType)) && primitiveObjSetter != null)

And as a side note, I don't see the purpose of this line this.dictionary[pathElements[0]] = this.dictionary[pathElements[0]]; (just assigning to itself??) so I removed that line too. I'll make a PR soon.