react-native-notify-kit 9.4.0 → 9.5.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/README.md +10 -4
- package/android/build.gradle +1 -0
- package/android/src/androidTest/java/app/notifee/core/DoScheduledWorkOrderingTest.java +234 -0
- package/android/src/androidTest/java/app/notifee/core/RebootRecoveryTest.java +204 -0
- package/android/src/androidTest/java/app/notifee/core/database/TimingWorkDataRepository.java +56 -0
- package/android/src/androidTest/java/app/notifee/core/database/WorkDataRepositoryRaceTest.java +221 -0
- package/android/src/androidTest/res/drawable/test_icon.xml +14 -0
- package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +160 -79
- package/android/src/main/java/app/notifee/core/NotificationManager.java +153 -96
- package/android/src/main/java/app/notifee/core/database/WorkDataRepository.java +38 -34
- package/android/src/test/java/app/notifee/core/database/WorkDataRepositoryFutureContractTest.java +279 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/version.ts +1 -1
|
@@ -543,15 +543,13 @@ class NotificationManager {
|
|
|
543
543
|
task -> {
|
|
544
544
|
if (notificationType == NOTIFICATION_TYPE_TRIGGER
|
|
545
545
|
|| notificationType == NOTIFICATION_TYPE_ALL) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
},
|
|
554
|
-
LISTENING_CACHED_THREAD_POOL);
|
|
546
|
+
// Chain the Room delete onto the alarm-manager cancel so the outer
|
|
547
|
+
// future — and therefore the JS Promise — only completes after Room
|
|
548
|
+
// has drained. Fixes upstream invertase/notifee#549.
|
|
549
|
+
return Futures.transformAsync(
|
|
550
|
+
NotifeeAlarmManager.cancelAllNotifications(),
|
|
551
|
+
ignored -> WorkDataRepository.getInstance(getApplicationContext()).deleteAll(),
|
|
552
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
555
553
|
}
|
|
556
554
|
return Futures.immediateFuture(null);
|
|
557
555
|
},
|
|
@@ -613,9 +611,11 @@ class NotificationManager {
|
|
|
613
611
|
}))
|
|
614
612
|
.continueWith(
|
|
615
613
|
task -> {
|
|
616
|
-
// delete
|
|
614
|
+
// Chain the Room delete so the outer future — and therefore the JS
|
|
615
|
+
// Promise — only completes after Room has drained. Fixes upstream
|
|
616
|
+
// invertase/notifee#549 for the per-id cancel path.
|
|
617
617
|
if (notificationType != NOTIFICATION_TYPE_DISPLAYED) {
|
|
618
|
-
WorkDataRepository.getInstance(getApplicationContext()).deleteByIds(ids);
|
|
618
|
+
return WorkDataRepository.getInstance(getApplicationContext()).deleteByIds(ids);
|
|
619
619
|
}
|
|
620
620
|
return Futures.immediateFuture(null);
|
|
621
621
|
},
|
|
@@ -691,56 +691,75 @@ class NotificationManager {
|
|
|
691
691
|
|
|
692
692
|
static ListenableFuture<Void> createTriggerNotification(
|
|
693
693
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
694
|
+
int triggerType = ObjectUtils.getInt(triggerBundle.get("type"));
|
|
695
|
+
ListenableFuture<Void> scheduleFuture;
|
|
696
|
+
switch (triggerType) {
|
|
697
|
+
case 0:
|
|
698
|
+
scheduleFuture = createTimestampTriggerNotification(notificationModel, triggerBundle);
|
|
699
|
+
break;
|
|
700
|
+
case 1:
|
|
701
|
+
scheduleFuture = createIntervalTriggerNotification(notificationModel, triggerBundle);
|
|
702
|
+
break;
|
|
703
|
+
default:
|
|
704
|
+
scheduleFuture = Futures.immediateFuture(null);
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
705
707
|
|
|
708
|
+
return Futures.transform(
|
|
709
|
+
scheduleFuture,
|
|
710
|
+
unused -> {
|
|
706
711
|
EventBus.post(
|
|
707
712
|
new NotificationEvent(
|
|
708
713
|
NotificationEvent.TYPE_TRIGGER_NOTIFICATION_CREATED, notificationModel));
|
|
709
|
-
|
|
710
714
|
return null;
|
|
711
|
-
}
|
|
715
|
+
},
|
|
716
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
712
717
|
}
|
|
713
718
|
|
|
714
|
-
|
|
719
|
+
// Returns a future that completes only after the Room insert has persisted AND
|
|
720
|
+
// WorkManager has enqueued the periodic work. Chaining Room-first guarantees the
|
|
721
|
+
// worker reads the row on its first fire even if the process is killed between
|
|
722
|
+
// the JS Promise resolving and WorkManager scheduling. Fixes upstream
|
|
723
|
+
// invertase/notifee#549 for the interval-trigger path.
|
|
724
|
+
static ListenableFuture<Void> createIntervalTriggerNotification(
|
|
715
725
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
716
726
|
IntervalTriggerModel trigger = IntervalTriggerModel.fromBundle(triggerBundle);
|
|
717
727
|
String uniqueWorkName = "trigger:" + notificationModel.getId();
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
728
|
+
Context context = getApplicationContext();
|
|
729
|
+
|
|
730
|
+
ListenableFuture<Void> insertFuture =
|
|
731
|
+
WorkDataRepository.insertTriggerNotification(
|
|
732
|
+
context, notificationModel, triggerBundle, false);
|
|
733
|
+
|
|
734
|
+
return Futures.transform(
|
|
735
|
+
insertFuture,
|
|
736
|
+
unused -> {
|
|
737
|
+
WorkManager workManager = WorkManager.getInstance(context);
|
|
738
|
+
Data.Builder workDataBuilder =
|
|
739
|
+
new Data.Builder()
|
|
740
|
+
.putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
|
|
741
|
+
.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC)
|
|
742
|
+
.putString("id", notificationModel.getId());
|
|
743
|
+
|
|
744
|
+
long interval = trigger.getInterval();
|
|
745
|
+
PeriodicWorkRequest.Builder workRequestBuilder =
|
|
746
|
+
new PeriodicWorkRequest.Builder(Worker.class, interval, trigger.getTimeUnit())
|
|
747
|
+
.setInitialDelay(interval, trigger.getTimeUnit());
|
|
748
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
749
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
750
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
751
|
+
workManager.enqueueUniquePeriodicWork(
|
|
752
|
+
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
753
|
+
return null;
|
|
754
|
+
},
|
|
755
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
741
756
|
}
|
|
742
757
|
|
|
743
|
-
|
|
758
|
+
// Returns a future that completes only after the Room insert has persisted AND
|
|
759
|
+
// the AlarmManager / WorkManager schedule call has run. Same rationale as the
|
|
760
|
+
// interval path above. Fixes upstream invertase/notifee#549 for the timestamp
|
|
761
|
+
// trigger path (AlarmManager by default since 9.1.12).
|
|
762
|
+
static ListenableFuture<Void> createTimestampTriggerNotification(
|
|
744
763
|
NotificationModel notificationModel, Bundle triggerBundle) {
|
|
745
764
|
TimestampTriggerModel trigger = TimestampTriggerModel.fromBundle(triggerBundle);
|
|
746
765
|
|
|
@@ -749,52 +768,56 @@ class NotificationManager {
|
|
|
749
768
|
long delay = trigger.getDelay();
|
|
750
769
|
int interval = trigger.getInterval();
|
|
751
770
|
|
|
752
|
-
// Save in DB
|
|
753
771
|
Data.Builder workDataBuilder =
|
|
754
772
|
new Data.Builder()
|
|
755
773
|
.putString(Worker.KEY_WORK_TYPE, Worker.WORK_TYPE_NOTIFICATION_TRIGGER)
|
|
756
774
|
.putString("id", notificationModel.getId());
|
|
757
775
|
|
|
758
776
|
Boolean withAlarmManager = trigger.getWithAlarmManager();
|
|
777
|
+
Context context = getApplicationContext();
|
|
778
|
+
|
|
779
|
+
ListenableFuture<Void> insertFuture =
|
|
780
|
+
WorkDataRepository.insertTriggerNotification(
|
|
781
|
+
context, notificationModel, triggerBundle, withAlarmManager);
|
|
782
|
+
|
|
783
|
+
return Futures.transform(
|
|
784
|
+
insertFuture,
|
|
785
|
+
unused -> {
|
|
786
|
+
if (withAlarmManager) {
|
|
787
|
+
NotifeeAlarmManager.scheduleTimestampTriggerNotification(notificationModel, trigger);
|
|
788
|
+
return null;
|
|
789
|
+
}
|
|
759
790
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
791
|
-
workRequestBuilder.addTag(uniqueWorkName);
|
|
792
|
-
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
793
|
-
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC);
|
|
794
|
-
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
795
|
-
workManager.enqueueUniquePeriodicWork(
|
|
796
|
-
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
797
|
-
}
|
|
791
|
+
WorkManager workManager = WorkManager.getInstance(context);
|
|
792
|
+
|
|
793
|
+
// WorkManager - One time trigger
|
|
794
|
+
if (interval == -1) {
|
|
795
|
+
OneTimeWorkRequest.Builder workRequestBuilder =
|
|
796
|
+
new OneTimeWorkRequest.Builder(Worker.class);
|
|
797
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
798
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
799
|
+
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_ONE_TIME);
|
|
800
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
801
|
+
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
802
|
+
workManager.enqueueUniqueWork(
|
|
803
|
+
uniqueWorkName, ExistingWorkPolicy.REPLACE, workRequestBuilder.build());
|
|
804
|
+
} else {
|
|
805
|
+
// WorkManager - repeat trigger
|
|
806
|
+
PeriodicWorkRequest.Builder workRequestBuilder =
|
|
807
|
+
new PeriodicWorkRequest.Builder(
|
|
808
|
+
Worker.class, trigger.getInterval(), trigger.getTimeUnit());
|
|
809
|
+
|
|
810
|
+
workRequestBuilder.addTag(Worker.WORK_TYPE_NOTIFICATION_TRIGGER);
|
|
811
|
+
workRequestBuilder.addTag(uniqueWorkName);
|
|
812
|
+
workRequestBuilder.setInitialDelay(delay, TimeUnit.SECONDS);
|
|
813
|
+
workDataBuilder.putString(Worker.KEY_WORK_REQUEST, Worker.WORK_REQUEST_PERIODIC);
|
|
814
|
+
workRequestBuilder.setInputData(workDataBuilder.build());
|
|
815
|
+
workManager.enqueueUniquePeriodicWork(
|
|
816
|
+
uniqueWorkName, ExistingPeriodicWorkPolicy.UPDATE, workRequestBuilder.build());
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
},
|
|
820
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
798
821
|
}
|
|
799
822
|
|
|
800
823
|
static ListenableFuture<List<Bundle>> getDisplayedNotifications() {
|
|
@@ -982,17 +1005,51 @@ class NotificationManager {
|
|
|
982
1005
|
new ExtendedListenableFuture<>(result)
|
|
983
1006
|
.addOnCompleteListener(
|
|
984
1007
|
(e2, _unused) -> {
|
|
985
|
-
completer.set(Result.success());
|
|
986
1008
|
if (e2 != null) {
|
|
987
1009
|
Logger.e(TAG, "Failed to display notification", e2);
|
|
1010
|
+
completer.set(Result.success());
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
String workerRequestType = data.getString(Worker.KEY_WORK_REQUEST);
|
|
1014
|
+
if (workerRequestType != null
|
|
1015
|
+
&& workerRequestType.equals(Worker.WORK_REQUEST_ONE_TIME)) {
|
|
1016
|
+
// DO NOT reorder — completer.set must only run after the
|
|
1017
|
+
// delete future completes, otherwise WorkManager may start
|
|
1018
|
+
// a new work instance that reads the stale row. Previously
|
|
1019
|
+
// completer.set fired before the delete was enqueued, leaving
|
|
1020
|
+
// a zombie row that reboot recovery would resurrect as a
|
|
1021
|
+
// ghost alarm. See #549 audit Part B, Caller #5.
|
|
1022
|
+
//
|
|
1023
|
+
// Note: CallbackToFutureAdapter.Completer.set() returns
|
|
1024
|
+
// false on double-set, it does NOT throw. This is an
|
|
1025
|
+
// androidx contract. Do not refactor under the assumption
|
|
1026
|
+
// that double-set is dangerous.
|
|
1027
|
+
Futures.addCallback(
|
|
1028
|
+
WorkDataRepository.getInstance(getApplicationContext())
|
|
1029
|
+
.deleteById(id),
|
|
1030
|
+
new FutureCallback<Void>() {
|
|
1031
|
+
@Override
|
|
1032
|
+
public void onSuccess(Void unused) {
|
|
1033
|
+
completer.set(Result.success());
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
@Override
|
|
1037
|
+
public void onFailure(@NonNull Throwable t) {
|
|
1038
|
+
// Notification was already displayed; a failed
|
|
1039
|
+
// delete leaves an orphan row that the next
|
|
1040
|
+
// cancelAll or app restart will clean up. Still
|
|
1041
|
+
// report success so WorkManager doesn't retry
|
|
1042
|
+
// the already-displayed notification.
|
|
1043
|
+
Logger.e(
|
|
1044
|
+
TAG,
|
|
1045
|
+
"Failed to delete one-time trigger row after" + " display",
|
|
1046
|
+
new Exception(t));
|
|
1047
|
+
completer.set(Result.success());
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
LISTENING_CACHED_THREAD_POOL);
|
|
988
1051
|
} else {
|
|
989
|
-
|
|
990
|
-
if (workerRequestType != null
|
|
991
|
-
&& workerRequestType.equals(Worker.WORK_REQUEST_ONE_TIME)) {
|
|
992
|
-
// delete database entry if work is a one-time request
|
|
993
|
-
WorkDataRepository.getInstance(getApplicationContext())
|
|
994
|
-
.deleteById(id);
|
|
995
|
-
}
|
|
1052
|
+
completer.set(Result.success());
|
|
996
1053
|
}
|
|
997
1054
|
},
|
|
998
1055
|
LISTENING_CACHED_THREAD_POOL);
|
|
@@ -20,13 +20,16 @@ package app.notifee.core.database;
|
|
|
20
20
|
import android.content.Context;
|
|
21
21
|
import android.os.Bundle;
|
|
22
22
|
import androidx.annotation.NonNull;
|
|
23
|
+
import androidx.annotation.VisibleForTesting;
|
|
23
24
|
import app.notifee.core.model.NotificationModel;
|
|
24
25
|
import app.notifee.core.utility.ObjectUtils;
|
|
25
26
|
import com.google.common.util.concurrent.ListenableFuture;
|
|
27
|
+
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
26
28
|
import java.util.List;
|
|
27
29
|
|
|
28
30
|
public class WorkDataRepository {
|
|
29
|
-
private WorkDataDao mWorkDataDao;
|
|
31
|
+
private final WorkDataDao mWorkDataDao;
|
|
32
|
+
private final ListeningExecutorService mExecutor;
|
|
30
33
|
private static WorkDataRepository mInstance;
|
|
31
34
|
|
|
32
35
|
public static @NonNull WorkDataRepository getInstance(@NonNull Context context) {
|
|
@@ -42,52 +45,56 @@ public class WorkDataRepository {
|
|
|
42
45
|
public WorkDataRepository(Context context) {
|
|
43
46
|
NotifeeCoreDatabase db = NotifeeCoreDatabase.getDatabase(context);
|
|
44
47
|
mWorkDataDao = db.workDao();
|
|
48
|
+
mExecutor = NotifeeCoreDatabase.databaseWriteListeningExecutor;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
@VisibleForTesting
|
|
52
|
+
WorkDataRepository(@NonNull WorkDataDao dao, @NonNull ListeningExecutorService executor) {
|
|
53
|
+
mWorkDataDao = dao;
|
|
54
|
+
mExecutor = executor;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Submit a DAO write to the listening executor and return a future that
|
|
58
|
+
// completes when the DAO call returns (success or exception). Using the
|
|
59
|
+
// submit(Runnable, result) overload avoids the Callable<Object> inference
|
|
60
|
+
// that would otherwise require a cast at every call site.
|
|
61
|
+
private @NonNull ListenableFuture<Void> submitWrite(@NonNull Runnable work) {
|
|
62
|
+
return mExecutor.submit(work, (Void) null);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public @NonNull ListenableFuture<Void> insert(WorkDataEntity workData) {
|
|
66
|
+
return submitWrite(() -> mWorkDataDao.insert(workData));
|
|
52
67
|
}
|
|
53
68
|
|
|
54
69
|
public ListenableFuture<WorkDataEntity> getWorkDataById(String id) {
|
|
55
|
-
return
|
|
56
|
-
() -> mWorkDataDao.getWorkDataById(id));
|
|
70
|
+
return mExecutor.submit(() -> mWorkDataDao.getWorkDataById(id));
|
|
57
71
|
}
|
|
58
72
|
|
|
59
73
|
public ListenableFuture<List<WorkDataEntity>> getAllWithAlarmManager(Boolean withAlarmManager) {
|
|
60
|
-
return
|
|
61
|
-
() -> mWorkDataDao.getAllWithAlarmManager(withAlarmManager));
|
|
74
|
+
return mExecutor.submit(() -> mWorkDataDao.getAllWithAlarmManager(withAlarmManager));
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
public ListenableFuture<List<WorkDataEntity>> getAll() {
|
|
65
|
-
return
|
|
78
|
+
return mExecutor.submit(() -> mWorkDataDao.getAll());
|
|
66
79
|
}
|
|
67
80
|
|
|
68
|
-
public
|
|
69
|
-
|
|
70
|
-
() -> {
|
|
71
|
-
mWorkDataDao.deleteById(id);
|
|
72
|
-
});
|
|
81
|
+
public @NonNull ListenableFuture<Void> deleteById(String id) {
|
|
82
|
+
return submitWrite(() -> mWorkDataDao.deleteById(id));
|
|
73
83
|
}
|
|
74
84
|
|
|
75
|
-
public
|
|
76
|
-
|
|
77
|
-
() -> {
|
|
78
|
-
mWorkDataDao.deleteByIds(ids);
|
|
79
|
-
});
|
|
85
|
+
public @NonNull ListenableFuture<Void> deleteByIds(List<String> ids) {
|
|
86
|
+
return submitWrite(() -> mWorkDataDao.deleteByIds(ids));
|
|
80
87
|
}
|
|
81
88
|
|
|
82
|
-
public
|
|
83
|
-
|
|
84
|
-
() -> {
|
|
85
|
-
mWorkDataDao.deleteAll();
|
|
86
|
-
});
|
|
89
|
+
public @NonNull ListenableFuture<Void> deleteAll() {
|
|
90
|
+
return submitWrite(() -> mWorkDataDao.deleteAll());
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
public static
|
|
90
|
-
|
|
93
|
+
public static @NonNull ListenableFuture<Void> insertTriggerNotification(
|
|
94
|
+
@NonNull Context context,
|
|
95
|
+
NotificationModel notificationModel,
|
|
96
|
+
Bundle triggerBundle,
|
|
97
|
+
Boolean withAlarmManager) {
|
|
91
98
|
WorkDataEntity workData =
|
|
92
99
|
new WorkDataEntity(
|
|
93
100
|
notificationModel.getId(),
|
|
@@ -95,13 +102,10 @@ public class WorkDataRepository {
|
|
|
95
102
|
ObjectUtils.bundleToBytes(triggerBundle),
|
|
96
103
|
withAlarmManager);
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
return getInstance(context).insert(workData);
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
public
|
|
102
|
-
|
|
103
|
-
() -> {
|
|
104
|
-
mWorkDataDao.update(workData);
|
|
105
|
-
});
|
|
108
|
+
public @NonNull ListenableFuture<Void> update(WorkDataEntity workData) {
|
|
109
|
+
return submitWrite(() -> mWorkDataDao.update(workData));
|
|
106
110
|
}
|
|
107
111
|
}
|