9

I am trying to make a background service that runs even if the application is closed. this services is supposed to listen to Firebase changes, and start the application according to a trigger. I don't know whether I am missing something in the code or not even close to the right answer, but here is my code :

public class FireBaseService extends Service {

private HashMap<String, String> fireBaseBattery;

@Override
public void onCreate() {
    super.onCreate();

    fireBaseBattery = new HashMap<>();
    final Firebase firebaseRef_Battery = new Firebase("the url i want to take data from");

    firebaseRef_Battery.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            if (dataSnapshot.getValue() != null) {
                fireBaseBattery = (HashMap<String, String>) dataSnapshot.getValue();
                String battery = (String) fireBaseBattery.get("battery");
                int battery_int = Integer.parseInt(battery);

                System.out.println("SERVICE FIREBASE : " + battery);
                if (battery_int <= 10) {
                    Intent intent = new Intent(getApplicationContext(), MainActivity.class);
                    startActivity(intent);
                }
            }
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {

        }
    });
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

}

And this is the manifest :

    <uses-permission android:name="android.permission.READ_SMS" />
    <application
        android:name=".ParseApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

<service android:enabled="true"
        android:name=".FireBaseService">
    </service>
        <activity
            android:name=".Launcher"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 </application>

</manifest>

Edit : I added a line in MainActivity.class to start the service

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    startService(new Intent(this, FireBaseService.class));
}
MuhammadNe
  • 674
  • 4
  • 11
  • 24
  • add your service within the application tag – Shubhank Jun 10 '16 at 15:29
  • I did but it didn't work – MuhammadNe Jun 10 '16 at 15:33
  • debug if your service is launching and code is executing in it. – Shubhank Jun 10 '16 at 15:34
  • I did but it doesn't execute. – MuhammadNe Jun 10 '16 at 15:37
  • http://stackoverflow.com/questions/20501225/using-service-to-run-background-and-create-notification – Shubhank Jun 10 '16 at 15:39
  • A debugger is your friend here. Step and see if you service is even being run. If it is, also check if your `onCancelled()` is being invoked. Leaving it empty like you've done is a sure-fire way to miss important events (such as the database telling you that you don't have permission to read the data). – Frank van Puffelen Jun 10 '16 at 15:44
  • @FrankvanPuffelen I checked now if onCancelled is being invoked but it seems like the service is not running in the first place. I have also edited the question, I added a line in the main activity that is supposed to run the service, but yet doesn't. – MuhammadNe Jun 10 '16 at 15:55

3 Answers3

2

I edited the Application as following :

  1. The Service class extends Activity and not Service
  2. I added the service manifest inside the application tag directly as the following :

android:name=".FireBaseService"

  1. There is no need to start the service from main activity.

Edited Manifest:

 <uses-permission android:name="android.permission.READ_SMS" />
<application
    android:name=".FireBaseService"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <activity
        android:name=".Launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme.NoActionBar">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

MuhammadNe
  • 674
  • 4
  • 11
  • 24
0

What you want, it just startForeground you can do it. For safety reasons, the user should be aware that something is running in the background.

jrbedard
  • 3,662
  • 5
  • 30
  • 34
Pengewap
  • 11
  • 4
0

I think I have cracked it.

I have solved it using a child event listener and a sticky service. Here is how it goes:

I first add a child event listener in my data store class:

class DataStore {

        private static DataStore sDataStore;
        private List<EmergencyZoneEventListener> mEmergencyZoneEventListeners = null;

        static DataStore get() {
            if (sDataStore == null) {
                sDataStore = new DataStore();
            }

            return sDataStore;
        }

        void addEmergencyZoneEventListener(EmergencyZoneEventListener emergencyZoneEventListener) {
            if (emergencyZoneEventListener != null && !sDataStore.mEmergencyZoneEventListeners.contains(emergencyZoneEventListener)) {
                sDataStore.mEmergencyZoneEventListeners.add(emergencyZoneEventListener);
            }
        }

        void removeEmergencyZoneEventListener(EmergencyZoneEventListener emergencyZoneEventListener) {
            mEmergencyZoneEventListeners.remove(emergencyZoneEventListener);
        }

        DataStore() {
            FirebaseDatabase.getInstance().getReference("/emergencyZones/").
                    addChildEventListener(new ChildEventListener() {
                        @Override
                        public void onChildAdded(DataSnapshot dataSnapshot, String s) {
                            emergencyZoneAddedOrChangedRemotely(dataSnapshot);
                        }

                        @Override
                        public void onChildChanged(DataSnapshot dataSnapshot, String s) {
                            emergencyZoneAddedOrChangedRemotely(dataSnapshot);
                        }

                        @Override
                        public void onChildRemoved(DataSnapshot dataSnapshot) {
                            emergencyZoneRemovedRemotely(dataSnapshot);
                        }

                        @Override
                        public void onChildMoved(DataSnapshot dataSnapshot, String s) { }

                        @Override
                        public void onCancelled(DatabaseError databaseError) {
                            Log.w(TAG, "onCancelled: " + databaseError.getMessage() + ": "
                                    + databaseError.getDetails());
                        }
                    });
        }
    }

And here is what happens within emergencyZoneAddedOrChangedRemotely:

private void emergencyZoneAddedOrChangedRemotely(DataSnapshot dataSnapshot) {
        EmergencyZone ez = dataSnapshot.getValue(EmergencyZone.class);
        if (ez == null || ez.getKey() == null) {
            Log.w(TAG, "emergencyZoneAddedRemotely: EmergencyZone or getKey is null");
            return;
        }

        for (EmergencyZoneEventListener listener : mEmergencyZoneEventListeners) {
            listener.onEmergencyZoneAdded(ez);
        }
    }

Here, each time a change occurs in the Firebase, and the Firebase fires my ChildEventListener I in turn fire my locally attached listeners using listener.onEmergencyZondeAdded.

And finally the sticky service:

public class NotificationService extends Service {

    private static final String TAG = "NotificationService";

    private DataStore mDataStore;
    private DataStore.EmergencyZoneEventListener mEmergencyZoneEventListener = new DataStore.EmergencyZoneEventListener() {
        @Override
        public void onEmergencyZoneAdded(EmergencyZone emergencyZone) {
            Log.i(TAG, "onEmergencyZoneAdded: " + emergencyZone.getKey());
        }

        @Override
        public void onEmergencyZoneChanged(EmergencyZone emergencyZone) {
            Log.i(TAG, "onEmergencyZoneChanged: " + emergencyZone.getKey());
        }

        @Override
        public void onEmergencyZoneRemoved(EmergencyZone emergencyZone) { }
    };

    public static Intent getIntent(Context packageContext) {
        return new Intent(packageContext, NotificationService.class);
    }

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate: NotificationService is being created...");
        mDataStore = DataStore.get(this);
        mDataStore.addEmergencyZoneEventListener(mEmergencyZoneEventListener);
        QueryPreferences.setIsNotificationsOn(this, true);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: Received start id " + startId + ": " + intent);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        Log.i(TAG, "onDestroy: NotificationService is being destroyed...");
        mDataStore.removeEmergencyZoneEventListener(mEmergencyZoneEventListener);
        QueryPreferences.setIsNotificationsOn(this, false);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

I tested this solution when the application was exited and when phone was asleep and it worked neatly. When I closed the application from the task manager the service didn't get destroyed but got recreated and continued to receive remote changes.

Hope it helps.

Note: My callbacks can take up to 5 minutes to get fired following the actual changes in the firebase database.

felixy
  • 179
  • 1
  • 6
  • Mate I don't have an android 8.0+ device with me - and no way am I struggling with the slow emulator to test it – felixy Jul 26 '18 at 08:52
  • Where did you initialize Firebase? – Arnold Parge Jan 31 '19 at 14:41
  • Hey man. It is a bit of old piece of code therefore delayed reply. I checked it and you should statically initialize the "DataStore" class and should use it as a singleton. Maybe through a "holder" class (more to it here => https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom)? Hope this was useful. Have a nice one. – felixy Feb 02 '19 at 18:09
  • @felixy what is QueryPreferences , EmergencyZoneEventListener & EmergencyZone here? – TejpalBh Jul 19 '19 at 06:10
  • @TejpalBh QueryPreferences should be a SharedPreferences wrapper. The DataStore class is a singleton wrapping lower level Firebase database operations and it calls registered callbacks of type EmergencyZoneEventListener to notify its users about changes that have occurred on the database. EmergencyZone class encapsulates the records kept in the database. – felixy Jul 19 '19 at 10:09