Wie kann man doppelte Datenbankeinträge verhindern, wenn in Android mehrere regelmäßige Zahlungen mit unterschiedlichen
Posted: 03 Jan 2025, 09:37
Ich möchte dem Benutzer eine reguläre Zahlungsoption hinzufügen. Wenn der Benutzer eine Zahlung in die Datenbank einfügt und die Häufigkeit auf „Täglich“ oder eine andere Option einstellt, basierend auf der Anzahl der verbleibenden Zahlungen, sollten die Daten entsprechend der ausgewählten Häufigkeit hinzugefügt werden. Danach sollten die verbleibenden Zahlungen aktualisiert werden. Wenn der Benutzer eine der täglichen Zahlungen löscht, sollte das System die Zahlung über den regulären Zahlungsplan erneut hinzufügen.
PaymentReceiver
UserViewModel
DAO
PaymentReceiver
Code: Select all
class PaymentReceiver : BroadcastReceiver() {
companion object {
private val lastExecutionTimes = ConcurrentHashMap()
}
override fun onReceive(context: Context, intent: Intent) {
val title = intent.getStringExtra("TITLE")
val message = intent.getStringExtra("MESSAGE")
val paymentId = intent.getIntExtra("paymentId", 0)
val frequency = intent.getStringExtra("FREQUENCY") ?: return
val currentTime = System.currentTimeMillis()
val userViewModel = UserViewModel(Application.getContext())
val paymentManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val soundUri = Uri.parse("android.resource://${context.packageName}/raw/notification")
val payment = runBlocking {
userViewModel.getRegularPaymentById(paymentId.toLong())
}
if (payment == null) {
Log.e("PaymentReceiver", "Payment not found for ID: $paymentId")
return
}
if (lastExecutionTimes[paymentId]?.let { currentTime - it < 5000 } == true) {
Log.d("PaymentReceiver", "Duplicate execution avoided for paymentId: $paymentId")
return
}
lastExecutionTimes[paymentId] = currentTime
if (payment?.remainingPayments!! = Build.VERSION_CODES.O) {
val channel = NotificationChannel(
"PAYMENT_CHANNEL",
"Payments",
NotificationManager.IMPORTANCE_HIGH
).apply {
setSound(soundUri, null)
enableVibration(true)
lockscreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
}
paymentManager.createNotificationChannel(channel)
}
val notificationIntent = Intent(context, SplashActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent = PendingIntent.getActivity(
context,
paymentId,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val builder = NotificationCompat.Builder(context, "PAYMENT_CHANNEL")
.setSmallIcon(R.mipmap.ic_logo)
.setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_logo))
.setContentTitle(title)
.setContentText(message)
.setSound(soundUri)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
paymentManager.notify(paymentId, builder.build())
if (payment?.remainingPayments!! > 0) {
scheduleNextReminder(context, title, message, paymentId, frequency, userViewModel)
}
}
private fun scheduleNextReminder(
context: Context,
title: String?,
message: String?,
paymentId: Int,
frequency: String,
userViewModel: UserViewModel
) {
val nextAlarmTime = calculateNextDueDate(System.currentTimeMillis(), frequency)
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val newIntent = Intent(context, PaymentReceiver::class.java).apply {
putExtra("TITLE", title)
putExtra("MESSAGE", message)
putExtra("paymentId", paymentId)
putExtra("FREQUENCY", frequency)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
paymentId,
newIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
nextAlarmTime,
pendingIntent
)
Log.d("Receiver", "onReceive: *************")
userViewModel.processRegularPayments()
}
private val currentDate: Long get() = resetTimeToZero(System.currentTimeMillis())
private fun calculateNextDueDate(startDate: Long, frequency: String): Long {
val calendar = Calendar.getInstance().apply {
timeInMillis = startDate
}
if (startDate == currentDate) {
return currentDate
}
when (frequency) {
"Daily" -> calendar.add(Calendar.DAY_OF_YEAR, 1)
"Weekly" -> calendar.add(Calendar.WEEK_OF_YEAR, 1)
"Every two weeks" -> calendar.add(Calendar.WEEK_OF_YEAR, 2)
"Monthly" -> calendar.add(Calendar.MONTH, 1)
"Every two months" -> calendar.add(Calendar.MONTH, 2)
"Every three months" -> calendar.add(Calendar.MONTH, 3)
"Every four months" -> calendar.add(Calendar.MONTH, 4)
"Every six months" -> calendar.add(Calendar.MONTH, 6)
"Every year" -> calendar.add(Calendar.YEAR, 1)
}
return Utils.resetTimeToZero(calendar.timeInMillis)
}
private fun cancelAlarm(context: Context, paymentId: Int) {
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, PaymentReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(
context,
paymentId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
alarmManager.cancel(pendingIntent)
Log.d("PaymentReceiver", "Alarm canceled for paymentId: $paymentId")
}
}
Code: Select all
class UserViewModel(application: Context) : ViewModel() {
val repository: UserRepository
private val mutex = Mutex()
private var isProcessingPayments = false
init {
val userDao = UserDatabase.getUserDatabase(application).userDao()
repository = UserRepository(userDao)
}
fun getRegularPaymentById(id: Long): RegularPayment? {
return repository.getRegularPaymentById(id)
}
fun getRegularPayments() {
viewModelScope.launch {
_getRegularPayments.value = repository.getAllRegularPayments()
}
}
fun insertOrUpdateRegularPayment(regularPayment: RegularPayment) {
viewModelScope.launch(Dispatchers.IO) {
val paymentId = repository.insertOrUpdateRegularPayment(regularPayment)
regularPayment.id = paymentId
setPayment(Application.getContext(), regularPayment)
}
}
fun setPayment(context: Context, regularPayment: RegularPayment) {
Log.d("UserView", "setPayment: ${regularPayment.id}")
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(context, PaymentReceiver::class.java).apply {
putExtra("paymentId", regularPayment.id.toInt())
putExtra("TITLE", "Payment Added")
putExtra(
"MESSAGE",
"Added payment: ${regularPayment.paymentName} of ${regularPayment.amount}"
)
putExtra("FREQUENCY", regularPayment.frequency)
}
val pendingIntent = PendingIntent.getBroadcast(
context,
regularPayment.id.toInt(),
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
if (regularPayment.remainingPayments
addPaymentToUserData(payment)
}
}
} finally {
isProcessingPayments = false
}
}
}
private fun shouldAddPayment(payment: RegularPayment): Boolean {
val dueDate = calculateNextDueDate(payment.startDate, payment.frequency)
return payment.remainingPayments > 0 && dueDate calendar.add(Calendar.DAY_OF_YEAR, 1)
"Weekly" -> calendar.add(Calendar.WEEK_OF_YEAR, 1)
"Every two weeks" -> calendar.add(Calendar.WEEK_OF_YEAR, 2)
"Monthly" -> calendar.add(Calendar.MONTH, 1)
"Every two months" -> calendar.add(Calendar.MONTH, 2)
"Every three months" -> calendar.add(Calendar.MONTH, 3)
"Every four months" -> calendar.add(Calendar.MONTH, 4)
"Every six months" -> calendar.add(Calendar.MONTH, 6)
"Every year" -> calendar.add(Calendar.YEAR, 1)
}
return resetTimeToZero(calendar.timeInMillis)
}
Code: Select all
@Upsert
suspend fun insertOrUpdateRegularPayment(regularPayment: RegularPayment):Long
@Query("SELECT * FROM regular_payment")
suspend fun getAllRegularPayments(): List
@Query("SELECT * FROM regular_payment WHERE id = :id")
fun getRegularPaymentById(id:Long): RegularPayment?
@Query("UPDATE regular_payment SET remainingPayments = :remaining WHERE id = :id")
suspend fun updateRemainingPayments(id: Long, remaining: Int)
@Query("""
SELECT
(SELECT COUNT(*) FROM user_data WHERE regularPaymentID = :paymentId AND expenseName = :paymentName AND date = :date) AS addedCount,
(SELECT COUNT(*) FROM deleted_payment WHERE paymentId = :paymentId AND deleteDate = :date) AS deletedCount
""")
fun isPaymentProcessable(paymentId: Long, paymentName: String, date: Long): ProcessableResult
- Ich habe versucht, die Verwendung von isProcessingPayments als Flag zu verwalten.
- Versuchen Sie, den AlarmManager abzubrechen und einen neuen zu starten jedes Mal.