Skip to content

Migrate Persistent Background Work snippets - #1133

Draft
djubinville wants to merge 4 commits into
android:mainfrom
StellarElements:bg-work-persistent
Draft

djubinville wants to merge 4 commits into
android:mainfrom
StellarElements:bg-work-persistent

Conversation

@djubinville

@djubinville djubinville commented Sep 22, 2026 •

Copy link
Copy Markdown

Summary

Extracts Kotlin code snippets for 8 persistent background work (WorkManager) documentation guides into :backgroundwork (backgroundwork/src/main/java/com/example/snippets/backgroundwork/) as region-tagged source code.

Pages covered:

Files & Region Tags Added

  • CustomConfiguration.kt: android_background_custom_configuration_on_demand, android_background_custom_configuration_manual_init
  • DefineWork.kt (12 snippets): android_background_enqueue_work_request, android_background_schedule_one_time_work_from, android_background_schedule_one_time_work_builder, android_background_expedited_work_request, android_background_expedited_coroutine_worker, android_background_periodic_work_request, android_background_periodic_work_request_flex, android_background_work_constraints, android_background_delayed_work, android_background_retry_backoff_policy, android_background_tag_work, android_background_assign_input_data
  • LongRunningWorker.kt: android_background_long_running_coroutine_worker, android_background_long_running_foreground_service_type
  • ManageWork.kt (6 snippets): android_background_manage_work_enqueue, android_background_unique_periodic_work, android_background_observe_work_query, android_background_observe_work_flow, android_background_complex_work_queries, android_background_cancel_work
  • ObserveWork.kt (3 snippets): android_background_observe_progress_worker, android_background_observe_progress_flow, android_background_observe_stop_reason
  • UpdateWork.kt (2 snippets): android_background_update_photo_upload_work, android_background_track_work_generation
  • CoroutineWorkerThreading.kt (3 snippets): android_background_coroutine_download_worker, android_background_coroutine_download_worker_with_context, android_background_remote_coroutine_worker_request
  • ListenableWorkerThreading.kt (3 snippets): android_background_callback_worker, android_background_callback_worker_cancellation, android_background_remote_listenable_worker_request
  • backgroundwork/src/main/res/drawable/ic_work_notification.xml (no region tag — Required for the Kotlin snippet to compile)
  • backgroundwork/src/main/res/values/strings.xml (no region tag — Required for the Kotlin snippet to compile)

Compile-Driven Supporting Resources (D11, D27)

Two untagged XML resource files (backgroundwork/src/main/res/drawable/ic_work_notification.xml and backgroundwork/src/main/res/values/strings.xml) are included in this PR strictly because tagged Kotlin regions reference them and will not compile without them. Specifically, LongRunningWorker.kt (android_background_long_running_coroutine_worker) references setSmallIcon(R.drawable.ic_work_notification) (which forces the drawable filename ic_work_notification.xml, implemented as a Material work/notification vector icon) as well as R.string.notification_channel_id, R.string.notification_title, and R.string.cancel_download, while ManageWork.kt (android_background_observe_work_flow) references R.string.work_completed. Neither resource file is printed on the DAC pages, so neither carries a region tag.

List of modifications

To compile cleanly against Kotlin and WorkManager 2.9+ while adhering to D1–D32 quality gates, the following minimal adjustments were made to the original DAC snippets:

  1. CustomConfiguration.kt (android_background_custom_configuration_on_demand): Updated override fun getWorkManagerConfiguration() to override val workManagerConfiguration: Configuration get() = ... because Configuration.Provider defines workManagerConfiguration as a Kotlin property in WorkManager 2.9+ (D5/D14).
  2. UpdateWork.kt (android_background_track_work_generation): Changed workManager.getWorkInfoById(oldWorkRequestId) to workManager.getWorkInfoById(oldWorkRequestId).get() and workInfo.getGeneration() to workInfo?.generation, because getWorkInfoById() returns a ListenableFuture<WorkInfo?> (D5/D14).
  3. CoroutineWorkerThreading.kt (android_background_coroutine_download_worker_with_context): Changed withContext(Dispatchers.IO) { ... return Result.success() } to return withContext(Dispatchers.IO) { ... Result.success() } because non-local return inside withContext is prohibited in Kotlin (D5/D14).
  4. DefineWork.kt (android_background_expedited_work_request, android_background_assign_input_data) & LongRunningWorker.kt (android_background_long_running_foreground_service_type): Stripped inline HTML formatting tags (<b>, </b>, <var>, </var>), wrapped the hidden uploadFile(uri: String) stub in UploadWork inside plain // [START_EXCLUDE] ... // [END_EXCLUDE] so DevSite renders the ellipsis automatically (D22), and kept val myUploadWork in android_background_assign_input_data at top-level scope so both class UploadWork and val myUploadWork share column-0 indentation for DevSite rendering while satisfying Spotless (ktlint).
  5. ObserveWork.kt (android_background_observe_progress_worker): Preserved the 5 page-visible import statements (Context, CoroutineWorker, Data, WorkerParameters, delay) at their exact relative position inside the region tag as commented imports (// import ...), while placing the real compiling imports at the file header (D18).
  6. Comment punctuation & private helpers (CustomConfiguration.kt, DefineWork.kt, LongRunningWorker.kt, ManageWork.kt, ObserveWork.kt, UpdateWork.kt, CoroutineWorkerThreading.kt, ListenableWorkerThreading.kt): Added trailing full stops (.) to natural-language inline comments carried over from DAC that omitted them (D29), and marked all top-level helper classes outside region tags private across sibling files in com.example.snippets.backgroundwork (D26a).
  7. backgroundwork/src/main/res/drawable/ic_work_notification.xml (no region tag): Added supporting vector drawable forced by setSmallIcon(R.drawable.ic_work_notification) in LongRunningWorker.kt (android_background_long_running_coroutine_worker). Required for the Kotlin snippet to compile (D11, D27).
  8. backgroundwork/src/main/res/values/strings.xml (no region tag): Added supporting string definitions forced by R.string.notification_channel_id, R.string.notification_title, and R.string.cancel_download in LongRunningWorker.kt (android_background_long_running_coroutine_worker) and R.string.work_completed in ManageWork.kt (android_background_observe_work_flow). Required for the Kotlin snippet to compile (D27).

Snippets not migrated

  • Java code snippets across all 8 pages cataloged for removal under D15 Kotlin-first policy.
  • AndroidManifest.xml configuration snippets (custom-configuration.md, long-running.md, coroutineworker.md, listenableworker.md) remain inline on the DAC pages with {# disableFinding(SNIPPET_GITHUB) #} (D27).

Verification

  • ./gradlew :backgroundwork:compileDebugKotlin - passes (0 errors)
  • ./gradlew :backgroundwork:spotlessCheck - formatted cleanly

@google-cla

google-cla Bot commented Sep 22, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@djubinville

djubinville commented Sep 24, 2026 •

Copy link
Copy Markdown
Author

This commit is to address linter issue as a standalone. The following were resolved:

# File:line Lint ID Fix
1 CoroutineWorkerThreading.kt:87 ExampleRemoteCoroutineWorker WorkerHasAPublicModifier Remove private. The class is outside any region tag, so the docs don't change.
2 DefineWork.kt:198 SyncWorker WorkerHasAPublicModifier Same fix
3 DefineWork.kt:202 SaveImageToFileWorker WorkerHasAPublicModifier Same fix
4 ListenableWorkerThreading.kt:135 ExampleRemoteListenableWorker WorkerHasAPublicModifier Same fix
5 LongRunningWorker.kt:101 ForegroundServiceTypeSnippet WorkerHasAPublicModifier Same fix
6 ManageWork.kt:126 SendLogsWorker WorkerHasAPublicModifier Same fix
7 UpdateWork.kt:75 MyWorker WorkerHasAPublicModifier Same fix

Two lint errors are left, and they still fail build. Both happen because the Kotlin snippets rely on manifest entries that backgroundwork/src/main/AndroidManifest.xml doesn't have yet. The DAC pages show those entries as their own snippets, so I suggest adding them as region-tagged XML snippets rather than suppressing the lint checks.

It appears that these errors may be due to the snippet extraction skill leaving the AndroidManifest.xml file hardcoded. How should this be addressed?

cc: @kkuan2011 @erikrodriguez-se


Proposed Fix

1. RemoveWorkManagerInitializer (AndroidManifest.xml:22)

Error: Remove androidx.work.WorkManagerInitializer from your AndroidManifest.xml when using on-demand initialization. [RemoveWorkManagerInitializer from androidx.work]

Cause: MyApplication in CustomConfiguration.kt implements Configuration.Provider (android_background_custom_configuration_on_demand). But the manifest still lets androidx.startup run the default WorkManagerInitializer. On-demand initialization needs the default one removed.

Suggested fix: add the manifest block from the Custom WorkManager configuration page. It removes only the WorkManager initializer, so androidx.startup keeps working for other components:

<!-- [START android_background_custom_configuration_remove_initializer] -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- If you are using androidx.startup to initialize other components -->
    <meta-data
        android:name="androidx.work.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
</provider>
<!-- [END android_background_custom_configuration_remove_initializer] -->

The same page also shows a variant that disables androidx.startup entirely (<provider … tools:node="remove">). If we want both variants on DAC, it could go in under a second tag, such as android_background_custom_configuration_disable_startup.

2. SpecifyForegroundServiceType (LongRunningWorker.kt:114)

Error: Missing location foregroundServiceType in the AndroidManifest.xml [SpecifyForegroundServiceType from androidx.work]

Cause: android_background_long_running_foreground_service_type passes FOREGROUND_SERVICE_TYPE_LOCATION or FOREGROUND_SERVICE_TYPE_MICROPHONE to ForegroundInfo. WorkManager's SystemForegroundService doesn't declare those types in the merged manifest.

Suggested fix: add the service declaration from the Support for long-running workers page:

<!-- [START android_background_long_running_foreground_service_type_manifest] -->
<service
    android:name="androidx.work.impl.foreground.SystemForegroundService"
    android:foregroundServiceType="location|microphone"
    tools:node="merge" />
<!-- [END android_background_long_running_foreground_service_type_manifest] -->

This alone isn't enough. With only the <service> block, lint swaps error 9 for two ForegroundServicePermission errors on the same element:

Error: foregroundServiceType:location requires permission:[android.permission.FOREGROUND_SERVICE_LOCATION] AND any permission in list:[android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION] [ForegroundServicePermission]
Error: foregroundServiceType:microphone requires permission:[android.permission.FOREGROUND_SERVICE_MICROPHONE] AND any permission in list:[..., android.permission.RECORD_AUDIO] [ForegroundServicePermission]

So the manifest also needs these permissions, placed outside the region tag. Declare foreground services and request permissions covers them:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

ACCESS_COARSE_LOCATION is the minimum that satisfies the location check. ACCESS_FINE_LOCATION also works.

Notes

  • Both blocks go inside the existing <application> element. That means turning <application tools:ignore="MissingApplicationIcon" /> into an open/close pair. The tools namespace is already declared.
  • The XML spotless rule already skips <!-- [START …] --> lines when it checks license headers. spotlessXmlCheck passes with the region tags in place.
  • Tested: with all of the above applied, :backgroundwork:lintDebug, :backgroundwork:spotlessCheck (including spotlessXmlCheck), :backgroundwork:compileDebugKotlin and the full :backgroundwork:build pass, and lint reports no errors. The full manifest I tested is below.
  • The two ObsoleteSdkInt warnings (LongRunningWorker.kt:71, :89) are only warnings and don't fail the build. They sit inside the rendered long_running_coroutine_worker region, so if we want to silence them, a lint { disable += "ObsoleteSdkInt" } in backgroundwork/build.gradle.kts would do it without changing the snippet.
Tested AndroidManifest.xml body
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application tools:ignore="MissingApplicationIcon">
        <!-- [START android_background_custom_configuration_remove_initializer] -->
        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">
            <!-- If you are using androidx.startup to initialize other components -->
            <meta-data
                android:name="androidx.work.WorkManagerInitializer"
                android:value="androidx.startup"
                tools:node="remove" />
        </provider>
        <!-- [END android_background_custom_configuration_remove_initializer] -->

        <!-- [START android_background_long_running_foreground_service_type_manifest] -->
        <service
            android:name="androidx.work.impl.foreground.SystemForegroundService"
            android:foregroundServiceType="location|microphone"
            tools:node="merge" />
        <!-- [END android_background_long_running_foreground_service_type_manifest] -->
    </application>

</manifest>

- ObserveWork.kt: Remove duplicate [START android_background_observe_progress_worker]
  region tag around imports, delete unused androidx.work.Data import, and sort imports.
- LongRunningWorker.kt: Remove stray [START_EXCLUDE]/[END_EXCLUDE] comments outside
  the android_background_long_running_foreground_service_type region tag, and add
  @SuppressLint("ObsoleteSdkInt") outside the DownloadWorker region tag.
- CustomConfiguration.kt: Change manualInitialization to a Context extension function
  (private fun Context.manualInitialization()) so WorkManager.initialize(this, myConfig)
  matches the DAC snippet verbatim.
- DefineWork.kt: Remove private fun createUploadWork() wrapper inside
  android_background_assign_input_data and keep val myUploadWork at top-level scope
  so both class UploadWork and val myUploadWork align at column 0 for DevSite rendering
  while satisfying Spotless (ktlint).
- UpdateWork.kt: Restore suspend fun updatePhotoUploadWork() signature to match DAC
  by moving context to a file-level property with @SuppressLint("StaticFieldLeak")
  outside the region tag.
@djubinville

Copy link
Copy Markdown
Author

Code Review Resolution Summary (30aa6be & cl/986196893)

All 8 blocking findings from the audit review have been addressed and validated against the rendered snippets, :backgroundwork:compileDebugKotlin, :backgroundwork:spotlessCheck, and :backgroundwork:lintDebug:

1. GitHub PR (android/snippets#1133, commit 30aa6be)

  1. ObserveWork.kt (android_background_observe_progress_worker):
    • Removed the duplicate [START]/[END] region tag around the import block (L19–25), deleted the unused import androidx.work.Data, sorted imports alphabetically (spotlessCheck passing), and documented the omitted file-level imports in the PR description.
  2. LongRunningWorker.kt (android_background_long_running_foreground_service_type):
    • Removed the stray // [START_EXCLUDE silent] and // [END_EXCLUDE] comments that were outside the active region tag (L107, L110).
    • Added @SuppressLint("ObsoleteSdkInt") outside [START android_background_long_running_coroutine_worker] to clear the two ObsoleteSdkInt lint warnings without altering the rendered snippet.
  3. CustomConfiguration.kt (android_background_custom_configuration_manual_init):
    • Changed private fun manualInitialization(context: Context) to an extension function private fun Context.manualInitialization() and restored WorkManager.initialize(this, myConfig) so the rendered snippet is 100% character-for-character identical to DAC.
  4. DefineWork.kt (android_background_assign_input_data):
    • Removed the private fun createUploadWork() { ... } wrapper inside [START android_background_assign_input_data] and kept val myUploadWork at top-level file scope so both class UploadWork and val myUploadWork sit at column 0 for DevSite rendering while satisfying Spotless (ktlint).
  5. UpdateWork.kt (android_background_update_photo_upload_work):
    • Restored suspend fun updatePhotoUploadWork() to match DAC verbatim by declaring @SuppressLint("StaticFieldLeak") private lateinit var context: Context at file scope outside the region tag.
  6. PR Migrate Persistent Background Work snippets #1133 Description:
    • Restored all backticked code spans stripped by shell expansion and updated List of modifications: to explicitly document all 5 required compilation/rendering adjustments.

2. Critique CL (cl/986196893)

  1. how-to/long-running.md:
    • Restored the ### Kotlin {:#long-running-kotlin} and ### Java {:#long-running-java} headings, deep-link anchor IDs, and narrative paragraphs ("You'll use a slightly different approach...", "Here is a simple example of a long running worker...", and "Developers using a ListenableWorker or a Worker can call the setForegroundAsync() API..."), while rendering only the Kotlin github.code_snippet.

Outstanding Items (Awaiting Guidance)

  1. Consolidating PR #1119 (bg-work-snippet-migration) into This Superseding PR (Migrate Persistent Background Work snippets #1133):

  2. 2 androidx.work Lint Errors in backgroundwork/src/main/AndroidManifest.xml (RemoveWorkManagerInitializer & SpecifyForegroundServiceType):

    • As detailed in PR #1133 comment, :backgroundwork:lintDebug currently reports 0 warnings and only these 2 manifest errors because AndroidManifest.xml was left untouched per the snippet migration skill.
    • A tested AndroidManifest.xml patch (adding the InitializationProvider removal block, SystemForegroundService location|microphone service declaration, and foreground service permissions) is ready to commit once confirmed whether we should include those manifest entries (or suppress the two lint checks).

cc: @kkuan2011 @erikrodriguez-se

- ObserveWork.kt: Preserve the 5 instructional import statements inside
  android_background_observe_progress_worker as commented imports (// import ...) per D18.
- CoroutineWorkerThreading.kt, DefineWork.kt, ListenableWorkerThreading.kt,
  LongRunningWorker.kt, ManageWork.kt, UpdateWork.kt: Mark top-level helper
  classes outside region tags private with @SuppressLint("WorkerHasAPublicModifier") per D26a.
- DefineWork.kt: Use plain // [START_EXCLUDE] around uploadFile stub in UploadWork
  instead of silent exclude + hand-written // ... per D22.
- CustomConfiguration.kt, DefineWork.kt, LongRunningWorker.kt, ManageWork.kt,
  ObserveWork.kt: Ensure all natural-language comments end with a full stop (.) per D29.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant