Skip to content

Commit

Permalink
mod: let retryTask run on ApiResponse.Failure
Browse files Browse the repository at this point in the history
mod: use workQuery to check work is running
mod: replace unique work when enqueueing work by clicking instant attendance
mod: add boolean value to attend check-in worker to separate work was enqueued by clicking instant attendance
  • Loading branch information
joeloewi7178 committed Mar 14, 2024
1 parent 135a22c commit 1b38114
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ import androidx.compose.material3.rememberSwipeToDismissBoxState
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.snapshotFlow
Expand All @@ -58,19 +58,22 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.os.bundleOf
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.flowWithLifecycle
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.google.firebase.Firebase
Expand Down Expand Up @@ -252,12 +255,14 @@ fun AttendanceWithGamesItem(
{ attendance ->
Firebase.analytics.logEvent("instant_attend_click", bundleOf())

val oneTimeWork =
AttendCheckInEventWorker.buildOneTimeWork(attendanceId = attendance.id)
val oneTimeWork = AttendCheckInEventWorker.buildOneTimeWork(
attendanceId = attendance.id,
isInstantAttendance = true
)

WorkManager.getInstance(context).beginUniqueWork(
attendance.oneTimeAttendCheckInEventWorkerName.toString(),
ExistingWorkPolicy.APPEND_OR_REPLACE,
ExistingWorkPolicy.REPLACE,
oneTimeWork
).enqueue()

Expand Down Expand Up @@ -401,14 +406,24 @@ private fun DismissContent(
},
trailingContent = {
val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val workerName =
attendanceWithGames().value.attendance.oneTimeAttendCheckInEventWorkerName.toString()
val isRunning by remember(context, workerName) {
val isRunningFlow = remember(context, workerName) {
WorkManager.getInstance(context)
.getWorkInfosForUniqueWorkFlow(workerName)
.map { list -> list.any { it.state == WorkInfo.State.RUNNING } }
.getWorkInfosFlow(
WorkQuery.Builder
.fromUniqueWorkNames(listOf(workerName))
.addStates(listOf(WorkInfo.State.RUNNING))
.build()
)
.catch { }
.map { it.isNotEmpty() }
.flowOn(Dispatchers.IO)
}.collectAsState(initial = false)
}
val isRunning by produceState(initialValue = false) {
isRunningFlow.flowWithLifecycle(lifecycleOwner.lifecycle).collect { value = it }
}

IconButton(
enabled = !isRunning,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class AttendCheckInEventWorker @AssistedInject constructor(
private val _firstTriggeredTimestamp by lazy {
inputData.getLong(FIRST_TRIGGERED_TIMESTAMP, Instant.now().toEpochMilli())
}
private val _isInstantAttendance by lazy { inputData.getBoolean(IS_INSTANT_ATTENDANCE, false) }

override suspend fun getForegroundInfo(): ForegroundInfo =
notificationGenerator.createForegroundInfo(_attendanceId.toInt())
Expand Down Expand Up @@ -205,21 +206,41 @@ class AttendCheckInEventWorker @AssistedInject constructor(
is HoYoLABUnsuccessfulResponseException -> {
when (val retCode = HoYoLABRetCode.findByCode(cause.retCode)) {
HoYoLABRetCode.TooManyRequests, HoYoLABRetCode.TooManyRequestsGenshinImpact -> {
//do not make log, not to skip this game when retry

notificationGenerator.createAttendanceRetryScheduledNotification(
nickname = attendanceWithGames.attendance.nickname,
contentText = context.getString(R.string.attendance_retry_too_many_requests_error)
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
if (_isInstantAttendance) {
//make log and do not retry if this work was enqueued by clicking instant attendance

addFailureLog(_attendanceId, game.type, cause)

createUnsuccessfulAttendanceNotification(
nickname = attendanceWithGames.attendance.nickname,
hoYoLABGame = game.type,
region = game.region,
hoYoLABUnsuccessfulResponseException = cause
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
}

Result.success()
} else {
//do not make log, not to skip this game when retry
notificationGenerator.createAttendanceRetryScheduledNotification(
nickname = attendanceWithGames.attendance.nickname,
contentText = context.getString(R.string.attendance_retry_too_many_requests_error)
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
}

//good to retry
Result.retry()
}

//good to retry
Result.retry()
}

else -> {
Expand Down Expand Up @@ -265,29 +286,49 @@ class AttendCheckInEventWorker @AssistedInject constructor(
}

else -> {
//do not make log, not to pass this game when retry
notificationGenerator.createAttendanceRetryScheduledNotification(
nickname = attendanceWithGames.attendance.nickname
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
}
if (_isInstantAttendance) {
//make log and do not retry if this work was enqueued by clicking instant attendance
addFailureLog(_attendanceId, game.type, cause)

notificationGenerator.createUnsuccessfulAttendanceNotification(
nickname = attendanceWithGames.attendance.nickname,
hoYoLABGame = game.type,
attendanceId = _attendanceId
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
}

//these errors are not hoyolab server's errors, but networks errors, generally
//do retry
Firebase.crashlytics.log("runAttemptCount: $runAttemptCount")
Result.retry()
Result.success()
} else {
//do not make log, not to pass this game when retry
notificationGenerator.createAttendanceRetryScheduledNotification(
nickname = attendanceWithGames.attendance.nickname
).let { notification ->
notificationGenerator.safeNotify(
UUID.randomUUID().toString(),
game.type.gameId,
notification
)
}

//these errors are not hoyolab server's errors, but networks errors, generally
//do retry
Firebase.crashlytics.log("runAttemptCount: $runAttemptCount")
Result.retry()
}
}
}
}
)
}
}.fold(
onSuccess = { results ->
if (results.contains(Result.retry())) {
//do not retry if this work was enqueued by clicking instant attendance
if (results.contains(Result.retry()) && !_isInstantAttendance) {
return@withContext Result.retry()
}

Expand All @@ -313,18 +354,21 @@ class AttendCheckInEventWorker @AssistedInject constructor(
companion object {
const val ATTENDANCE_ID = "attendanceId"
const val FIRST_TRIGGERED_TIMESTAMP = "triggeredTimestamp"
const val IS_INSTANT_ATTENDANCE = "isInstantAttendance"

fun buildOneTimeWork(
attendanceId: Long,
triggeredTimestamp: Long = Instant.now().toEpochMilli(),
isInstantAttendance: Boolean = false,
constraints: Constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
) = OneTimeWorkRequestBuilder<AttendCheckInEventWorker>()
.setInputData(
workDataOf(
ATTENDANCE_ID to attendanceId,
FIRST_TRIGGERED_TIMESTAMP to triggeredTimestamp
FIRST_TRIGGERED_TIMESTAMP to triggeredTimestamp,
IS_INSTANT_ATTENDANCE to isInstantAttendance
)
)
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ suspend fun <T : Any> runAndRetryWithExponentialBackOff(
task = task
)

private tailrec suspend fun <T : Any> retryTask(
internal tailrec suspend fun <T : Any> retryTask(
attempt: Int = 1,
initialDelay: Int,
retryFactor: Float,
Expand All @@ -46,25 +46,17 @@ private tailrec suspend fun <T : Any> retryTask(
return when (val apiResponse = task()) {
is ApiResponse.Success -> apiResponse
is ApiResponse.Failure -> {
when (apiResponse) {
is ApiResponse.Failure.Error -> {
apiResponse
}

is ApiResponse.Failure.Exception -> {
if (attempt < maxAttempts) {
retryTask(
attempt = attempt + 1,
initialDelay = initialDelay,
retryFactor = retryFactor,
maxAttempts = maxAttempts,
maxDelayMillis = maxDelayMillis,
task = task
)
} else {
apiResponse
}
}
if (attempt < maxAttempts) {
retryTask(
attempt = attempt + 1,
initialDelay = initialDelay,
retryFactor = retryFactor,
maxAttempts = maxAttempts,
maxDelayMillis = maxDelayMillis,
task = task
)
} else {
apiResponse
}
}
}
Expand Down

0 comments on commit 1b38114

Please sign in to comment.