First attempt

This commit is contained in:
Benoit Marty 2025-11-19 16:56:01 +01:00
parent a9d04b7384
commit e0c829d887
4 changed files with 51 additions and 31 deletions

View file

@ -29,30 +29,29 @@ class SyncNotificationWorkManagerRequest(
private val workerDataConverter: WorkerDataConverter, private val workerDataConverter: WorkerDataConverter,
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
) : WorkManagerRequest { ) : WorkManagerRequest {
override fun build(): Result<WorkRequest> { override fun build(): List<Result<WorkRequest>> {
if (notificationEventRequests.isEmpty()) { if (notificationEventRequests.isEmpty()) {
return Result.failure(InvalidParameterException("notificationEventRequests cannot be empty")) return listOf(Result.failure(InvalidParameterException("notificationEventRequests cannot be empty")))
}
val data = workerDataConverter.serialize(notificationEventRequests).getOrElse {
return Result.failure(it)
} }
Timber.d("Scheduling ${notificationEventRequests.size} notification requests with WorkManager for $sessionId") Timber.d("Scheduling ${notificationEventRequests.size} notification requests with WorkManager for $sessionId")
return Result.success( return workerDataConverter.serialize(notificationEventRequests).map {
OneTimeWorkRequestBuilder<FetchNotificationsWorker>() it.map { data ->
.setInputData(data) OneTimeWorkRequestBuilder<FetchNotificationsWorker>()
.apply { .setInputData(data)
// Expedited workers aren't needed on Android 12 or lower: .apply {
// They force displaying a foreground sync notification for no good reason, since they sync almost immediately anyway // Expedited workers aren't needed on Android 12 or lower:
// See https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-work#backwards-compat // They force displaying a foreground sync notification for no good reason, since they sync almost immediately anyway
if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { // See https://developer.android.com/develop/background-work/background-tasks/persistent/getting-started/define-work#backwards-compat
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) if (buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}
} }
} .setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC))
.setTraceTag(workManagerTag(sessionId, WorkManagerRequestType.NOTIFICATION_SYNC)) // TODO investigate using this instead of the resolver queue
// TODO investigate using this instead of the resolver queue // .setInputMerger()
// .setInputMerger() .build()
.build() }
) }
} }
@Serializable @Serializable

View file

@ -12,6 +12,7 @@ import androidx.work.Data
import androidx.work.workDataOf import androidx.work.workDataOf
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.androidutils.json.JsonProvider
import io.element.android.libraries.core.extensions.mapCatchingExceptions
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -23,12 +24,29 @@ import timber.log.Timber
class WorkerDataConverter( class WorkerDataConverter(
private val json: JsonProvider, private val json: JsonProvider,
) { ) {
fun serialize(notificationEventRequests: List<NotificationEventRequest>): Result<Data> { fun serialize(notificationEventRequests: List<NotificationEventRequest>): List<Result<Data>> {
return serializeRequests(notificationEventRequests)
.fold(
onSuccess = {
listOf(Result.success(it))
},
onFailure = {
// Perform serialization on sublists, workDataOf may have failed because of size limit
Timber.w(it, "Failed to serialize ${notificationEventRequests.size} notification requests, trying with chunks of $CHUNK_SIZE.")
notificationEventRequests.chunked(CHUNK_SIZE).map { chunk ->
serializeRequests(chunk)
}
},
)
}
private fun serializeRequests(notificationEventRequests: List<NotificationEventRequest>): Result<Data> {
return runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) } return runCatchingExceptions { json().encodeToString(notificationEventRequests.map { it.toData() }) }
.onFailure { .onFailure {
Timber.e(it, "Failed to serialize notification requests") Timber.e(it, "Failed to serialize notification requests")
} }
.map { str -> .mapCatchingExceptions { str ->
// Note: workDataOf can fail if the data is too large
workDataOf(REQUESTS_KEY to str) workDataOf(REQUESTS_KEY to str)
} }
} }
@ -51,6 +69,7 @@ class WorkerDataConverter(
companion object { companion object {
private const val REQUESTS_KEY = "requests" private const val REQUESTS_KEY = "requests"
private const val CHUNK_SIZE = 20
} }
} }

View file

@ -11,5 +11,5 @@ package io.element.android.libraries.workmanager.api
import androidx.work.WorkRequest import androidx.work.WorkRequest
interface WorkManagerRequest { interface WorkManagerRequest {
fun build(): Result<WorkRequest> fun build(): List<Result<WorkRequest>>
} }

View file

@ -27,14 +27,16 @@ class DefaultWorkManagerScheduler(
private val workManager by lazy { WorkManager.getInstance(context) } private val workManager by lazy { WorkManager.getInstance(context) }
override fun submit(workManagerRequest: WorkManagerRequest) { override fun submit(workManagerRequest: WorkManagerRequest) {
workManagerRequest.build().fold( workManagerRequest.build().forEach {
onSuccess = { it.fold(
workManager.enqueue(it) onSuccess = { workRequest ->
}, workManager.enqueue(workRequest)
onFailure = { },
Timber.e(it, "Failed to build WorkManager request $workManagerRequest") onFailure = {
} Timber.e(it, "Failed to build WorkManager request $workManagerRequest")
) }
)
}
} }
override fun cancel(sessionId: SessionId) { override fun cancel(sessionId: SessionId) {