0

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;
    }
}
Guy
  • 145
  • 3
  • 12
  • 1
    Check **[this](https://stackoverflow.com/questions/50885891/one-time-login-in-app-firebaseauth)** out. – Alex Mamo Oct 05 '18 at 10:25
  • 1
    Can you get the device token? If yes, then maybe you can send http post request to a cloud function with the device token to signal reboot. Then the server sends [data only](https://firebase.google.com/docs/cloud-messaging/concept-options#data_messages) messages to the app to reschedule the alarms. You might even store the device token in your app settings bundle to avoid firebase in the receiver. – James Poag Oct 05 '18 at 13:47
  • @AlexMamo James Poag I've looked into those issues, it seems that saving the userId in the SharedPreferences is the simplest solution for the problem at hand, since those notifications are user specific, getting the user currently logged in (or previously logged in that hasn't logged out) proves to be sufficient. Thank you both! – Guy Oct 05 '18 at 18:50

2 Answers2

1

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.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks for the tip Frank! I've looked into Firebase Cloud Messaging, it looks really helpful, however it seems to be more relevant to announcement type notifications. The App I'm working on is a simple To-do list, enabling the user to select a specific task and schedule a reminder. Something that, if I understood correctly, FCM doesn't allow. But for the purpose of this discussion, saving Firebase userId in SharedPreferences does the trick. – Guy Oct 05 '18 at 18:59
  • Correct. That sounds more like a local alarm. In that case I'd recommend enabling disk persistence for the Firebase Realtime Database, which allows you to load data without having a connection to the server. For having a signed in user, I'm not sure how to solve that. Maybe keep the list of alarms in persistent storage? – Frank van Puffelen Oct 05 '18 at 19:04
  • Yes, I did enable disk persistence and it proved quite useful regardless. The workaround for having a signed in user was to use SharedPreferences. Every time a user is authenticated (in order to access his specific data from the DB) his user ID is saved. So when the device reboots, the alarms specific to him can be reinstated. – Guy Oct 06 '18 at 06:45
  • 1
    Cool. That indeed sounds like what I was hoping. I'm actually not sure of `onAuthStateChanged` would already fire in a `BroadcastReceiver`. Definitely something worth trying. – Frank van Puffelen Oct 06 '18 at 14:09
0

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!

Guy
  • 145
  • 3
  • 12