5

I'm looking at using firebase as a data store for user data for a web app. My current thought is to store each user's data using the timestamp of when they joined as the key referencing that user's data. The advantage of this scheme is that it's a simple way to assign unique integer ids to users, and makes chronological sorting of users simple.

A downside, however, is that if two add user requests are submitted with identical data, the app will happily add two separate entries, which is unideal. I could shuffle things around (I'm starting to think I should use email as the key and prioritize by join data, rather than my current scheme), but suppose I don't want to. Is there any way to prevent duplicate data?

The naive approach would probably be just to do something like:

if(!searchFirebaseForUser(data)) {
    addUser(data);
}

But this is definitely a race condition; it'd be easy for two requests to both query and find no user in the database, and both add. I'd like to do this in a transaction, but it doesn't seem like the Firebase transaction support covers this case. Is there any way to handle this?

Ashish
  • 6,791
  • 3
  • 26
  • 48
Retsam
  • 30,909
  • 11
  • 68
  • 90

4 Answers4

10

You will probably have to use the username or email address as a key, and try to atomically write to that location.

Here is the relevant code sample from the transaction function reference. In this case, we use wilma as the key for the user.

// Try to create a user for wilma, but only if the user id 'wilma' isn't already taken.
var wilmaRef = new Firebase('https://SampleChat.firebaseIO-demo.com/users/wilma');
wilmaRef.transaction(function(currentData) {
  if (currentData === null) {
    return {name: {first: 'Wilma', last: 'Flintstone'} };
  } else {
    console.log('User wilma already exists.');
    return; // Abort the transaction.
  }
}, function(error, committed, snapshot) {
  if (error)
    console.log('Transaction failed abnormally!', error);
  else if (!committed)
    console.log('We aborted the transaction (because wilma already exists).');
  else
    console.log('User wilma added!');
  console.log('Wilma\'s data: ', snapshot.val());
});
Gastón Saillén
  • 12,319
  • 5
  • 67
  • 77
Jo Liss
  • 30,333
  • 19
  • 121
  • 170
4

Are Security Rules not sufficient to enforce uniqueness? I have no idea if they are atomic or not.

{
    "rules": {
        "users": {
            "$username": {
                ".write": "!data.exists()"
            }
        }
    }
}
Robert Payne
  • 278
  • 2
  • 6
  • This worked to keep me from writing duplicate records whenever a user logs in (which I made to create a record in the user table whenver the user manually clicks the login button on my app). I followed this page in the Firebase documentation on how to create separate user tables if you're not using custom login (Facebook, in my case): https://www.firebase.com/docs/web/guide/user-auth.html#section-storing – HaulinOats May 04 '16 at 14:57
  • Doesn't work. What this code does is avoid writing inside username if there's already something written. – Hugo Passos Oct 20 '17 at 18:09
1

You can use push to automatically generate chronologically incremental IDs that won't conflict with other clients even if they're created at the same time (they have a random component in them).

For example:

var ref = new Firebase(URL);
var record = ref.push(userInfo);
console.log("User was assigned ID: " + record.name());
Anant
  • 7,408
  • 1
  • 30
  • 30
  • 1
    Two things here: I don't think this will solve my problem in case the same user gets added twice. My problem is that I don't want two identical entries that only differ by ID. – Retsam Sep 03 '13 at 19:04
  • On a side-note, the reason I avoided using push is because it gives non-numeric IDs, since this has to play nicely with other components which will be assuming user ID is numeric. – Retsam Sep 03 '13 at 19:05
  • 1
    Ah, got it. You'll need to use .transaction() to assign unique IDs. There's an example here: https://gist.github.com/anantn/4323981 – Anant Sep 03 '13 at 19:29
-2

instead of defining the rule in fire-base database the easiest way to prevent duplicate entries is first of all get all the data from the fire-base database and compare it with the data(new Data) you want to store,if it is matched with previous data then discard storing in the database again otherwise store in database.check below for more clarity.

public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private BroadcastReceiver mRegistrationBroadcastReceiver;
private TextView txtRegId, txtMessage;
DatabaseReference databaseArtists;
ListView listViewArtists;
public static String regId;
List<Artist> artistList;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
txtRegId = (TextView) findViewById(R.id.regid);
txtRegId.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            displayFirebaseRegId();
            boolean flag=false;
            String tokenId=regId;
            for(Artist a:artistList)
            {Log.d("RAaz",a.getTokenId()+"    "+tokenId);
                if(a.getTokenId().equalsIgnoreCase(tokenId))
                {
                    flag=true;
                    Toast.makeText(MainActivity.this, "True", Toast.LENGTH_SHORT).show();
                }
            }
            if(flag)
            {
                Toast.makeText(MainActivity.this, "User Already Exists", Toast.LENGTH_SHORT).show();
            }
            else {
                addArtist();
            }
        }
    });
    mRegistrationBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // checking for type intent filter
            if (intent.getAction().equals(Config.REGISTRATION_COMPLETE)) {
                // gcm successfully registered
                // now subscribe to `global` topic to receive app wide notifications
                FirebaseMessaging.getInstance().subscribeToTopic(Config.TOPIC_GLOBAL);
                displayFirebaseRegId();
            } else if (intent.getAction().equals(Config.PUSH_NOTIFICATION)) {
                // new push notification is received
                String message = intent.getStringExtra("message");
                Toast.makeText(getApplicationContext(), "Push notification: " + message, Toast.LENGTH_LONG).show();
                txtMessage.setText(message);
            }
        }
    };
    displayFirebaseRegId();
    databaseArtists = FirebaseDatabase.getInstance().getReference("artist");
    artistList = new ArrayList<>();}

Below code is for adding data to the firebase

private void addArtist() {
    String name = "User";
    String genre = regId;
    if (!TextUtils.isEmpty(name)) {
        String id = databaseArtists.push().getKey();
        Artist artist = new Artist(id,genre,name);
        databaseArtists.child(id).setValue(artist);
        Toast.makeText(this, "Artist Added", Toast.LENGTH_SHORT).show();
    } else {
        Toast.makeText(this, "Please enter name", Toast.LENGTH_SHORT).show();
    }
}

use onStart to get the details from firebase database

protected void onStart() {
    super.onStart();
    Toast.makeText(this, "On Start", Toast.LENGTH_SHORT).show();
    databaseArtists.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            artistList.clear();
            for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
                Artist artist = dataSnapshot1.getValue(Artist.class);
                artistList.add(artist);
            }
        }
        @Override
        public void onCancelled(DatabaseError databaseError) {
        }
    });
}

finally add the pojo class

public class Artist {
private String artistId;
private String tokenId;
private String roleName;

public Artist() {
}

public Artist(String artistId, String tokenId, String roleName) {
    this.artistId = artistId;
    this.tokenId = tokenId;
    this.roleName = roleName;
}

public String getArtistId() {
    return artistId;
}

public void setArtistId(String artistId) {
    this.artistId = artistId;
}

public String getTokenId() {
    return tokenId;
}

public void setTokenId(String tokenId) {
    this.tokenId = tokenId;
}

public String getRoleName() {
    return roleName;
}

public void setRoleName(String roleName) {
    this.roleName = roleName;
}

}

  • 1
    The problem with this answer is that the check and insert operations are not atomic - a race condition exists when 2 users decide to choose the same name at roughly the same time. Assuming that both users execute the check //if(a.getTokenId().equalsIgnoreCase(tokenId))// at the same time, it will return false for both users. Because neither user has inserted their tokenId to the data store yet. As a result, the insert logic for both users will be executed and the last person to perform the insert will win. – Kevin Ortman Aug 03 '17 at 02:33