KNETPersistentRetryQueue to kolejka retry z persystencją,
która przetrwa restart aplikacji. Idealna dla offline-first aplikacji.
import rip.nerd.kitsunenet.util.KNETPersistentRetryQueue
import rip.nerd.kitsunenet.util.KNETPersistentRetryQueue.Priority
val retryQueue = KNETPersistentRetryQueue(context)
// Dodaj request do kolejki po błędzie
try {
client.request(request)
} catch (e: Exception) {
retryQueue.enqueue(request)
}
// Retry wszystkich
val results = retryQueue.retryAll(client)
println("Success: ${results.successCount}")
println("Failed: ${results.failedCount}")
// Priorytety (od najwyższego)
Priority.CRITICAL // Najpierw - płatności, auth
Priority.HIGH // Ważne dane użytkownika
Priority.NORMAL // Standardowe (default)
Priority.LOW // Może poczekać - analytics, logs
// Użycie
retryQueue.enqueue(paymentRequest, Priority.CRITICAL)
retryQueue.enqueue(syncRequest, Priority.NORMAL)
retryQueue.enqueue(analyticsRequest, Priority.LOW)
// Kolejność retry: CRITICAL → HIGH → NORMAL → LOW
val retryQueue = KNETPersistentRetryQueue(
context = context,
maxRetries = 5, // Max prób na request
maxQueueSize = 100, // Max requestów w kolejce
retryDelayMs = 5000, // Delay między retry
exponentialBackoff = true // Exponential delay
)
// Start automatycznego retry w tle
retryQueue.startAutoRetry(
client = client,
intervalMs = 60_000, // Co minutę
onSuccess = { request, response ->
Log.d("Retry", "Success: ${request.request.url}")
},
onFailure = { request, error, attempt ->
Log.w("Retry", "Failed (attempt $attempt): ${error?.message}")
},
onBatchComplete = { result ->
Log.d("Retry", "Batch: ${result.successCount}/${result.retriedCount}")
}
)
// Sprawdź status
if (retryQueue.isAutoRetryRunning()) {
println("Auto-retry is active")
}
// Zatrzymaj
retryQueue.stopAutoRetry()
// Retry z progress callback
val results = retryQueue.retryAll(client) { completed, total ->
updateProgress(completed, total)
}
// Wyniki
results.results.forEach { result ->
if (result.success) {
println("✓ ${result.request.request.url}")
} else {
println("✗ ${result.request.request.url}: ${result.error?.message}")
}
}
println("Duration: ${results.durationMs}ms")
// Dodaj metadata do requestu
retryQueue.enqueue(
request = orderRequest,
priority = Priority.HIGH,
metadata = mapOf(
"order_id" to orderId,
"user_id" to userId,
"action" to "create_order"
)
)
// Pobierz request z metadata
val queued = retryQueue.getById(requestId)
val orderId = queued?.metadata?.get("order_id")
val stats = retryQueue.getStats()
println("Queue size: ${stats.queueSize}")
println("Total enqueued: ${stats.totalEnqueued}")
println("Total retried: ${stats.totalRetried}")
println("Total succeeded: ${stats.totalSucceeded}")
println("Total failed: ${stats.totalFailed}")
// Wiek najstarszego requestu
stats.oldestRequestAge?.let { age ->
println("Oldest: ${age / 1000}s ago")
}
// Podział po priorytetach
stats.byPriority.forEach { (priority, count) ->
println("$priority: $count")
}
class OfflineSyncManager(
context: Context,
private val client: KNETClient,
private val networkObserver: KNETNetworkObserver
) {
private val retryQueue = KNETPersistentRetryQueue(
context = context,
maxRetries = 10,
exponentialBackoff = true
)
init {
// Retry gdy połączenie wraca
networkObserver.onConnectionRestored {
retryQueue.retryAll(client)
}
}
suspend fun sync(data: SyncData) {
val request = KNETRequest.post(
url = "https://api.example.com/sync",
data = data.toMap()
)
try {
client.request(request)
} catch (e: Exception) {
// Zapisz do retry
retryQueue.enqueue(request, Priority.HIGH)
}
}
}
class PaymentService(
context: Context,
private val client: KNETClient
) {
private val retryQueue = KNETPersistentRetryQueue(
context = context,
maxRetries = 5
)
suspend fun processPayment(payment: Payment): PaymentResult {
val request = KNETRequest.post(
url = "https://api.example.com/payments",
data = payment.toMap()
)
return try {
val response = client.request(request)
PaymentResult.Success(response.json())
} catch (e: Exception) {
// Zawsze zachowaj do retry
val id = retryQueue.enqueue(
request = request,
priority = Priority.CRITICAL,
metadata = mapOf(
"payment_id" to payment.id,
"amount" to payment.amount.toString()
)
)
PaymentResult.Queued(id)
}
}
}
class RetryWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
private val retryQueue = KNETPersistentRetryQueue(applicationContext)
private val client = KNETClient()
override suspend fun doWork(): Result {
val result = retryQueue.retryAll(client)
return if (result.failedCount == 0) {
Result.success()
} else {
// Reschedule jeśli są błędy
Result.retry()
}
}
}
// Zaplanuj WorkManager
val request = PeriodicWorkRequestBuilder<RetryWorker>(
15, TimeUnit.MINUTES
).setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
).build()
WorkManager.getInstance(context).enqueue(request)
KNETPersistentRetryQueue| Metoda | Opis |
|---|---|
enqueue(request, priority, metadata) | Dodaje do kolejki |
retryAll(client, onProgress) | Retry wszystkich |
startAutoRetry(...) | Startuje auto-retry |
stopAutoRetry() | Zatrzymuje auto-retry |
getQueue() | Lista requestów |
getById(id) | Pobiera po ID |
remove(id) | Usuwa z kolejki |
clear() | Czyści kolejkę |
size() | Rozmiar kolejki |
getStats() | Statystyki |
shutdown() | Zamyka zasoby |