Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 624a461

Browse files
committed
Merge pull request #236 from Microsoft/minimumBackgroundDuration
Minimum background duration
2 parents ae21449 + 9afec87 commit 624a461

File tree

6 files changed

+115
-42
lines changed

6 files changed

+115
-42
lines changed

CodePush.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
239239
ignoreFailedUpdates: true,
240240
installMode: CodePush.InstallMode.ON_NEXT_RESTART,
241241
mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
242+
minimumBackgroundDuration: 0,
242243
updateDialog: null,
243244
...options
244245
};
@@ -273,7 +274,11 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
273274
if (resolvedInstallMode == CodePush.InstallMode.ON_NEXT_RESTART) {
274275
log("Update is installed and will be run on the next app restart.");
275276
} else {
276-
log("Update is installed and will be run when the app next resumes.");
277+
if (syncOptions.minimumBackgroundDuration > 0) {
278+
log(`Update is installed and will be run after the app has been in the background for at least ${syncOptions.minimumBackgroundDuration} seconds.`);
279+
} else {
280+
log("Update is installed and will be run when the app next resumes.");
281+
}
277282
}
278283
break;
279284
case CodePush.SyncStatus.UNKNOWN_ERROR:
@@ -302,7 +307,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg
302307
resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;
303308

304309
syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);
305-
await localPackage.install(resolvedInstallMode, () => {
310+
await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {
306311
syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);
307312
});
308313

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,8 @@ While the `sync` method tries to make it easy to perform silent and active updat
567567

568568
* __mandatoryInstallMode__ *(codePush.InstallMode)* - Specifies when you would like to install updates which are marked as mandatory. Defaults to `codePush.InstallMode.IMMEDIATE`. Refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do.
569569

570+
* __minimumBackgroundDuration__ *(Number)* - Specifies the minimum number of seconds that the app needs to have been in the background before restarting the app. This property only applies to updates which are installed using `InstallMode.ON_NEXT_RESUME`, and can be useful for getting your update in front of end users sooner, without being too obtrusive. Defaults to `0`, which has the effect of applying the update immediately after a resume, regardless how long it was in the background.
571+
570572
* __updateDialog__ *(UpdateDialogOptions)* - An "options" object used to determine whether a confirmation dialog should be displayed to the end user when an update is available, and if so, what strings to use. Defaults to `null`, which has the effect of disabling the dialog completely. Setting this to any truthy value will enable the dialog with the default strings, and passing an object to this parameter allows enabling the dialog as well as overriding one or more of the default strings. Before enabling this option within an App Store-distributed app, please refer to [this note](#user-content-apple-note).
571573

572574
The following list represents the available options and their defaults:
@@ -595,10 +597,10 @@ Example Usage:
595597
// in the Info.plist file
596598
codePush.sync({ deploymentKey: "KEY" });
597599
598-
// Download the update silently
599-
// but install is on the next resume
600-
// instead of waiting until the app is restarted
601-
codePush.sync({ installMode: codePush.InstallMode.ON_NEXT_RESUME });
600+
// Download the update silently, but install it on
601+
// the next resume, as long as at least 5 minutes
602+
// has passed since the app was put into the background.
603+
codePush.sync({ installMode: codePush.InstallMode.ON_NEXT_RESUME, minimumBackgroundDuration: 60 * 5 });
602604
603605
// Download the update silently, and install optional updates
604606
// on the next restart, but install mandatory updates on the next resume.
@@ -681,7 +683,7 @@ Contains details about an update that has been downloaded locally or already ins
681683
- __packageSize__: The size of the code contained within the update, in bytes. *(Number)*
682684
683685
###### Methods
684-
- __install(installMode: codePush.InstallMode = codePush.InstallMode.ON_NEXT_RESTART): Promise<void>__: Installs the update by saving it to the location on disk where the runtime expects to find the latest version of the app. The `installMode` parameter controls when the changes are actually presented to the end user. The default value is to wait until the next app restart to display the changes, but you can refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do.
686+
- __install(installMode: codePush.InstallMode = codePush.InstallMode.ON_NEXT_RESTART, minimumBackgroundDuration = 0): Promise<void>__: Installs the update by saving it to the location on disk where the runtime expects to find the latest version of the app. The `installMode` parameter controls when the changes are actually presented to the end user. The default value is to wait until the next app restart to display the changes, but you can refer to the [`InstallMode`](#installmode) enum reference for a description of the available options and what they do. If the `installMode` parameter is set to `InstallMode.ON_NEXT_RESUME`, then the `minimumBackgroundDuration` parameter allows you to control how long the app must have been in the background before forcing the install after it is resumed.
685687
686688
##### RemotePackage
687689

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@
3232
import java.io.File;
3333
import java.io.IOException;
3434
import java.util.ArrayList;
35+
import java.util.Date;
3536
import java.util.HashMap;
3637
import java.util.List;
3738
import java.util.Map;
3839
import java.util.zip.ZipEntry;
3940
import java.util.zip.ZipFile;
4041

4142
public class CodePush {
42-
4343
private static boolean needToReportRollback = false;
4444
private static boolean isRunningBinaryVersion = false;
4545
private static boolean testConfigurationFlag = false;
@@ -348,8 +348,8 @@ public void clearUpdates() {
348348
}
349349

350350
private class CodePushNativeModule extends ReactContextBaseJavaModule {
351-
352351
private LifecycleEventListener lifecycleEventListener = null;
352+
private int minimumBackgroundDuration = 0;
353353

354354
private void loadBundle() {
355355
Intent intent = mainActivity.getIntent();
@@ -491,7 +491,7 @@ protected Void doInBackground(Object... params) {
491491
}
492492

493493
@ReactMethod
494-
public void installUpdate(final ReadableMap updatePackage, final int installMode, final Promise promise) {
494+
public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) {
495495
AsyncTask asyncTask = new AsyncTask() {
496496
@Override
497497
protected Void doInBackground(Object... params) {
@@ -505,24 +505,41 @@ protected Void doInBackground(Object... params) {
505505
savePendingUpdate(pendingHash, /* isLoading */false);
506506
}
507507

508-
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() &&
509-
lifecycleEventListener == null) {
510-
// Ensure we do not add the listener twice.
511-
lifecycleEventListener = new LifecycleEventListener() {
512-
@Override
513-
public void onHostResume() {
514-
loadBundle();
515-
}
516-
517-
@Override
518-
public void onHostPause() {
519-
}
520-
521-
@Override
522-
public void onHostDestroy() {
523-
}
524-
};
525-
getReactApplicationContext().addLifecycleEventListener(lifecycleEventListener);
508+
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()) {
509+
// Store the minimum duration on the native module as an instance
510+
// variable instead of relying on a closure below, so that any
511+
// subsequent resume-based installs could override it.
512+
CodePushNativeModule.this.minimumBackgroundDuration = minimumBackgroundDuration;
513+
514+
if (lifecycleEventListener == null) {
515+
// Ensure we do not add the listener twice.
516+
lifecycleEventListener = new LifecycleEventListener() {
517+
private Date lastPausedDate = null;
518+
519+
@Override
520+
public void onHostResume() {
521+
// Determine how long the app was in the background and ensure
522+
// that it meets the minimum duration amount of time.
523+
long durationInBackground = (new Date().getTime() - lastPausedDate.getTime()) / 1000;
524+
if (durationInBackground >= CodePushNativeModule.this.minimumBackgroundDuration) {
525+
loadBundle();
526+
}
527+
}
528+
529+
@Override
530+
public void onHostPause() {
531+
// Save the current time so that when the app is later
532+
// resumed, we can detect how long it was in the background.
533+
lastPausedDate = new Date();
534+
}
535+
536+
@Override
537+
public void onHostDestroy() {
538+
}
539+
};
540+
541+
getReactApplicationContext().addLifecycleEventListener(lifecycleEventListener);
542+
}
526543
}
527544

528545
promise.resolve("");

ios/CodePush/CodePush.m

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ @interface CodePush () <RCTBridgeModule>
1212
@implementation CodePush {
1313
BOOL _hasResumeListener;
1414
BOOL _isFirstRunAfterUpdate;
15+
int _minimumBackgroundDuration;
16+
NSDate *_lastResignedDate;
1517
}
1618

1719
RCT_EXPORT_MODULE()
@@ -380,6 +382,27 @@ - (void)savePendingUpdate:(NSString *)packageHash
380382
[preferences synchronize];
381383
}
382384

385+
#pragma mark - Application lifecycle event handlers
386+
387+
// These two handlers will only be registered when there is
388+
// a resume-based update still pending installation.
389+
- (void)applicationWillEnterForeground
390+
{
391+
// Determine how long the app was in the background and ensure
392+
// that it meets the minimum duration amount of time.
393+
int durationInBackground = [[NSDate date] timeIntervalSinceDate:_lastResignedDate];
394+
if (durationInBackground >= _minimumBackgroundDuration) {
395+
[self loadBundle];
396+
}
397+
}
398+
399+
- (void)applicationWillResignActive
400+
{
401+
// Save the current time so that when the app is later
402+
// resumed, we can detect how long it was in the background.
403+
_lastResignedDate = [NSDate date];
404+
}
405+
383406
#pragma mark - JavaScript-exported module methods
384407

385408
/*
@@ -522,16 +545,27 @@ - (void)savePendingUpdate:(NSString *)packageHash
522545
[self savePendingUpdate:updatePackage[PackageHashKey]
523546
isLoading:NO];
524547

525-
if (installMode == CodePushInstallModeOnNextResume && !_hasResumeListener) {
526-
// Ensure we do not add the listener twice.
527-
// Register for app resume notifications so that we
528-
// can check for pending updates which support "restart on resume"
529-
[[NSNotificationCenter defaultCenter] addObserver:self
530-
selector:@selector(loadBundle)
531-
name:UIApplicationWillEnterForegroundNotification
532-
object:[UIApplication sharedApplication]];
533-
_hasResumeListener = YES;
548+
if (installMode == CodePushInstallModeOnNextResume) {
549+
_minimumBackgroundDuration = minimumBackgroundDuration;
550+
551+
if (!_hasResumeListener) {
552+
// Ensure we do not add the listener twice.
553+
// Register for app resume notifications so that we
554+
// can check for pending updates which support "restart on resume"
555+
[[NSNotificationCenter defaultCenter] addObserver:self
556+
selector:@selector(applicationWillEnterForeground)
557+
name:UIApplicationWillEnterForegroundNotification
558+
object:[UIApplication sharedApplication]];
559+
560+
[[NSNotificationCenter defaultCenter] addObserver:self
561+
selector:@selector(applicationWillResignActive)
562+
name:UIApplicationWillResignActiveNotification
563+
object:[UIApplication sharedApplication]];
564+
565+
_hasResumeListener = YES;
566+
}
534567
}
568+
535569
// Signal to JS that the update has been applied.
536570
resolve(nil);
537571
}

package-mixins.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ module.exports = (NativeCodePush) => {
3737
};
3838

3939
const local = {
40-
async install(installMode = NativeCodePush.codePushInstallModeOnNextRestart, updateInstalledCallback) {
40+
async install(installMode = NativeCodePush.codePushInstallModeOnNextRestart, minimumBackgroundDuration = 0, updateInstalledCallback) {
4141
const localPackage = this;
42-
await NativeCodePush.installUpdate(this, installMode);
42+
await NativeCodePush.installUpdate(this, installMode, minimumBackgroundDuration);
4343
updateInstalledCallback && updateInstalledCallback();
4444
if (installMode == NativeCodePush.codePushInstallModeImmediate) {
4545
NativeCodePush.restartApp(false);

react-native-code-push.d.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ interface LocalPackage extends Package {
2525
* Installs the update by saving it to the location on disk where the runtime expects to find the latest version of the app.
2626
*
2727
* @param installMode Indicates when you would like the update changes to take affect for the end-user.
28+
* @param minimumBackgroundDuration For resume-based installs, this specifies the number of seconds the app needs to be in the background before forcing a restart. Defaults to 0 if unspecified.
2829
*/
29-
install(installMode: CodePush.InstallMode): ReactNativePromise<void>;
30+
install(installMode: CodePush.InstallMode, minimumBackgroundDuration?: number): ReactNativePromise<void>;
3031
}
3132

3233
interface Package {
@@ -106,11 +107,25 @@ interface SyncOptions {
106107
deploymentKey?: string;
107108

108109
/**
109-
* Indicates when you would like to "install" the update after downloading it, which includes reloading the JS bundle in order for
110-
* any changes to take affect. Defaults to codePush.InstallMode.ON_NEXT_RESTART.
110+
* Specifies when you would like to install optional updates (i.e. those that aren't marked as mandatory).
111+
* Defaults to codePush.InstallMode.ON_NEXT_RESTART.
111112
*/
112113
installMode?: CodePush.InstallMode;
113114

115+
/**
116+
* Specifies when you would like to install updates which are marked as mandatory.
117+
* Defaults to codePush.InstallMode.IMMEDIATE.
118+
*/
119+
mandatoryInstallMode?: CodePush.InstallMode;
120+
121+
/**
122+
* Specifies the minimum number of seconds that the app needs to have been in the background before restarting the app. This property
123+
* only applies to updates which are installed using `InstallMode.ON_NEXT_RESUME`, and can be useful for getting your update in front
124+
* of end users sooner, without being too obtrusive. Defaults to `0`, which has the effect of applying the update immediately after a
125+
* resume, regardless how long it was in the background.
126+
*/
127+
minimumBackgroundDuration?: number;
128+
114129
/**
115130
* An "options" object used to determine whether a confirmation dialog should be displayed to the end user when an update is available,
116131
* and if so, what strings to use. Defaults to null, which has the effect of disabling the dialog completely. Setting this to any truthy

0 commit comments

Comments
 (0)