Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Android): Add updateNotification() method #37

Merged
merged 4 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ const veryIntensiveTask = async (taskDataArguments) => {
const options = {
taskName: 'Example',
taskTitle: 'ExampleTask title',
taskDesc: 'ExampleTask desc',
taskDesc: 'ExampleTask description',
taskIcon: {
name: 'ic_launcher',
type: 'mipmap',
Expand All @@ -132,6 +132,7 @@ const options = {


await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({taskDesc: 'New ExampleTask description'}); // Only Android, iOS will ignore this call
// iOS will also run everything here in the background until .stop() is called
await BackgroundService.stop();
```
Expand Down Expand Up @@ -167,7 +168,7 @@ taskIconOptions

## Maintainers

* [Rapsssito](https://github.com/rapsssito)
* [Rapsssito](https://github.com/rapsssito) [[Support me :heart:](https://github.com/sponsors/Rapsssito)]

## Acknowledgments

Expand Down
35 changes: 35 additions & 0 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,38 @@ test('stop', async () => {
expect(BackgroundActions.isRunning()).toBe(false);
promiseFinish(); // Clear the promise
});

test('updateNotification-ios', async () => {
Platform.OS = 'ios';
let promiseFinish = () => {};
const defaultTask = jest.fn(
async () => await new Promise((resolve) => (promiseFinish = resolve))
);
await BackgroundActions.start(defaultTask, defaultOptions);
RNBackgroundActionsModule.updateNotification.mockClear();
await BackgroundActions.updateNotification({ taskDesc: 'New Desc' });
expect(RNBackgroundActionsModule.updateNotification).toHaveBeenCalledTimes(0);
promiseFinish(); // Clear the promise
});

test('updateNotification-android', async () => {
Platform.OS = 'android';
let promiseFinish = () => {};
const defaultTask = jest.fn(
async () => await new Promise((resolve) => (promiseFinish = resolve))
);
await BackgroundActions.start(defaultTask, defaultOptions);
RNBackgroundActionsModule.updateNotification.mockClear();
const updatedOptions = { taskDesc: 'New Desc' };
await BackgroundActions.updateNotification(updatedOptions);
expect(RNBackgroundActionsModule.updateNotification).toHaveBeenCalledTimes(1);
const newOptions = RNBackgroundActionsModule.updateNotification.mock.calls[0][0];
expect(newOptions.taskDesc).toBe(updatedOptions.taskDesc);
promiseFinish(); // Clear the promise
});

test('updateNotification-android-notRunning', async () => {
Platform.OS = 'android';
const updatedOptions = { taskDesc: 'New Desc' };
await expect(BackgroundActions.updateNotification(updatedOptions)).rejects.toBeDefined();
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.asterinet.react.bgactions;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
Expand Down Expand Up @@ -62,6 +65,27 @@ public void stop(@NonNull final Promise promise) {
promise.resolve(null);
}

@SuppressWarnings("unused")
@ReactMethod
public void updateNotification(@NonNull final ReadableMap options, @NonNull final Promise promise) {
// Get the task info from the options
try {
final Bundle extras = getExtrasFromOptions(options);
// Get info
final String taskTitle = extras.getString("taskTitle", "RNBackgroundActionsTaskTitle");
final String taskDesc = extras.getString("taskDesc", "RNBackgroundActionsTaskDesc");
final int iconInt = extras.getInt("iconInt");
final int color = extras.getInt("color");
final Notification notification = RNBackgroundActionsTask.buildNotification(reactContext, taskTitle, taskDesc, iconInt, color);
final NotificationManager notificationManager = (NotificationManager) reactContext.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(RNBackgroundActionsTask.SERVICE_NOTIFICATION_ID, notification);
} catch (Exception e) {
promise.reject(e);
return;
}
promise.resolve(null);
}

@NonNull
private Bundle getExtrasFromOptions(@NonNull final ReadableMap options) {
// Create extras
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
Expand All @@ -13,14 +14,30 @@
import com.facebook.react.bridge.Arguments;
import com.facebook.react.jstasks.HeadlessJsTaskConfig;

import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

final public class RNBackgroundActionsTask extends HeadlessJsTaskService {

private static final String CHANNEL_ID = "RN_BACKGROUND_ACTIONS_CHANNEL";
private static final int SERVICE_NOTIFICATION_ID = 92901;
public static final int SERVICE_NOTIFICATION_ID = 92901;

@NonNull
public static Notification buildNotification(@NonNull final Context context, @NonNull final String taskTitle, @NonNull final String taskDesc, final int iconInt, @ColorInt int color) {
final Intent notificationIntent = new Intent(context, ReactActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
return new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(taskTitle)
.setContentText(taskDesc)
.setSmallIcon(iconInt)
.setContentIntent(contentIntent)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setColor(color)
.build();
}

@Override
protected @Nullable
Expand All @@ -46,17 +63,7 @@ public int onStartCommand(Intent intent, int flags, int startId) {
// Turning into a foreground service
createNotificationChannel(taskTitle, taskDesc); // Necessary creating channel for API 26+
// Create the notification
final Intent notificationIntent = new Intent(this, ReactActivity.class);
final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT);
final Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(taskTitle)
.setContentText(taskDesc)
.setSmallIcon(iconInt)
.setContentIntent(contentIntent)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_MIN)
.setColor(color)
.build();
final Notification notification = buildNotification(this, taskTitle, taskDesc, iconInt, color);
startForeground(SERVICE_NOTIFICATION_ID, notification);
return super.onStartCommand(intent, flags, startId);
}
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module.exports = {
'node_modules/(?!(react-native|react-navigation|@react-navigation|@react-native-community))',
],
modulePathIgnorePatterns: ['examples/'],
collectCoverageFrom: ['index.js'],
collectCoverageFrom: ['src/index.js'],
globals: {
__DEV__: true,
},
Expand Down
1 change: 1 addition & 0 deletions jest.setup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
jest.mock('./src/RNBackgroundActionsModule.js', () => ({
start: jest.fn(),
stop: jest.fn(),
updateNotification: jest.fn(),
}));
38 changes: 31 additions & 7 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,38 @@ import RNBackgroundActions from './RNBackgroundActionsModule';
* taskTitle: string,
* taskDesc: string,
* taskIcon: {name: string, type: string, package?: string},
* color?: string,
* parameters?: any}} BackgroundTaskOptions
* color?: string}} BackgroundTaskOptions
*/
class BackgroundServer {
constructor() {
/** @private */
this._runnedTasks = 0;
/** @private */
this._stopTask = () => {};
/** @private */
this._isRunning = false;
/** @private @type {BackgroundTaskOptions} */
this._currentOptions;
}

/**
* **ANDROID ONLY**
*
* Updates the task notification.
*
* *On iOS this method will return immediately*
*
* @param {{taskTitle?: string,
* taskDesc?: string,
* taskIcon?: {name: string, type: string, package?: string},
* color?: string}} taskData
*/
async updateNotification(taskData) {
if (Platform.OS !== 'android') return;
if (!this.isRunning())
throw new Error('A BackgroundAction must be running before updating the notification');
this._currentOptions = this._normalizeOptions({ ...this._currentOptions, ...taskData });
await RNBackgroundActions.updateNotification(this._currentOptions);
}

/**
Expand All @@ -29,18 +53,18 @@ class BackgroundServer {

/**
* @param {(taskData: any) => Promise<void>} task
* @param {BackgroundTaskOptions} options
* @param {BackgroundTaskOptions & {parameters?: any}} options
* @returns {Promise<void>}
*/
async start(task, options) {
this._runnedTasks++;
const finalOptions = this._normalizeOptions(options);
this._currentOptions = this._normalizeOptions(options);
const finalTask = this._generateTask(task, options.parameters);
if (Platform.OS === 'android') {
AppRegistry.registerHeadlessTask(finalOptions.taskName, () => finalTask);
await RNBackgroundActions.start(finalOptions);
AppRegistry.registerHeadlessTask(this._currentOptions.taskName, () => finalTask);
await RNBackgroundActions.start(this._currentOptions);
} else {
await RNBackgroundActions.start(finalOptions);
await RNBackgroundActions.start(this._currentOptions);
finalTask();
}
this._isRunning = true;
Expand Down