Skip to content

Commit be94dd5

Browse files
santosh-pingleSantosh Pingle
andauthored
api support for sync job cancellation. (#2717)
* cancel api to cancel the sync job. * remove commented code. * datastore to store uuid. * code cleanup. --------- Co-authored-by: Santosh Pingle <[email protected]>
1 parent 36e2ccf commit be94dd5

File tree

9 files changed

+441
-186
lines changed

9 files changed

+441
-186
lines changed

demo/src/main/java/com/google/android/fhir/demo/PeriodicSyncFragment.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Google LLC
2+
* Copyright 2024-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import android.view.LayoutInflater
2121
import android.view.MenuItem
2222
import android.view.View
2323
import android.view.ViewGroup
24+
import android.widget.Button
2425
import android.widget.ProgressBar
2526
import android.widget.TextView
2627
import androidx.appcompat.app.AppCompatActivity
@@ -48,6 +49,7 @@ class PeriodicSyncFragment : Fragment() {
4849
setUpActionBar()
4950
setHasOptionsMenu(true)
5051
refreshPeriodicSynUi()
52+
setUpSyncButtons(view)
5153
}
5254

5355
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@@ -67,6 +69,30 @@ class PeriodicSyncFragment : Fragment() {
6769
}
6870
}
6971

72+
private fun setUpSyncButtons(view: View) {
73+
val syncNowButton = view.findViewById<Button>(R.id.sync_now_button)
74+
val cancelSyncButton = view.findViewById<Button>(R.id.cancel_sync_button)
75+
syncNowButton.apply {
76+
setOnClickListener {
77+
periodicSyncViewModel.collectPeriodicSyncJobStatus()
78+
toggleButtonVisibility(hiddenButton = syncNowButton, visibleButton = cancelSyncButton)
79+
visibility = View.GONE
80+
}
81+
}
82+
cancelSyncButton.apply {
83+
setOnClickListener {
84+
periodicSyncViewModel.cancelPeriodicSyncJob()
85+
toggleButtonVisibility(hiddenButton = cancelSyncButton, visibleButton = syncNowButton)
86+
visibility = View.GONE
87+
}
88+
}
89+
}
90+
91+
private fun toggleButtonVisibility(hiddenButton: View, visibleButton: View) {
92+
hiddenButton.visibility = View.GONE
93+
visibleButton.visibility = View.VISIBLE
94+
}
95+
7096
private fun refreshPeriodicSynUi() {
7197
viewLifecycleOwner.lifecycleScope.launch {
7298
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {

demo/src/main/java/com/google/android/fhir/demo/PeriodicSyncViewModel.kt

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Google LLC
2+
* Copyright 2024-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,36 +31,43 @@ import com.google.android.fhir.sync.RepeatInterval
3131
import com.google.android.fhir.sync.Sync
3232
import com.google.android.fhir.sync.SyncJobStatus
3333
import java.util.concurrent.TimeUnit
34+
import kotlinx.coroutines.flow.MutableSharedFlow
3435
import kotlinx.coroutines.flow.MutableStateFlow
35-
import kotlinx.coroutines.flow.SharedFlow
36-
import kotlinx.coroutines.flow.SharingStarted
3736
import kotlinx.coroutines.flow.StateFlow
38-
import kotlinx.coroutines.flow.shareIn
3937
import kotlinx.coroutines.launch
38+
import timber.log.Timber
4039

4140
class PeriodicSyncViewModel(application: Application) : AndroidViewModel(application) {
4241

43-
val pollPeriodicSyncJobStatus: SharedFlow<PeriodicSyncJobStatus> =
44-
Sync.periodicSync<DemoFhirSyncWorker>(
45-
application.applicationContext,
42+
private val _uiStateFlow = MutableStateFlow(PeriodicSyncUiState())
43+
val uiStateFlow: StateFlow<PeriodicSyncUiState> = _uiStateFlow
44+
45+
private val _pollPeriodicSyncJobStatus = MutableSharedFlow<PeriodicSyncJobStatus>(replay = 10)
46+
47+
init {
48+
viewModelScope.launch { initializePeriodicSync() }
49+
}
50+
51+
private suspend fun initializePeriodicSync() {
52+
val periodicSyncJobStatusFlow =
53+
Sync.periodicSync<DemoFhirSyncWorker>(
54+
context = getApplication<Application>().applicationContext,
4655
periodicSyncConfiguration =
4756
PeriodicSyncConfiguration(
4857
syncConstraints = Constraints.Builder().build(),
4958
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES),
5059
),
5160
)
52-
.shareIn(viewModelScope, SharingStarted.Eagerly, 10)
53-
54-
private val _uiStateFlow = MutableStateFlow(PeriodicSyncUiState())
55-
val uiStateFlow: StateFlow<PeriodicSyncUiState> = _uiStateFlow
5661

57-
init {
58-
collectPeriodicSyncJobStatus()
62+
periodicSyncJobStatusFlow.collect { status -> _pollPeriodicSyncJobStatus.emit(status) }
5963
}
6064

61-
private fun collectPeriodicSyncJobStatus() {
65+
fun collectPeriodicSyncJobStatus() {
6266
viewModelScope.launch {
63-
pollPeriodicSyncJobStatus.collect { periodicSyncJobStatus ->
67+
_pollPeriodicSyncJobStatus.collect { periodicSyncJobStatus ->
68+
Timber.d(
69+
"currentSyncJobStatus: ${periodicSyncJobStatus.currentSyncJobStatus} lastSyncJobStatus ${periodicSyncJobStatus.lastSyncJobStatus}",
70+
)
6471
val lastSyncStatus = getLastSyncStatus(periodicSyncJobStatus.lastSyncJobStatus)
6572
val lastSyncTime = getLastSyncTime(periodicSyncJobStatus.lastSyncJobStatus)
6673
val currentSyncStatus =
@@ -83,6 +90,14 @@ class PeriodicSyncViewModel(application: Application) : AndroidViewModel(applica
8390
}
8491
}
8592

93+
fun cancelPeriodicSyncJob() {
94+
viewModelScope.launch {
95+
Sync.cancelPeriodicSync<DemoFhirSyncWorker>(
96+
getApplication<FhirApplication>().applicationContext,
97+
)
98+
}
99+
}
100+
86101
private fun getLastSyncStatus(lastSyncJobStatus: LastSyncJobStatus?): String? {
87102
return when (lastSyncJobStatus) {
88103
is LastSyncJobStatus.Succeeded ->

demo/src/main/java/com/google/android/fhir/demo/SyncFragment.kt

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Google LLC
2+
* Copyright 2024-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@ import androidx.fragment.app.viewModels
3030
import androidx.navigation.fragment.NavHostFragment
3131
import com.google.android.fhir.demo.extensions.launchAndRepeatStarted
3232
import com.google.android.fhir.sync.CurrentSyncJobStatus
33+
import timber.log.Timber
3334

3435
class SyncFragment : Fragment() {
3536
private val syncFragmentViewModel: SyncFragmentViewModel by viewModels()
@@ -49,6 +50,9 @@ class SyncFragment : Fragment() {
4950
view.findViewById<Button>(R.id.sync_now_button).setOnClickListener {
5051
syncFragmentViewModel.triggerOneTimeSync()
5152
}
53+
view.findViewById<Button>(R.id.cancel_sync_button).setOnClickListener {
54+
syncFragmentViewModel.cancelOneTimeSyncWork()
55+
}
5256
observeLastSyncTime()
5357
launchAndRepeatStarted(
5458
{ syncFragmentViewModel.pollState.collect(::currentSyncJobStatus) },
@@ -73,24 +77,41 @@ class SyncFragment : Fragment() {
7377
}
7478

7579
private fun currentSyncJobStatus(currentSyncJobStatus: CurrentSyncJobStatus) {
76-
requireView().findViewById<TextView>(R.id.current_status).text =
80+
Timber.d("currentSyncJobStatus: $currentSyncJobStatus")
81+
// Update status text
82+
val statusTextView = requireView().findViewById<TextView>(R.id.current_status)
83+
statusTextView.text =
7784
getString(R.string.current_status, currentSyncJobStatus::class.java.simpleName)
7885

79-
// Update progress indicator visibility and handle status-specific actions
86+
// Get views once to avoid repeated lookups
8087
val syncIndicator = requireView().findViewById<ProgressBar>(R.id.sync_indicator)
88+
val syncNowButton = requireView().findViewById<Button>(R.id.sync_now_button)
89+
val cancelSyncButton = requireView().findViewById<Button>(R.id.cancel_sync_button)
90+
91+
// Update view states based on sync status
8192
when (currentSyncJobStatus) {
8293
is CurrentSyncJobStatus.Running -> {
8394
syncIndicator.visibility = View.VISIBLE
95+
syncNowButton.visibility = View.GONE
96+
cancelSyncButton.visibility = View.VISIBLE
8497
}
8598
is CurrentSyncJobStatus.Succeeded -> {
8699
syncIndicator.visibility = View.GONE
87100
syncFragmentViewModel.updateLastSyncTimestamp(currentSyncJobStatus.timestamp)
101+
syncNowButton.visibility = View.VISIBLE
102+
cancelSyncButton.visibility = View.GONE
88103
}
89104
is CurrentSyncJobStatus.Failed,
105+
is CurrentSyncJobStatus.Cancelled, -> {
106+
syncIndicator.visibility = View.GONE
107+
syncNowButton.visibility = View.VISIBLE
108+
cancelSyncButton.visibility = View.GONE
109+
}
90110
is CurrentSyncJobStatus.Enqueued,
91-
is CurrentSyncJobStatus.Cancelled,
92111
is CurrentSyncJobStatus.Blocked, -> {
93112
syncIndicator.visibility = View.GONE
113+
syncNowButton.visibility = View.GONE
114+
cancelSyncButton.visibility = View.VISIBLE
94115
}
95116
}
96117
}

demo/src/main/java/com/google/android/fhir/demo/SyncFragmentViewModel.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 Google LLC
2+
* Copyright 2023-2025 Google LLC
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -53,15 +53,21 @@ class SyncFragmentViewModel(application: Application) : AndroidViewModel(applica
5353
val pollState: SharedFlow<CurrentSyncJobStatus> =
5454
_oneTimeSyncTrigger
5555
.flatMapLatest {
56-
Sync.oneTimeSync<DemoFhirSyncWorker>(context = application.applicationContext)
56+
Sync.oneTimeSync<DemoFhirSyncWorker>(
57+
context = application.applicationContext,
58+
)
5759
}
5860
.map { it }
59-
.shareIn(viewModelScope, SharingStarted.Eagerly, 0)
61+
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 0)
6062

6163
fun triggerOneTimeSync() {
6264
viewModelScope.launch { _oneTimeSyncTrigger.emit(true) }
6365
}
6466

67+
fun cancelOneTimeSyncWork() {
68+
viewModelScope.launch { Sync.cancelOneTimeSync<DemoFhirSyncWorker>(getApplication()) }
69+
}
70+
6571
/** Emits last sync time. */
6672
fun updateLastSyncTimestamp(lastSync: OffsetDateTime? = null) {
6773
val formatter =

demo/src/main/res/layout/periodic_sync.xml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,42 @@
8787
android:layout_marginTop="8dp"
8888
/>
8989

90+
<!-- Sync Now Button -->
91+
<Button
92+
android:id="@+id/sync_now_button"
93+
android:layout_width="wrap_content"
94+
android:layout_height="wrap_content"
95+
app:layout_constraintStart_toStartOf="parent"
96+
app:layout_constraintEnd_toEndOf="parent"
97+
app:layout_constraintTop_toBottomOf="@id/progress_percentage_label"
98+
app:layout_constraintBottom_toBottomOf="parent"
99+
android:layout_marginTop="16dp"
100+
android:layout_marginBottom="64dp"
101+
android:paddingLeft="24dp"
102+
android:paddingRight="24dp"
103+
android:text="Sync Now"
104+
android:backgroundTint="?attr/colorPrimary"
105+
android:textColor="?attr/colorOnPrimary"
106+
app:layout_goneMarginTop="16dp"
107+
/>
108+
109+
<Button
110+
android:id="@+id/cancel_sync_button"
111+
android:layout_width="wrap_content"
112+
android:layout_height="wrap_content"
113+
app:layout_constraintStart_toStartOf="parent"
114+
app:layout_constraintEnd_toEndOf="parent"
115+
app:layout_constraintTop_toBottomOf="@id/progress_percentage_label"
116+
app:layout_constraintBottom_toBottomOf="parent"
117+
android:layout_marginTop="16dp"
118+
android:layout_marginBottom="64dp"
119+
android:paddingLeft="24dp"
120+
android:paddingRight="24dp"
121+
android:text="Cancel Sync"
122+
android:backgroundTint="?attr/colorPrimary"
123+
android:textColor="?attr/colorOnPrimary"
124+
android:visibility="gone"
125+
app:layout_goneMarginTop="16dp"
126+
/>
127+
90128
</androidx.constraintlayout.widget.ConstraintLayout>

demo/src/main/res/layout/sync.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,22 @@
7979
android:textColor="?attr/colorOnPrimary"
8080
/>
8181

82+
<Button
83+
android:id="@+id/cancel_sync_button"
84+
android:layout_width="wrap_content"
85+
android:layout_height="wrap_content"
86+
app:layout_constraintStart_toStartOf="parent"
87+
app:layout_constraintEnd_toEndOf="parent"
88+
app:layout_constraintTop_toBottomOf="@id/sync_now_button"
89+
app:layout_constraintBottom_toBottomOf="parent"
90+
android:layout_marginTop="16dp"
91+
android:layout_marginBottom="64dp"
92+
android:paddingLeft="24dp"
93+
android:paddingRight="24dp"
94+
android:text="Cancel Sync"
95+
android:backgroundTint="?attr/colorPrimary"
96+
android:textColor="?attr/colorOnPrimary"
97+
android:visibility="gone"
98+
/>
99+
82100
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)