My app currently schedules notifications for tasks at specific times (chosen by the user). The task's title and reminder date is saved in the Firebase Realtime database.
The point is, when the device reboots, all of that is lost. So it is required to restart all the relevant notifications that were scheduled in the past.
Therefore, I have a BroadcastReceiver that can get the BOOT_COMPLETED intent.
However, I can't access Firebase DB, because no user is currently logged in - and I also can't start Firebase Authentication (startActivityForResult isn't recognized and importing it causes more errors). I assume that's because the BroadcastReceiver isn't an activity.
I'm wondering if there is a way around that.
I currently tried setting an intent to start the MainActivity (where the user is authenticated) and then perform my relevant tasks, however that did not work.
I'm wondering if there is a way around that, to get the authenticated user (or authenticate him on device reboot) and then get the data from Firebase.
AlarmReceiver code (extends BroadcastReceiver) - it's a bit of a mess but it helps get context.
public class AlarmReceiver extends BroadcastReceiver {
private ChildEventListener mChildEventListener;
private FirebaseDatabase mFirebaseDatabase;
private DatabaseReference mTaskDatabaseReference;
private FirebaseAuth mFirebaseAuth;
private FirebaseAuth.AuthStateListener mAuthStateListener;
private int i;
#Override
public void onReceive(final Context context, Intent intent) {
mFirebaseDatabase = FirebaseDatabase.getInstance();
mFirebaseAuth = FirebaseAuth.getInstance();
if (intent.getAction() != null && context != null) {
if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
TaskInfoFragment.showReminderNotification(context, MainActivity.class, "hoooo",1000);
onSignedOutCleanup(context);
mTaskDatabaseReference=mFirebaseDatabase.getReference().child("users").child(MainActivity.getCurrentUserId());
attachDatabaseReadListener(context);
//TODO - HANDLE DEVICE REBOOT
}
}
//Trigger the notification
Bundle extras = intent.getExtras();
String taskTitle = "Error, no task title!";
int taskIntId=-1;
if (extras != null) {
taskTitle = extras.getString("taskTitle");
taskIntId=extras.getInt("taskIntId");
}
TaskInfoFragment.showReminderNotification(context, MainActivity.class, taskTitle,taskIntId);
}
private void attachDatabaseReadListener(final Context context) {
i=0;
mChildEventListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
i++;
TaskList task = dataSnapshot.getValue(TaskList.class);
TaskInfoFragment.showReminderNotification(context, MainActivity.class, task.getTitle(),i);
Log.d("here is another task","title: "+task.getTitle());
}
public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
public void onChildRemoved(DataSnapshot dataSnapshot) {
}
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
public void onCancelled(DatabaseError databaseError) {}
};
mTaskDatabaseReference.addChildEventListener(mChildEventListener);
}
private void onSignedInInitialize(final String userId,Context context) {
//Get reference for the task list for the logged in user and attach the database listener
mTaskDatabaseReference=mFirebaseDatabase.getReference().child("users").child(userId);
attachDatabaseReadListener(context);
}
private void onSignedOutCleanup(Context context) {
Intent taskIntent = new Intent(context,MainActivity.class);
// Send the intent to launch a new activity
context.startActivity(taskIntent);
}
private void detachDatabaseReadListener() {
if (mChildEventListener != null) {
mTaskDatabaseReference.removeEventListener(mChildEventListener);
mChildEventListener = null;
}
}
If you want to display these notifications even before the user has logged in, then clearly they are not meant to be associated with the user. I'd associate them with another value, probably one that identifies the device. The InstanceIdToken that James commented about may be a good solution for that, since it identifies a specific app on a specific device.
But definitely also check out Firebase Cloud Messaging, which allows you to deliver notifications to a device right after it has booted. Since FCM messages are handled by a receiver in Google Play Services, this is typically a more reliable way to deliver such messages, than by trying to load them from a remote database in your own receiver.
Not coincidentally: FCM relies on Instance ID tokens to identify the device/app to deliver a message to.
So in conclusion, I saved the current logged in user using SharedPreferences.
(It works because the notifications are user related, so we can safely assume the last user that logged in is the relevant one).
Then I changed my Firebase DB tree to allow me to access the Tasks using the current logged in user ID. A little more coding and bug fixing and it's a go.
Thanks for all the help!
Related
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.
Within my app I often have the need to read data once. I originally started by using the addListenerForSingleValueEvent() method for this, however I ran into problems using this method as it does not work as I wanted when offline capabilities are enabled (see here the issue: Firebase Offline Capabilities and addListenerForSingleValueEvent)
In the question above it is mentioned that a workaround is to use the addValueEventListener() method, however I do not fully understand how to do this (particularly how to remove the ValueEventListener as soon I am finished grabbing the data I need).
Take this method which I created in a standalone class to query the Users node on Firebase where I store the users FCM Token. It seems to have an issue of not returning the latest token from the server everytime.
public class SendFCMMessage {
String userToken;
String currentUser;
String userName;
ValueEventListener userListener;
public void sendMessage(final String contactNumber) {
final DatabaseReference ref = FirebaseDatabase.getInstance().getReferenceFromUrl(link).child("Users").child(contactNumber);
userListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
User user = dataSnapshot.getValue(User.class);
userToken = user.getToken();
// Send FCM Message after getting user token and then remove event listener
ref.removeEventListener(userListener);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.d("TAG", "Something terrible went wrong: " + databaseError);
}
};
ref.addValueEventListener(userListener);
}
}
If I remove the line
ref.removeEventListener(userListener);
Then this code works fine, however I would like to know how I could remove the ValueEventListener as soon as I receive the data I need?
Thanks,
R
ValueEventListener vel; //Declared Global
Listen your DatabaseReference like this;
vel = yourDatabaseReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChanged(DataSnapshot dataSnapShot) {
//Do your stuff here. I suggest you create another method for this if you don't want a problem with inner class.
//For example, workDataSnapshot(DataSnapshot dataSnapShot) <-- Work here
yourDatabaseReference.removeEventListener(vel);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope it helps you.
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.
I'm making chat application with Firebase Realtime Database.
I Made
public class MessageAdapter extends BaseAdapter implements ChildEventListener{
...
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Message msg = dataSnapshot.getValue(Message.class);
this.addItem(msg);
this.notifyDataSetChanged();
}
...
}
and this MessageAdapter refresh my Listview when data is added to Firebase.
But
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
Message msg = dataSnapshot.getValue(Message.class);
this.addItem(msg);
this.notifyDataSetChanged();
}
onChildAdded Method is called whenever I started app. I want to load Message Only after User starting app. It loads every messages which is stored in my Firebase Realtime Database.
Is there a way to load only new message after user starting application? and furthermore I want to make this to load specific number of messages like if I set the number as 5, when user start an app it loads from Firebase Database only 5 stored messages.
Okay, so there are a couple questions here, first would be to make the app wait before querying the data base. To do this I would suggest pausing the thread for a few seconds while it loads the data using a handler.
handler1.postDelayed(new Runnable() {
#Override
public void run() {
// Your Database Reference Listener
}
}, 1000);
also to listen to only a few from firebase just change the listener like so
yourDatabaseReferance.limitToFirst(10).addChildEventListener(new ChildEventListener()
Users are created using Email and Password. This is how I do the Sign-up:
mSignup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mEmailStr = removeSpaces(mEmail.getText().toString());
mPasswordStr = mPassword.getText().toString();
mUsernameStr = mUsername.getText().toString();
mIsSgl = mSglCheckBox.isChecked();
mUsernameStr=mUsername.getText().toString();
final User mUser = new User();
mUser.setEmail(mEmailStr);
mUser.setPassword(mPasswordStr);
mUser.setIsSgl(mIsSgl);
mUser.setStudyGroupName(mStudyGroupName);
mUser.setUsername(mUsernameStr);
FirebaseAuth.getInstance().createUserWithEmailAndPassword(mUser.getEmail(), mUser.getPassword()).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(Task<AuthResult> task) {
if (task.isSuccessful()) {
Toast.makeText(getActivity(), "Sucsses", Toast.LENGTH_SHORT).show();
generateUser(mUser);
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
Toast.makeText(getActivity(), "not Sucsses", Toast.LENGTH_SHORT).show();
}
}
});
}
});
This is how I push the data into database:
public void generateUser(User user)
{
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference users;
if(user.getIsSgl())
{
users = database.getReference(user.getStudyGroupName()).child("SGL");
}
else
{
users = database.getReference(user.getStudyGroupName()).child("Student");
}
users.push().setValue(user);
}
This is how I Sign-in:
mSignin.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mEmailStr = SignupActivityFragment.removeSpaces(mEmail.getText().toString());
mPasswordStr = mPassword.getText().toString();
mAuth.signInWithEmailAndPassword(mEmailStr, mPasswordStr).addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(Task<AuthResult> task) {
if (task.isSuccessful()){
FirebaseDatabase database = FirebaseDatabase.getInstance();
// thats not worked for me
database.getReference("StudyGroups").child("Student").orderByChild("email").equalTo(mEmailStr).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot childSnapShot : dataSnapshot.getChildren()) {
userKey = childSnapShot.getKey();
}
Toast.makeText(getContext(),"Userkey: " + userKey,Toast.LENGTH_LONG).show();
Log.v("himaAbousalem",userKey);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Toast.makeText(getActivity(), "Sucsses ", Toast.LENGTH_SHORT).show();
startActivity (new Intent(getActivity(),Controller.class));
}else {
Toast.makeText(getActivity(), "not sucsses", Toast.LENGTH_SHORT).show();
}
}
});
}
});
I want to query the database so that when a user signs-in by Email and password, it returns all the data of that user from the database.
How can I make the key of userId in Auth equal to the userId in database and how do I use that feature?
tl;dr - In this case, store each user using their associated uid generated by Fireabase Auth instead of using a push ID.
In your situation the challenge with using a push ID to store info specific to each user is that when a user signs in you don't know the push ID you used when you first stored their info. To find a user each time they sign in you would have to search through every user in your database until you find a matching email/password to retrieve their correct profile information - the more users you have, the longer it would take to do the search. One alternative, which would probably be faster, is to use Firebase Authentication to create users and the Firebase Database to store any user specific information.
When you create a new user using Firebase Authentication it will assign a unique user id to the user that will be the same throughout the lifetime of the user. You then use the unique user id generated by Firebase Auth instead of a push ID to store user info in the database. The next time a user signs in you get the user's uid from Firebase Auth and use it to query the database to get that user's information.
Check the guide for how to create a password-based user and how to sign a user in using Firebase Auth: Create a password-based account
In order to use the unique uid generated by Firebase Auth I suggest a few changes to your code.
Update database structure
I suggest you update your database structure by adding a new location (maybe "allUsers") for use when you create/sign in users. Right now it looks like your are breaking up students into groups. If you need to keep this structure, for reasons beyond authentication, you can use it along with my suggestion. The reason for a single location which stores all users is that you need a definite location to query when a user signs in. When using Firebase Auth, without a single location which stores all users there is no way to tell what group a user belongs to when they first sign in. You would have to check every group in order to find a match and that may take a long time. Having a location which stores all users solves that problem. Also, the query for retrieving user information from that single location is much simpler. If you do need to keep a user's information in multiple places just be sure to update their information in both places if any changes occur.
Create a class variable used to distinguish between create user and sign in existing user.
If you use the same Activity to create a new user and sign in an existing user then create a boolean variable to make a distinction between when a new user is being created and when an existing user is signing in. It will be used later in the AuthStateListener. If you handle user creation in a separate activity from general sign in then you shouldn't need this variable because each activity would have a separate AuthStateListener.
boolean mIsNewUser = false;
Move the call to generateUser() from the create user completion listener to an AuthStateListener. Also move your database query from the sign in completion listener to the AuthStateLisener
Whenever you create a user successfully they will automatically be signed in too. So, if you move your call to generateUser() from the createUserWithEmailAndPassword OnCompleteListener to your AuthStateListener you can get access to the created user's uid. When signing an existing user move your database query to the AuthStateListener as well, again so we can access the user's uid. I'm also going to create a helper method for the database query called getExistingUserInfo. As an FYI, the onComplete() callback in the OnCompleteListeners for creating and signing in users gives you access to an AuthResult which according to the API has a method for returning the current user but the documentation says to access user information in the AuthStateListener.
private FirebaseAuth mAuth;
private FirebaseAuth.AuthStateListener mAuthListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
mAuth = FirebaseAuth.getInstance();
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
if(mIsNewUser){
//call to generate user using Uid instead of pushID
generateUser(mUser, user.getUid());
} else{
getExistingUserInfo(user.getUid());
}
startActivity(new Intent(getActivity(), MainActivity.class));
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
}
// ...
}
};
// ...
}
}
#Override
public void onStart() {
super.onStart();
mAuth.addAuthStateListener(mAuthListener);
}
#Override
public void onStop() {
super.onStop();
if (mAuthListener != null) {
mAuth.removeAuthStateListener(mAuthListener);
}
}
Update your generateUser() helper method to use the uid instead of a push ID:
I'm going to assume you want to keep you existing database structure and add the single location for all users as suggested. Based on this I've made a couple of changes to the write operation you were using in generateUser(). Mainly, instead of using setValue() to write to the database I'm using updateChildren(). By using updateChildren() we can take advantage of Firebase's ability to do atomic updates. This will allow us to write to the appropriate student group location and the location storing all users simultaneously. By taking this approach if the write operation to either location fails neither location will be updated. This way you can be certain if a student is added to a group they will also be listed in the allUsers location.
private void generateUser(User user, String uid)
{
DatabaseReference database = FirebaseDatabase.getInstance().getReference();
String userType;
String allusers = "allUsers/" + uid;
Map<String, Object> newUserUpdate = new HashMap<>();
if (user.getUsername() != null) {
if (user.isSgl()) {
userType = user.getStudyGroupName() + "/" + "SGL" + "/" + uid;
} else {
userType = user.getStudyGroupName() + "/" + "Student" + "/" + uid;
}
newUserUpdate.put(userType, user.serialize());
newUserUpdate.put(allusers, user.serialize());
database.updateChildren(newUserUpdate);
}
}
Update database query to use new location which stores all users
As I mentioned above, by creating a single location for all users you can reduce the complexity of the query used to find a user's info when they sign in. Again, if you need to store users by group you can keep that but be sure to update both locations if a users info changes.
public void getExistingUserInfo(String uid){
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.getReference("allUsers").child(uid).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
//get user data from dataSnapshot
Toast.makeText(getContext(),"Userkey: " + userKey,Toast.LENGTH_LONG).show();
Log.v("himaAbousalem",userKey);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
Set the variable used to distinguish between existing user sign in and new user creation in the create user completion listener
FirebaseAuth.getInstance().createUserWithEmailAndPassword(mUser.getEmail(), mUser.getPassword()).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(Task<AuthResult> task) {
if (task.isSuccessful()) {
//set boolean used in the AuthListener
mIsNewUser = true;
Toast.makeText(getActivity(), "Sucsses", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(), "not Sucsses", Toast.LENGTH_SHORT).show();
}
}
});