Home > Back-end >  How to get instance on my singleton handler in Broadcast Receiver?
How to get instance on my singleton handler in Broadcast Receiver?

Time:03-13

I went down a rabbit hole of trying to make my service work and I believe I have just made an amalgamation. I am trying to make a step counter service, it uses a handler which has a step counter sensor implemented onto it. Every 6 hours i want to update my server with the step count inforrmation, reset the step count near to the end of the day and set another alarm on my handler to do the same action every 6 hours. The handler also routinely sends data to an activity to read the number of steps its recorded.

However, everything works but I am unable to get an instance of my handler in my broadcast receiver. So another alarm is never set and the steps are never reset. Can anyone help me with this?

To be specific, the first line of code in the broadcast receiver (StepCounterHandler handler = StepCounterHandler.getInstance();) will always return null from my testing.

This is my code:

Android Manifest.xml:

    <receiver android:name=".Account.StepCounterService.StepCountUpdaterAlarm"
        android:exported="true">
    </receiver>

    <service
        android:enabled="true"
        android:process=":stepCounterService"
        android:name=".Account.StepCounterService.StepCounterService" />

StepCounterHandler.java:

public class StepCounterHandler extends Handler implements SensorEventListener {
private boolean onSensorChangedFirstCall = true;
private int previousSteps;
private int cumulativeSteps;

private List<String> debugInfo = new ArrayList<String>() {};

private static StepCounterHandler stepCounterHandler;

public static StepCounterHandler getInstance(Context callingContext, Looper looper) {
    if(stepCounterHandler == null) stepCounterHandler = new StepCounterHandler(callingContext, looper);
    Log.d("MyService", "setting handler instance:"   (stepCounterHandler == null));
    return stepCounterHandler;
}

public static StepCounterHandler getInstance()
{
    if(stepCounterHandler == null) return null;
    return stepCounterHandler;
}

private StepCounterHandler(Context callingContext, Looper looper) {
    super(looper);

    SensorManager sensorManager = (SensorManager) callingContext.getSystemService(Context.SENSOR_SERVICE);
    Sensor stepSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);

    setAlarm(callingContext);

    if (stepSensor != null) {
        sensorManager.registerListener(this, stepSensor, SensorManager.SENSOR_DELAY_UI);
    } else {
        Toast.makeText(callingContext, "No step sensor has been detected on this device.", Toast.LENGTH_SHORT).show();
    }
}

public void setAlarm(Context context)
{
    AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);

    Intent intent = new Intent(context, katibh.project.walkersquest.Account.StepCounterService.StepCountUpdaterAlarm.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,    intent, 0);

    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_YEAR, -1);
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 50);
    calendar.set(Calendar.SECOND, 00);


    SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
    Date date = new Date(System.currentTimeMillis());
    debugInfo.add("Attempted update at:"   formatter.format(date));

    while(calendar.getTimeInMillis() < System.currentTimeMillis())
    {
        calendar.add(Calendar.MINUTE, 1);
    }

    debugInfo.add("Attempting next update at:"   calendar.getTime());

    // CANNOT RUN ALARM EVERY MINUTE IN setAndAllowWhileIdle, ONE MINUTE ALARM IS FOR TESTING PURPOSES
    //        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    //            alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    //        } else {
                   alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
    //        }
}

@Override
public void handleMessage(@NonNull Message msg) {
    super.handleMessage(msg);
}

@Override
public void onSensorChanged(SensorEvent event) {
    int totalSteps = (int) event.values[0];
    if(onSensorChangedFirstCall)
    {
        previousSteps = totalSteps;
        onSensorChangedFirstCall = false;
    }

    cumulativeSteps = totalSteps - previousSteps;
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

public synchronized int getCumulativeSteps()
{
    cumulativeSteps = (int)(5000.0 * new Random().nextDouble());
    return cumulativeSteps;
}

public synchronized void resetStepsCounter() {
    cumulativeSteps = 0;
    previousSteps = 0;
    onSensorChangedFirstCall = true;
}
}

Broadcast Receiver:

public class StepCountUpdaterAlarm extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
    //Handler is null here when i the code below is executed.
    StepCounterHandler handler = StepCounterHandler.getInstance();
              
    //Do something...
    sendToServer(handler.getCumultiveSteps)
    handler.setAlarm(context);

    if(isTimeToReset())
       handler.resetStepsCounter();
}

private boolean isTimeToReset()
{
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 23);
    calendar.set(Calendar.MINUTE, 50);
    calendar.set(Calendar.SECOND, 00);

    return calendar.getTimeInMillis() < System.currentTimeMillis();
}
}

The example above sets and should fire an alarm every minute (for testing purposes), so i am using the set method. I understand that setAndAllowWhileIdle can only dispatch once every 15 minutes but the main issue is that I cannot get an instance of the handler in the receiver. It will always return null and I am not sure why.

CodePudding user response:

If the zero-parameter getInstance() is returning null, you have no singleton.

You have two processes. The StepCounterService is in the :stepCounterService process. StepCountUpdaterAlarm is in the default process. These processes share no objects, and so they will have separate StepCounterHandler singleton instances. If you have not done something previously to set up the StepCounterHandler in the default process, it will be null.

If you are expecting to share the singleton between StepCounterService and StepCountUpdaterAlarm, they will need to be in the same process. Either move StepCountUpdaterAlarm into the :stepCounterService process or move StepCounterService into the default process.

CodePudding user response:

Courtesy of @CommonsWare, in my case, I had to just move the Broadcast Receiver into the service process.

AndroidManifest.xml (Before):

<receiver android:name=".Account.StepCounterService.StepCountUpdaterAlarm"
    android:exported="true">
</receiver>

<service
    android:enabled="true"
    android:process=":stepCounterService"
    android:name=".Account.StepCounterService.StepCounterService" />

AndroidManifest.xml (After):

<receiver android:name=".Account.StepCounterService.StepCountUpdaterAlarm"
    android:process=":stepCounterService" 
    android:exported="true">
</receiver>

<service
    android:enabled="true"
    android:process=":stepCounterService"
    android:name=".Account.StepCounterService.StepCounterService" />
  • Related