@bravemobile/react-native-code-push 12.4.0 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CodePush.podspec CHANGED
@@ -14,7 +14,7 @@ Pod::Spec.new do |s|
14
14
  s.tvos.deployment_target = '15.5'
15
15
  s.preserve_paths = '*.js'
16
16
  s.library = 'z'
17
- s.source_files = 'ios/CodePush/*.{h,m}'
17
+ s.source_files = 'ios/CodePush/*.{h,m,mm}'
18
18
  s.public_header_files = ['ios/CodePush/CodePush.h']
19
19
 
20
20
  # Note: Even though there are copy/pasted versions of some of these dependencies in the repo,
@@ -22,4 +22,17 @@ Pod::Spec.new do |s|
22
22
  # linked properly at a parent workspace level.
23
23
  s.dependency 'React-Core'
24
24
  s.dependency 'SSZipArchive', '~> 2.5.5'
25
+
26
+ if ENV['RCT_NEW_ARCH_ENABLED'] == '1'
27
+ s.compiler_flags = '-DRCT_NEW_ARCH_ENABLED=1'
28
+ s.pod_target_xcconfig = {
29
+ 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
30
+ 'HEADER_SEARCH_PATHS' => "\"$(PODS_ROOT)/Headers/Public/ReactCodegen\" \"${PODS_CONFIGURATION_BUILD_DIR}/ReactCodegen/ReactCodegen.framework/Headers\""
31
+ }
32
+
33
+ s.dependency 'ReactCodegen'
34
+ s.dependency 'RCTRequired'
35
+ s.dependency 'RCTTypeSafety'
36
+ s.dependency 'ReactCommon/turbomodule/core'
37
+ end
25
38
  end
package/README.md CHANGED
@@ -8,11 +8,14 @@
8
8
 
9
9
  ### 🚀 New Architecture support
10
10
 
11
- Supports React Native 0.77 ~ 0.84.
11
+ Supports React Native 0.77 ~ 0.86.
12
12
 
13
13
  > [!NOTE]
14
14
  > If you are using React Native 0.76 or lower, please use version `12.0.2` of this library.
15
15
 
16
+ > [!IMPORTANT]
17
+ > `13.0.0` migrates the native module to TurboModule. A `13.0.0` binary only runs OTA bundles built with `13.0.0` or later, so ship a new binary version and start its release history fresh when you upgrade.
18
+
16
19
  (Tested on the React Native CLI template apps)
17
20
 
18
21
  ### ✅ Requirements
@@ -1,10 +1,17 @@
1
1
  apply plugin: "com.android.library"
2
+ apply plugin: "com.facebook.react"
2
3
 
3
4
  def DEFAULT_COMPILE_SDK_VERSION = 26
4
5
  def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
5
6
  def DEFAULT_TARGET_SDK_VERSION = 26
6
7
  def DEFAULT_MIN_SDK_VERSION = 16
7
8
 
9
+ react {
10
+ jsRootDir = file("../..")
11
+ libraryName = "RNCodePushSpec"
12
+ codegenJavaPackageName = "com.microsoft.codepush.react"
13
+ }
14
+
8
15
  android {
9
16
  namespace "com.microsoft.codepush.react"
10
17
 
@@ -4,18 +4,25 @@ import android.content.Context;
4
4
  import android.content.pm.PackageInfo;
5
5
  import android.content.pm.PackageManager;
6
6
 
7
- import com.facebook.react.ReactPackage;
7
+ import androidx.annotation.NonNull;
8
+ import androidx.annotation.Nullable;
9
+
10
+ import com.facebook.react.TurboReactPackage;
8
11
  import com.facebook.react.bridge.NativeModule;
9
12
  import com.facebook.react.bridge.ReactApplicationContext;
13
+ import com.facebook.react.module.model.ReactModuleInfo;
14
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
10
15
  import com.facebook.react.uimanager.ViewManager;
11
16
 
12
17
  import org.json.JSONException;
13
18
  import org.json.JSONObject;
14
19
 
15
20
  import java.util.ArrayList;
21
+ import java.util.HashMap;
16
22
  import java.util.List;
23
+ import java.util.Map;
17
24
 
18
- public class CodePush implements ReactPackage {
25
+ public class CodePush extends TurboReactPackage {
19
26
 
20
27
  private static boolean sIsRunningBinaryVersion = false;
21
28
  private static boolean sNeedToReportRollback = false;
@@ -300,17 +307,45 @@ public class CodePush implements ReactPackage {
300
307
  }
301
308
 
302
309
  @Override
303
- public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
304
- CodePushNativeModule codePushModule = new CodePushNativeModule(reactApplicationContext, this, mUpdateManager, mTelemetryManager, mSettingsManager);
305
- CodePushDialog dialogModule = new CodePushDialog(reactApplicationContext);
310
+ public @Nullable NativeModule getModule(String name, ReactApplicationContext reactApplicationContext) {
311
+ if (!CodePushNativeModule.NAME.equals(name)) {
312
+ return null;
313
+ }
314
+
315
+ return new CodePushNativeModule(
316
+ reactApplicationContext,
317
+ this,
318
+ mUpdateManager,
319
+ mTelemetryManager,
320
+ mSettingsManager
321
+ );
322
+ }
306
323
 
307
- List<NativeModule> nativeModules = new ArrayList<>();
308
- nativeModules.add(codePushModule);
309
- nativeModules.add(dialogModule);
310
- return nativeModules;
324
+ @Override
325
+ public ReactModuleInfoProvider getReactModuleInfoProvider() {
326
+ return new ReactModuleInfoProvider() {
327
+ @Override
328
+ public Map<String, ReactModuleInfo> getReactModuleInfos() {
329
+ Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
330
+ reactModuleInfoMap.put(
331
+ CodePushNativeModule.NAME,
332
+ new ReactModuleInfo(
333
+ CodePushNativeModule.NAME,
334
+ CodePushNativeModule.class.getName(),
335
+ false,
336
+ false,
337
+ false,
338
+ false,
339
+ true
340
+ )
341
+ );
342
+ return reactModuleInfoMap;
343
+ }
344
+ };
311
345
  }
346
+
312
347
  @Override
313
- public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
348
+ public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactApplicationContext) {
314
349
  return new ArrayList<>();
315
350
  }
316
351
  }
@@ -2,7 +2,6 @@ package com.microsoft.codepush.react;
2
2
 
3
3
  import android.app.Activity;
4
4
  import android.content.SharedPreferences;
5
- import android.os.AsyncTask;
6
5
  import android.os.Handler;
7
6
  import android.os.Looper;
8
7
  import android.view.View;
@@ -20,11 +19,11 @@ import com.facebook.react.bridge.JSBundleLoader;
20
19
  import com.facebook.react.bridge.LifecycleEventListener;
21
20
  import com.facebook.react.bridge.Promise;
22
21
  import com.facebook.react.bridge.ReactApplicationContext;
23
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
24
22
  import com.facebook.react.bridge.ReactMethod;
25
23
  import com.facebook.react.bridge.ReadableMap;
26
24
  import com.facebook.react.bridge.WritableMap;
27
25
  import com.facebook.react.common.annotations.UnstableReactNativeAPI;
26
+ import com.facebook.react.module.annotations.ReactModule;
28
27
  import com.facebook.react.modules.core.DeviceEventManagerModule;
29
28
  import com.facebook.react.modules.core.ReactChoreographer;
30
29
  import com.facebook.react.runtime.ReactHostDelegate;
@@ -37,15 +36,19 @@ import java.io.IOException;
37
36
  import java.lang.reflect.Field;
38
37
  import java.util.ArrayList;
39
38
  import java.util.Date;
40
- import java.util.HashMap;
41
- import java.util.Map;
42
39
  import java.util.UUID;
40
+ import java.util.concurrent.ExecutorService;
41
+ import java.util.concurrent.Executors;
42
+
43
+ @ReactModule(name = CodePushNativeModule.NAME)
44
+ public class CodePushNativeModule extends NativeCodePushSpec {
45
+ public static final String NAME = "CodePush";
43
46
 
44
- public class CodePushNativeModule extends ReactContextBaseJavaModule {
45
47
  private String mBinaryContentsHash = null;
46
48
  private String mClientUniqueId = null;
47
49
  private LifecycleEventListener mLifecycleEventListener = null;
48
50
  private int mMinimumBackgroundDuration = 0;
51
+ private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
49
52
 
50
53
  private CodePush mCodePush;
51
54
  private SettingsManager mSettingsManager;
@@ -76,24 +79,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
76
79
  }
77
80
 
78
81
  @Override
79
- public Map<String, Object> getConstants() {
80
- final Map<String, Object> constants = new HashMap<>();
81
-
82
- constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
83
- constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
84
- constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
85
- constants.put("codePushInstallModeOnNextSuspend", CodePushInstallMode.ON_NEXT_SUSPEND.getValue());
86
-
87
- constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue());
88
- constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue());
89
- constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue());
90
-
91
- return constants;
82
+ public String getName() {
83
+ return NAME;
92
84
  }
93
85
 
94
86
  @Override
95
- public String getName() {
96
- return "CodePush";
87
+ public void invalidate() {
88
+ clearLifecycleEventListener();
89
+ mBackgroundExecutor.shutdownNow();
90
+ super.invalidate();
97
91
  }
98
92
 
99
93
  private void loadBundleLegacy() {
@@ -265,6 +259,21 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
265
259
  return null;
266
260
  }
267
261
 
262
+ private void executeInBackground(Runnable runnable) {
263
+ mBackgroundExecutor.execute(runnable);
264
+ }
265
+
266
+ private void emitDownloadProgressEvent(DownloadProgress downloadProgress) {
267
+ if (mEventEmitterCallback != null) {
268
+ emitOnDownloadProgress(downloadProgress.createWritableMap());
269
+ return;
270
+ }
271
+
272
+ getReactApplicationContext()
273
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
274
+ .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
275
+ }
276
+
268
277
  private void restartAppInternal(boolean onlyIfUpdateIsPending) {
269
278
  if (this._restartInProgress) {
270
279
  CodePushUtils.log("Restart request queued until the current restart is completed");
@@ -292,7 +301,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
292
301
  }
293
302
 
294
303
  @ReactMethod
295
- public void allow(Promise promise) {
304
+ public void allow() {
296
305
  CodePushUtils.log("Re-allowing restarts");
297
306
  this._allowed = true;
298
307
 
@@ -302,58 +311,52 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
302
311
  this._restartQueue.remove(0);
303
312
  this.restartAppInternal(buf);
304
313
  }
305
-
306
- promise.resolve(null);
307
- return;
308
314
  }
309
315
 
310
316
  @ReactMethod
311
- public void clearPendingRestart(Promise promise) {
317
+ public void clearPendingRestart() {
312
318
  this._restartQueue.clear();
313
- promise.resolve(null);
314
- return;
315
319
  }
316
320
 
317
321
  @ReactMethod
318
- public void disallow(Promise promise) {
322
+ public void disallow() {
319
323
  CodePushUtils.log("Disallowing restarts");
320
324
  this._allowed = false;
321
- promise.resolve(null);
322
- return;
323
325
  }
324
326
 
325
327
  @ReactMethod
326
- public void restartApp(boolean onlyIfUpdateIsPending, Promise promise) {
327
- try {
328
- restartAppInternal(onlyIfUpdateIsPending);
329
- promise.resolve(null);
330
- } catch(CodePushUnknownException e) {
331
- CodePushUtils.log(e);
332
- promise.reject(e);
333
- }
328
+ public void restartApp(boolean onlyIfUpdateIsPending) {
329
+ restartAppInternal(onlyIfUpdateIsPending);
334
330
  }
335
331
 
336
332
  @ReactMethod
337
333
  public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
338
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
334
+ executeInBackground(new Runnable() {
339
335
  @Override
340
- protected Void doInBackground(Void... params) {
336
+ public void run() {
341
337
  try {
342
338
  JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
343
339
  mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
344
- private boolean hasScheduledNextFrame = false;
345
- private DownloadProgress latestDownloadProgress = null;
340
+ private volatile boolean hasScheduledNextFrame = false;
341
+ private volatile DownloadProgress latestDownloadProgress = null;
342
+ private final DownloadProgressEventDispatcher progressEventDispatcher = new DownloadProgressEventDispatcher(
343
+ new DownloadProgressEventDispatcher.EventEmitter() {
344
+ @Override
345
+ public void emit(DownloadProgress downloadProgress) {
346
+ emitDownloadProgressEvent(downloadProgress);
347
+ }
348
+ });
346
349
 
347
350
  @Override
348
351
  public void call(DownloadProgress downloadProgress) {
349
- if (!notifyProgress) {
352
+ if (!notifyProgress || downloadProgress == null) {
350
353
  return;
351
354
  }
352
355
 
353
356
  latestDownloadProgress = downloadProgress;
354
357
  // If the download is completed, synchronously send the last event.
355
- if (latestDownloadProgress.isCompleted()) {
356
- dispatchDownloadProgressEvent();
358
+ if (downloadProgress.isCompleted()) {
359
+ progressEventDispatcher.dispatch(downloadProgress);
357
360
  return;
358
361
  }
359
362
 
@@ -368,22 +371,16 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
368
371
  ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() {
369
372
  @Override
370
373
  public void doFrame(long frameTimeNanos) {
371
- if (!latestDownloadProgress.isCompleted()) {
372
- dispatchDownloadProgressEvent();
374
+ try {
375
+ progressEventDispatcher.dispatch(latestDownloadProgress);
376
+ } finally {
377
+ hasScheduledNextFrame = false;
373
378
  }
374
-
375
- hasScheduledNextFrame = false;
376
379
  }
377
380
  });
378
381
  }
379
382
  });
380
383
  }
381
-
382
- public void dispatchDownloadProgressEvent() {
383
- getReactApplicationContext()
384
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
385
- .emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
386
- }
387
384
  });
388
385
 
389
386
  JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
@@ -396,12 +393,8 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
396
393
  CodePushUtils.log(e);
397
394
  promise.reject(e);
398
395
  }
399
-
400
- return null;
401
396
  }
402
- };
403
-
404
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
397
+ });
405
398
  }
406
399
 
407
400
  @ReactMethod
@@ -426,16 +419,17 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
426
419
  }
427
420
 
428
421
  @ReactMethod
429
- public void getUpdateMetadata(final int updateState, final Promise promise) {
430
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
422
+ public void getUpdateMetadata(final double updateStateValue, final Promise promise) {
423
+ executeInBackground(new Runnable() {
431
424
  @Override
432
- protected Void doInBackground(Void... params) {
425
+ public void run() {
433
426
  try {
427
+ int updateState = (int) updateStateValue;
434
428
  JSONObject currentPackage = mUpdateManager.getCurrentPackage();
435
429
 
436
430
  if (currentPackage == null) {
437
431
  promise.resolve(null);
438
- return null;
432
+ return;
439
433
  }
440
434
 
441
435
  Boolean currentUpdateIsPending = false;
@@ -456,7 +450,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
456
450
 
457
451
  if (previousPackage == null) {
458
452
  promise.resolve(null);
459
- return null;
453
+ return;
460
454
  }
461
455
 
462
456
  promise.resolve(CodePushUtils.convertJsonObjectToWritable(previousPackage));
@@ -485,19 +479,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
485
479
  CodePushUtils.log(e);
486
480
  promise.reject(e);
487
481
  }
488
-
489
- return null;
490
482
  }
491
- };
492
-
493
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
483
+ });
494
484
  }
495
485
 
496
486
  @ReactMethod
497
487
  public void getNewStatusReport(final Promise promise) {
498
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
488
+ executeInBackground(new Runnable() {
499
489
  @Override
500
- protected Void doInBackground(Void... params) {
490
+ public void run() {
501
491
  try {
502
492
  if (mCodePush.needToReportRollback()) {
503
493
  mCodePush.setNeedToReportRollback(false);
@@ -509,7 +499,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
509
499
  WritableMap failedStatusReport = mTelemetryManager.getRollbackReport(lastFailedPackage);
510
500
  if (failedStatusReport != null) {
511
501
  promise.resolve(failedStatusReport);
512
- return null;
502
+ return;
513
503
  }
514
504
  } catch (JSONException e) {
515
505
  throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
@@ -521,41 +511,40 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
521
511
  WritableMap newPackageStatusReport = mTelemetryManager.getUpdateReport(CodePushUtils.convertJsonObjectToWritable(currentPackage));
522
512
  if (newPackageStatusReport != null) {
523
513
  promise.resolve(newPackageStatusReport);
524
- return null;
514
+ return;
525
515
  }
526
516
  }
527
517
  } else if (mCodePush.isRunningBinaryVersion()) {
528
518
  WritableMap newAppVersionStatusReport = mTelemetryManager.getBinaryUpdateReport(mCodePush.getAppVersion());
529
519
  if (newAppVersionStatusReport != null) {
530
520
  promise.resolve(newAppVersionStatusReport);
531
- return null;
521
+ return;
532
522
  }
533
523
  } else {
534
524
  WritableMap retryStatusReport = mTelemetryManager.getRetryStatusReport();
535
525
  if (retryStatusReport != null) {
536
526
  promise.resolve(retryStatusReport);
537
- return null;
527
+ return;
538
528
  }
539
529
  }
540
530
 
541
- promise.resolve("");
531
+ promise.resolve(null);
542
532
  } catch(CodePushUnknownException e) {
543
533
  CodePushUtils.log(e);
544
534
  promise.reject(e);
545
535
  }
546
- return null;
547
536
  }
548
- };
549
-
550
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
537
+ });
551
538
  }
552
539
 
553
540
  @ReactMethod
554
- public void installUpdate(final ReadableMap updatePackage, final int installMode, final int minimumBackgroundDuration, final Promise promise) {
555
- AsyncTask<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
541
+ public void installUpdate(final ReadableMap updatePackage, final double installModeValue, final double minimumBackgroundDurationValue, final Promise promise) {
542
+ executeInBackground(new Runnable() {
556
543
  @Override
557
- protected Void doInBackground(Void... params) {
544
+ public void run() {
558
545
  try {
546
+ int installMode = (int) installModeValue;
547
+ int minimumBackgroundDuration = (int) minimumBackgroundDurationValue;
559
548
  mUpdateManager.installPackage(CodePushUtils.convertReadableToJsonObject(updatePackage), mSettingsManager.isPendingUpdate(null));
560
549
 
561
550
  String pendingHash = CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY);
@@ -565,13 +554,12 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
565
554
  mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);
566
555
  }
567
556
 
568
- if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue() ||
569
- // We also add the resume listener if the installMode is IMMEDIATE, because
570
- // if the current activity is backgrounded, we want to reload the bundle when
571
- // it comes back into the foreground.
572
- installMode == CodePushInstallMode.IMMEDIATE.getValue() ||
573
- installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
574
-
557
+ if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()
558
+ // We also add the resume listener if the installMode is IMMEDIATE, because
559
+ // if the current activity is backgrounded, we want to reload the bundle when
560
+ // it comes back into the foreground.
561
+ || installMode == CodePushInstallMode.IMMEDIATE.getValue()
562
+ || installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
575
563
  // Store the minimum duration on the native module as an instance
576
564
  // variable instead of relying on a closure below, so that any
577
565
  // subsequent resume-based installs could override it.
@@ -625,17 +613,13 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
625
613
  }
626
614
  }
627
615
 
628
- promise.resolve("");
616
+ promise.resolve(null);
629
617
  } catch(CodePushUnknownException e) {
630
618
  CodePushUtils.log(e);
631
619
  promise.reject(e);
632
620
  }
633
-
634
- return null;
635
621
  }
636
- };
637
-
638
- asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
622
+ });
639
623
  }
640
624
 
641
625
  @ReactMethod
@@ -692,7 +676,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
692
676
  public void notifyApplicationReady(Promise promise) {
693
677
  try {
694
678
  mSettingsManager.removePendingUpdate();
695
- promise.resolve("");
679
+ promise.resolve(null);
696
680
  } catch(CodePushUnknownException e) {
697
681
  CodePushUtils.log(e);
698
682
  promise.reject(e);
@@ -27,4 +27,8 @@ class DownloadProgress {
27
27
  public boolean isCompleted() {
28
28
  return mTotalBytes == mReceivedBytes;
29
29
  }
30
+
31
+ long getReceivedBytes() {
32
+ return mReceivedBytes;
33
+ }
30
34
  }
@@ -0,0 +1,23 @@
1
+ package com.microsoft.codepush.react;
2
+
3
+ class DownloadProgressEventDispatcher {
4
+ interface EventEmitter {
5
+ void emit(DownloadProgress downloadProgress);
6
+ }
7
+
8
+ private final EventEmitter mEventEmitter;
9
+ private long mLastDispatchedBytes = -1;
10
+
11
+ DownloadProgressEventDispatcher(EventEmitter eventEmitter) {
12
+ mEventEmitter = eventEmitter;
13
+ }
14
+
15
+ synchronized void dispatch(DownloadProgress downloadProgress) {
16
+ if (downloadProgress == null || downloadProgress.getReceivedBytes() <= mLastDispatchedBytes) {
17
+ return;
18
+ }
19
+
20
+ mEventEmitter.emit(downloadProgress);
21
+ mLastDispatchedBytes = downloadProgress.getReceivedBytes();
22
+ }
23
+ }