Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api support for sync job cancellation. #2717

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
Expand Down Expand Up @@ -48,6 +49,7 @@ class PeriodicSyncFragment : Fragment() {
setUpActionBar()
setHasOptionsMenu(true)
refreshPeriodicSynUi()
setUpSyncButtons(view)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
Expand All @@ -67,6 +69,30 @@ class PeriodicSyncFragment : Fragment() {
}
}

private fun setUpSyncButtons(view: View) {
val syncNowButton = view.findViewById<Button>(R.id.sync_now_button)
val cancelSyncButton = view.findViewById<Button>(R.id.cancel_sync_button)
syncNowButton.apply {
setOnClickListener {
periodicSyncViewModel.collectPeriodicSyncJobStatus()
toggleButtonVisibility(hiddenButton = syncNowButton, visibleButton = cancelSyncButton)
visibility = View.GONE
}
}
cancelSyncButton.apply {
setOnClickListener {
periodicSyncViewModel.cancelPeriodicSyncJob()
toggleButtonVisibility(hiddenButton = cancelSyncButton, visibleButton = syncNowButton)
visibility = View.GONE
}
}
}

private fun toggleButtonVisibility(hiddenButton: View, visibleButton: View) {
hiddenButton.visibility = View.GONE
visibleButton.visibility = View.VISIBLE
}

private fun refreshPeriodicSynUi() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,37 +30,45 @@ import com.google.android.fhir.sync.PeriodicSyncJobStatus
import com.google.android.fhir.sync.RepeatInterval
import com.google.android.fhir.sync.Sync
import com.google.android.fhir.sync.SyncJobStatus
import java.util.*
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import timber.log.Timber

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

val pollPeriodicSyncJobStatus: SharedFlow<PeriodicSyncJobStatus> =
Sync.periodicSync<DemoFhirSyncWorker>(
application.applicationContext,
private val _uiStateFlow = MutableStateFlow(PeriodicSyncUiState())
val uiStateFlow: StateFlow<PeriodicSyncUiState> = _uiStateFlow

private val _pollPeriodicSyncJobStatus = MutableSharedFlow<PeriodicSyncJobStatus>(replay = 10)

init {
viewModelScope.launch { initializePeriodicSync() }
}

private suspend fun initializePeriodicSync() {
val periodicSyncJobStatusFlow =
Sync.periodicSync<DemoFhirSyncWorker>(
context = getApplication<Application>().applicationContext,
periodicSyncConfiguration =
PeriodicSyncConfiguration(
syncConstraints = Constraints.Builder().build(),
repeat = RepeatInterval(interval = 15, timeUnit = TimeUnit.MINUTES),
),
)
.shareIn(viewModelScope, SharingStarted.Eagerly, 10)

private val _uiStateFlow = MutableStateFlow(PeriodicSyncUiState())
val uiStateFlow: StateFlow<PeriodicSyncUiState> = _uiStateFlow

init {
collectPeriodicSyncJobStatus()
periodicSyncJobStatusFlow.collect { status -> _pollPeriodicSyncJobStatus.emit(status) }
}

private fun collectPeriodicSyncJobStatus() {
fun collectPeriodicSyncJobStatus() {
viewModelScope.launch {
pollPeriodicSyncJobStatus.collect { periodicSyncJobStatus ->
_pollPeriodicSyncJobStatus.collect { periodicSyncJobStatus ->
Timber.d(
"currentSyncJobStatus: ${periodicSyncJobStatus.currentSyncJobStatus} lastSyncJobStatus ${periodicSyncJobStatus.lastSyncJobStatus}",
)
val lastSyncStatus = getLastSyncStatus(periodicSyncJobStatus.lastSyncJobStatus)
val lastSyncTime = getLastSyncTime(periodicSyncJobStatus.lastSyncJobStatus)
val currentSyncStatus =
Expand All @@ -83,6 +91,14 @@ class PeriodicSyncViewModel(application: Application) : AndroidViewModel(applica
}
}

fun cancelPeriodicSyncJob() {
viewModelScope.launch {
Sync.cancelPeriodicSync<DemoFhirSyncWorker>(
getApplication<FhirApplication>().applicationContext,
)
}
}

private fun getLastSyncStatus(lastSyncJobStatus: LastSyncJobStatus?): String? {
return when (lastSyncJobStatus) {
is LastSyncJobStatus.Succeeded ->
Expand Down
31 changes: 26 additions & 5 deletions demo/src/main/java/com/google/android/fhir/demo/SyncFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.fragment.app.viewModels
import androidx.navigation.fragment.NavHostFragment
import com.google.android.fhir.demo.extensions.launchAndRepeatStarted
import com.google.android.fhir.sync.CurrentSyncJobStatus
import timber.log.Timber

class SyncFragment : Fragment() {
private val syncFragmentViewModel: SyncFragmentViewModel by viewModels()
Expand All @@ -49,9 +50,12 @@ class SyncFragment : Fragment() {
view.findViewById<Button>(R.id.sync_now_button).setOnClickListener {
syncFragmentViewModel.triggerOneTimeSync()
}
view.findViewById<Button>(R.id.cancel_sync_button).setOnClickListener {
syncFragmentViewModel.cancelOneTimeSyncWork()
}
observeLastSyncTime()
launchAndRepeatStarted(
{ syncFragmentViewModel.pollState.collect(::currentSyncJobStatus) },
{ syncFragmentViewModel.pollState.collect(::updateSyncJobStatus) },
)
}

Expand All @@ -72,25 +76,42 @@ class SyncFragment : Fragment() {
}
}

private fun currentSyncJobStatus(currentSyncJobStatus: CurrentSyncJobStatus) {
requireView().findViewById<TextView>(R.id.current_status).text =
private fun updateSyncJobStatus(currentSyncJobStatus: CurrentSyncJobStatus) {
Timber.d("currentSyncJobStatus: $currentSyncJobStatus")
// Update status text
val statusTextView = requireView().findViewById<TextView>(R.id.current_status)
statusTextView.text =
getString(R.string.current_status, currentSyncJobStatus::class.java.simpleName)

// Update progress indicator visibility and handle status-specific actions
// Get views once to avoid repeated lookups
val syncIndicator = requireView().findViewById<ProgressBar>(R.id.sync_indicator)
val syncNowButton = requireView().findViewById<Button>(R.id.sync_now_button)
val cancelSyncButton = requireView().findViewById<Button>(R.id.cancel_sync_button)

// Update view states based on sync status
when (currentSyncJobStatus) {
is CurrentSyncJobStatus.Running -> {
syncIndicator.visibility = View.VISIBLE
syncNowButton.visibility = View.GONE
cancelSyncButton.visibility = View.VISIBLE
}
is CurrentSyncJobStatus.Succeeded -> {
syncIndicator.visibility = View.GONE
syncFragmentViewModel.updateLastSyncTimestamp(currentSyncJobStatus.timestamp)
syncNowButton.visibility = View.VISIBLE
cancelSyncButton.visibility = View.GONE
}
is CurrentSyncJobStatus.Failed,
is CurrentSyncJobStatus.Cancelled, -> {
syncIndicator.visibility = View.GONE
syncNowButton.visibility = View.VISIBLE
cancelSyncButton.visibility = View.GONE
}
is CurrentSyncJobStatus.Enqueued,
is CurrentSyncJobStatus.Cancelled,
is CurrentSyncJobStatus.Blocked, -> {
syncIndicator.visibility = View.GONE
syncNowButton.visibility = View.GONE
cancelSyncButton.visibility = View.VISIBLE
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.google.android.fhir.sync.CurrentSyncJobStatus
import com.google.android.fhir.sync.Sync
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
import java.util.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
Expand All @@ -40,6 +41,7 @@ import kotlinx.coroutines.launch
/** View model for [MainActivity]. */
@OptIn(ExperimentalCoroutinesApi::class)
class SyncFragmentViewModel(application: Application) : AndroidViewModel(application) {
private var oneTimeSyncUuid: UUID? = null
private val _lastSyncTimestampLiveData = MutableLiveData<String>()
val lastSyncTimestampLiveData: LiveData<String>
get() = _lastSyncTimestampLiveData
Expand All @@ -53,15 +55,21 @@ class SyncFragmentViewModel(application: Application) : AndroidViewModel(applica
val pollState: SharedFlow<CurrentSyncJobStatus> =
_oneTimeSyncTrigger
.flatMapLatest {
Sync.oneTimeSync<DemoFhirSyncWorker>(context = application.applicationContext)
Sync.oneTimeSync<DemoFhirSyncWorker>(
context = application.applicationContext,
)
}
.map { it }
.shareIn(viewModelScope, SharingStarted.Eagerly, 0)
.shareIn(viewModelScope, SharingStarted.Eagerly, replay = 0)

fun triggerOneTimeSync() {
viewModelScope.launch { _oneTimeSyncTrigger.emit(true) }
}

fun cancelOneTimeSyncWork() {
viewModelScope.launch { Sync.cancelOneTimeSync<DemoFhirSyncWorker>(getApplication()) }
}

/** Emits last sync time. */
fun updateLastSyncTimestamp(lastSync: OffsetDateTime? = null) {
val formatter =
Expand Down
38 changes: 38 additions & 0 deletions demo/src/main/res/layout/periodic_sync.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,42 @@
android:layout_marginTop="8dp"
/>

<!-- Sync Now Button -->
<Button
android:id="@+id/sync_now_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_percentage_label"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp"
android:layout_marginBottom="64dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="Sync Now"
android:backgroundTint="?attr/colorPrimary"
android:textColor="?attr/colorOnPrimary"
app:layout_goneMarginTop="16dp"
/>

<Button
android:id="@+id/cancel_sync_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/progress_percentage_label"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp"
android:layout_marginBottom="64dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="Cancel Sync"
android:backgroundTint="?attr/colorPrimary"
android:textColor="?attr/colorOnPrimary"
android:visibility="gone"
app:layout_goneMarginTop="16dp"
/>

</androidx.constraintlayout.widget.ConstraintLayout>
18 changes: 18 additions & 0 deletions demo/src/main/res/layout/sync.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,22 @@
android:textColor="?attr/colorOnPrimary"
/>

<Button
android:id="@+id/cancel_sync_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/sync_now_button"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="16dp"
android:layout_marginBottom="64dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="Cancel Sync"
android:backgroundTint="?attr/colorPrimary"
android:textColor="?attr/colorOnPrimary"
android:visibility="gone"
/>

</androidx.constraintlayout.widget.ConstraintLayout>
Loading
Loading