Pairing in two player game with firebase in android? - android

I am trying to implement a two player game in android using firebase(for realtime pairing).
On firebase, i have set up a node representing active players. And on each client i have a childEventListener to listen to any changes on the players node.
Suppose
Initially there is only one player (Player A).
Then 3 more players(B,C and D) got added (At the SAME TIMESTAMP).
Then, on client side
1. Each of 4 players will get notified about the changes in the node through childEventListener.
Now, What i want to achieve is "UNIQUE PAIRING" i.e I should be able to generate 2 pairs from these 4 players. (Of course, one player can't be in both the pairs)
I have written code to pair two players in a transaction block so that no two players get paired with the same player.
private void attachActivePlayersEventListener() {
Log.i(TAG, "attachActivePlayersEventListener");
if (activePlayersEventListener == null) {
activePlayersEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
searchPlayer(dataSnapshot);
Log.i("PLAYER_ADDED ", dataSnapshot.getValue(Player.class).getName());
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
searchPlayer(dataSnapshot);
Log.i("PLAYER_CHANGED ", dataSnapshot.getValue(Player.class).getName());
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
mActivePlayersDbRef.addChildEventListener(activePlayersEventListener);
}
}
private void searchPlayer(DataSnapshot dataSnapshot) {
if(mPlayer.getState().equals(PlayerState.ACTIVE)) { // if the current user is active
final String oppKey = dataSnapshot.getKey();
Player oppPlayer = dataSnapshot.getValue(Player.class);
if (oppPlayer.getState().equals(PlayerState.ACTIVE)
&& !oppKey.equals(pushId)) { // if the opponent chosen is not the current user
DatabaseReference oppRef = mActivePlayersDbRef.child(oppKey);
oppRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Player player2 = mutableData.getValue(Player.class);
if (player2 == null) {
return Transaction.success(mutableData);
}
mActivePlayersDbRef.child(pushId).child("state").setValue(PlayerState.PLAYING);
mActivePlayersDbRef.child(oppKey).child("state").setValue(PlayerState.PLAYING);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) {
Log.d("PAIRING", "pairing:onComplete:" + databaseError);
}
});
}
}
}
My DOUBT is
Is this the correct way of pairing?
What happens when PlayerA tries to pair up with PlayerB, PlayerB tries to pair up with PlayerC and PlayerC tries to pair up with PlayerD and finally PlayerD tries to pair up with PlayerA(In a CYCLIC way). How to make sure this case doesn't happen?
Note that in Doubt2, I am not taking care of the condition where two players try to pair up with the same player. (As transaction block will ensure that it won't happen, I guess :/ ).

What you are trying to do is difficult to do correctly and safely. It's better to let a backend service do this matching so that the clients don't all have to figure out how to agree with each other somehow.
You can use Cloud Functions for Firebase to write a database trigger that responds to changes in your database. One strategy would be for clients to push data into a location in the database to indicate their intent to be matched. Then, when a function triggers on those writes, it can check to see if there are other suitable players to be matched, and write more data into the database to set up the game. The clients will also need a way to listen for the game starting up after they have been matched. This is still all very much non-trivial, but a lot easier than putting the logic in the clients.

Related

Update other nodes when the transaction is completed

I'm learning Firebase in android and I'm trying to make a room system with their respective slots. As in this case several users may want to access the same slot at the same time I decided to use Firebase transactions.
So when the user tries to log in to the slot I do:
mySlotRef1.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData mutableData) {
Slot p = mutableData.getValue(Slot.class);
if (p == null) {
return Transaction.success(mutableData);
}
if (p.getState().equals("closed")) {
return Transaction.abort();
}
// Set value and report transaction success
Slot sl1 = new Slot("slot1", idRoom, auth.getCurrentUser().getDisplayName(), auth.getCurrentUser().getUid(), "closed", Profile.getCurrentProfile().getProfilePictureUri(200, 200).toString());
mutableData.setValue(sl1);
return Transaction.success(mutableData);
}
#Override
public void onComplete(DatabaseError databaseError, boolean b,
DataSnapshot dataSnapshot) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
}
});
I think this works, i. e. when the slot is closed it only enters one user and rejects the others in the "Slot" ref. But the problem is that I also need to update two more values in the "User" directory and I can't find a way to do it "only when the user occupied the slot", that is, when the transaction was completed.
UPDATE:
This is the part where I check to see if the slot is open.
mySlotRef1.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Slot data = dataSnapshot.getValue(Slot.class);
state = data.getState();
if (state.equals("open")) {
saveSlot(slot);
} else {
Toast.makeText(getApplicationContext(), "Slot not available", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
This not how things are working when it comes to concurent writes. None of your slots will be closed. When you are using transactions, it means that every write that will be made in the same time by different users will be made in different thread of execution. So using transactions, it doesn't matter if two or many other users will make a write operation in the same time, you'll have a correct result.
If you want to update otrher fields within another class, just put your logic inside onComplete() method. This method is triggered once the transaction is complete.

android - instant notification from another mobile phone. how?

hi i have this awesome app that can work both for clients and business owners, so lets say a business owner is connected on one end and a client on another , the client can place an order of some kind (not revealing too much :D ) and what i want is the business owner to know the client just placed a new order, considering that both have the application. can someone point me on how to ?
i am working with Firebase Real Time Database i handle it pretty good , but what happens when the app is closed and the client just placed an order , it needs to pop up on the business owner side , i haven't tried notifications yet but i suppose its not to hard to understand i just want the business owner app to be triggered when an order is placed.
Thank you for your time.
You can use a background service, I actually did something like you need with Firebase, we need to inform some users that other user pressed a panic button. So here's a sample code of what I did, you'll need a sticky service and a firebase listener, I don't know if the current version works the same as I did but here's what you could use, or at least I can give you an idea of how to do what you need.
public class DummyService extends Service implements ValueEventListener, ChildEventListener {
private Query mRef;
public static final String FIREBASE_URL = "https://your-firebase-url.firebaseio.com/";
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
// some stuff else you need to do
Firebase.setAndroidContext(this);
// this will be your main ref, it'll hear everything on your firebase db
Firebase mFirebaseRoot = new Firebase(DummyService.FIREBASE_URL);
mRef = mFirebaseRoot;
// if you want to hear an specific query you could use something like I used
// you can read the docs at firebase web
//mRef = mFirebaseRoot.child("your_child);
mRef.addListenerForSingleValueEvent(this);
mRef.addChildEventListener(this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
// read the docs of how this listeners work
if (something) {
notifyUser();
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
#Override
public void onDataChange(DataSnapshot dataSnapshot) {}
#Override
public void onCancelled(FirebaseError firebaseError) {}
}
And you could use a BroadcastReceiver to catch when the device starts and fire your service, so this way user won't need to open the app and it'll always be running.
Hope this will be useful, sorry for the code sample indents, I don't why looks like this way haha.

Social network - Follow functionality using firebase database

I am building a social network like Instagram. I have worked on many social networks before, using mySQL. I am new to firebase server.
I want to search users via name and want to show the follow/following button on the list view. But I am little confused to run android firebase queries in a standard format. I do not want to run unnecessary loops. Below is my database structure for the follow table.
Node name is follow and follower_id refers to the user who is following the user and user who gets followed is referred as followed_id.
How to write a simple query using android firebase to show all the users with the name starting (e.g "an") and with the status that I am already following him/her or not.
FYI: I am not using firebase rest API's.
How to write a simple query using android firebase to show all the users with the name starting (e.g "an") and with the status that I am already following him/her or not.
I think you can't do it in a single query.
You could try to do it with nested queries, something like this:
ChildEventListener childEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
// Retrieve the userId
User user = dataSnapshot.getValue(User.class);
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot usersFollowedDataSnapshot) {
for ( DataSnapshot userFollowedDataSnapshot : usersFollowedDataSnapshot.getChildren() ) {
// Retrieve the follower structure
Follow follow = userFollowedDataSnapshot.getValue(Follow.class);
if ( myUserId == follow.getFollowerId() ) {
// This is a user that I'm following and which name starts with "am"
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
Query secondQuery = mFirebaseDatabaseReference.child("follow").orderByChild("followedId").equalTo(user.getId());
secondQuery.addListenerForSingleValueEvent(valueEventListener);
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
};
Query firstQuery = mFirebaseDatabaseReference.child("users").orderByChild("name").startAt("am").endAt("am\uf8ff");
firstQuery.addChildEventListener(childEventListener);
I know it's not very straightforward and looks disapponting. It could be also slow, but it worth trying.
Alternatively, you should consider to structure your database in a way to simplify the firebase queries.
See more on firebase queries for example here and here.

Update recyclerview item ui in client to indicate that Firebase Database on server got updated

I'm building a chat app using Firebase Realtime Database. I want to implement the functionality like WhatsApp, where the user can send the message without internet (Add the message object on RecyclerView) but will have the clock icon (To show that the message was not sent yet).
When the Firebase Database receives the message (The user device connects to the internet and Firebase sends the message), I want to update the added element on the recyclerview(change the clock icon for a positive icon).
What have I tried? I have that
mFirebaseDatabaseReference = FirebaseDatabase.getInstance().getReference();
mFirebaseAdapter = new ChatFirebaseAdapter(mFirebaseDatabaseReference.child("chat_id"), user.getId());
recyclerChat.setLayoutManager(mLinearLayoutManager);
recyclerChat.setAdapter(mFirebaseAdapter);
My adapter extends FirebaseRecyclerAdapter from firebase-ui-database
I tried to add a addChildEventListener on my DatabaseReference where I get a dataSnapshot of the new element, but I don't know how to get the right position on recyclerview.
mFirebaseDatabaseReference.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
UPDATE RECYCLERVIEW ITEM
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
I added an onCompletionListener when I add a new message, but I don't know how get the right position again.
mFirebaseDatabaseReference.push().setValue(message, new DatabaseReference.CompletionListener() {
#Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
if (databaseError == null) {
UPDATE RECYCLERVIEW ITEM
}
}
});
Does someone have an idea why I cant accomplish that? Thanks. I search a lot before asked, but I didn't find the right answer.
The solution to your problem lies in adding a delivered boolean which can be set to true and once you receive successful push id from your code in onCompletionListener, then all you need to do is set this boolean to that particular push id.
Now on your android client in your firebase ui you can check if this boolean is set to true and change the state of recycler view items accordingly. This solution will work because firebase database is real-time.
//setting boolean onComplete
mFirebaseDatabaseReference.push().setValue(message, new DatabaseReference.CompletionListener() { #Override public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) { if (databaseError == null) { databaseReference.child("delivered").setValue(true); } } });
For chat apps like whatsapp, messenger etc you would take another messaging route if not using firebase as backened. Which I am not discussing here as it is out of scope of asked question.

Race around condition for particular case firebase

-uniqueid1
status:0
userTokenId: uniqueid(which is null)
I am having a problem of race around condition where I don't know how firebase is implementing this case. The problem is as follows:
user checks status for whether the status is 0.
if status is 0 it adds its token to userTokenid field and status 1
if not then it does'nt add its tokenid.
Now the problem is when 2 or more user check status 0 and both enter the 2 point. This question might be pointless but I need to confirm. Do i have to worry about this case?
firebase.child('uniqueid1');
firebase.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(datasnapshot.child('status').getValue().equals(0)){
//update record for userTokenId and status
}
else{
//don't update
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
If both clients happen to update the same value around the same time with a setValue() or updateChildren() call, the last write wins.
If you don't want that, you should probably be using a transaction. From that documentation:
When working with complex data that could be corrupted by concurrent modifications, such as incremental counters, we provide a transaction operation. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and will return the new desired state you would like to write. For example, if we wanted to increment the number of upvotes on a specific blog post, we would write a transaction like the following:
Firebase upvotesRef = new Firebase("https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes");
upvotesRef.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
if(currentData.getValue() == null) {
currentData.setValue(1);
} else {
currentData.setValue((Long) currentData.getValue() + 1);
}
return Transaction.success(currentData); //we can also abort by calling Transaction.abort()
}
#Override
public void onComplete(FirebaseError firebaseError, boolean committed, DataSnapshot currentData) {
//This method will be called once with the results of the transaction.
}
});
Read the documentation on transaction() for full details.
Firebase ref=firebase.child("order_details/"+orderId);
ref.runTransaction(new Transaction.Handler() {
#Override
public Transaction.Result doTransaction(MutableData currentData) {
Log.d("status",currentData.getValue());
int status= (int) currentData.child("status").getValue();
if(status==0){
HashMap<String,Object> map=new HashMap<String, Object>();
map.put("TokenId",sharedPreferences.getString("tokenId",""));
map.put("status",1);
currentData.setValue(map);
}
((MapLoadingActivity)getActivity()).setProgressVisibility(false);
return Transaction.success(currentData);
}
#Override
public void onComplete(FirebaseError firebaseError, boolean b, DataSnapshot dataSnapshot) {
}
});
I know its late to ask the question,but doTransaction() is pretty weird sometimes it calls sometimes it simply returns null.Therefore i switched my approach to php by calling firebase using php wrapper class.Please do suggest ,if my approach is wrong.

Categories

Resources