feat: Introduce public Polling API facade and internalize engine#9
Merged
feat: Introduce public Polling API facade and internalize engine#9
Conversation
This commit refactors the PollingEngine by introducing a public `PollingApi` interface and a `Polling` facade object as the primary entry point for consumers. The `PollingEngine` itself is now internal, encapsulating the implementation details.
**Key Changes:**
* **Public API Facade (`Polling.kt`, `PollingApi.kt`):**
* Introduced `PollingApi` interface defining the stable public contract for interacting with the polling functionality.
* Created a `Polling` object that implements `PollingApi` and delegates calls to the internal `PollingEngine`. This is now the recommended way for apps to use the library.
* `PollingEngine.Handle` is replaced by a public `PollingSession` data class, which is returned by `Polling.startPolling` and contains the session `id`.
* **`PollingEngine.kt` Internalization:**
* `PollingEngine` object and its `State` enum are now `internal`.
* Public methods on `PollingEngine` (e.g., `startPolling`, `pollUntil`, `cancel`, `pause`, `resume`, `updateBackoff`, `cancelAll`, `shutdown`, `compose`) are now `internal` or `fun` (if they were already part of the internal API). Consumers should use the `Polling` facade.
* Removed `metrics` recording calls (`recordAttempt`, `recordResult`, `recordComplete`) from `PollingEngine` as the `Metrics` interface was removed.
* **Configuration and Core (`PollingConfig.kt`, `Core.kt`):**
* Removed `Logger` and `Metrics` interfaces and their usage in `PollingConfig` and `PollingConfigBuilder`. Observability is now primarily through `onAttempt`, `onResult`, and `onComplete` hooks.
* `ErrorCodes` object is now `internal`.
* `FetchStrategy`, `SuccessStrategy`, `RetryStrategy` interfaces are now `internal`.
* `DefaultRetryPredicates` object renamed to `RetryPredicates` and its `retryOnNetworkServerTimeout` predicate is renamed to `networkOrServerOrTimeout`.
* The default `throwableMapper` in `PollingConfig` and `PollingConfigBuilder` now uses a stable public error code (`-1`) instead of the internal `ErrorCodes.UNKNOWN_ERROR_CODE`.
* **Build & Documentation:**
* Updated `group` ID in `pollingengine/build.gradle.kts` from `io.github.bosankus` to `in.androidplay`.
* Updated README:
* Changed artifact coordinates to `in.androidplay:pollingengine`.
* Updated usage examples to use the new `Polling` facade and `RetryPredicates`.
* Added a section on "Control APIs and Runtime Updates" demonstrating `Polling.startPolling`, `pause`, `resume`, `updateBackoff`, `cancel`.
* Added examples for `RetryPredicates`.
* Added a note about the API rename (`PollingEngineApi` -> `PollingApi`, use `Polling` facade).
* Removed "Pluggable logging and metrics" from the feature list.
* Added `docs/PollingEngine.md` (renamed from `composeApp/src/androidMain/kotlin/in/androidplay/pollingengine/Platform.android.kt` which was an empty file): This new Markdown file serves as a developer guide, explaining the public API, concepts, and usage.
* Updated `docs/ci-setup.md` with the new timestamp.
* Updated `gradle/libs.versions.toml`:
* `pollingengine` artifact path updated to `in.androidplay:pollingengine`.
* `kotlinx-coroutines` version updated to `1.10.2`.
* **Sample App (`App.kt`):**
* Now uses the `Polling` facade instead of `PollingEngine` directly.
* Uses `Polling.run` for one-shot polling instead of `PollingEngine.startPolling` with a job for simplified start/stop logic.
* Removed `PollingEngine.Handle` and uses `PollingSession` (though direct handle usage is reduced due to `Polling.run`).
* Pause/Resume functionality is now managed by a local `isPaused` state that controls the countdown timer, as `Polling.run` is synchronous. The actual engine pause/resume is not used in this simplified flow.
* Stop functionality now cancels the `pollingJob` launched for `Polling.run`.
* Added a retry strategy selector using `RetryPredicates` (`Always`, `Never`, `Network/Server/Timeout`).
* Minor UI tweaks using `AnimatedVisibility` and `RoundedCornerShape`.
* Round elapsed time values more consistently using `kotlin.math.round`.
* **Testing (`PollingEngineCoreTests.kt`, `PollingEngineCancellationTest.kt`):**
* Added `PollingEngineCoreTests.kt` with basic tests for success, non-retryable failure, max attempts, overall timeout, and `compose` behavior.
* Updated `PollingEngineCancellationTest.kt` to use `Polling.run`.
* **Removed Files:**
* `composeApp/src/commonMain/kotlin/in/androidplay/pollingengine/Greeting.kt`
* `composeApp/src/commonMain/kotlin/in/androidplay/pollingengine/Platform.kt`
* `composeApp/src/androidMain/kotlin/in/androidplay/pollingengine/Platform.android.kt` (content moved to `docs/PollingEngine.md`)
**Impact:**
* Provides a clearer and more stable public API for library consumers via the `Polling` facade.
* Reduces the public API surface by internalizing engine details.
* Simplifies observability by focusing on callback hooks rather than pluggable interfaces for logging/metrics.
* The sample app demonstrates the new facade and provides a control to switch retry strategies.
This commit introduces a major refactoring of the `PollingEngine` and its sample application. The engine's `startPolling` method now returns a `Flow<PollingOutcome<T>>` instead of taking a callback, and the sample app is restructured around a `PollingViewModel`.
**Key Changes:**
* **`PollingEngine` (`PollingEngine.kt`, `Polling.kt`, `PollingAPI.kt`):**
* `startPolling` now returns `Flow<PollingOutcome<T>>`.
* The Flow emits the final `PollingOutcome` and then completes.
* Internal management of active polls uses `channelFlow` and `awaitClose` for proper cancellation.
* A new overload for `startPolling` that accepts a `PollingConfigBuilder<T>.() -> Unit` lambda is added for convenience.
* The `Handle` class is removed as Flow subscription management replaces its role.
* Minor adjustment: The pause check (`control.state.map { it == State.Running }.first { it }`) is moved after the `onAttempt` callback and before `delay()`, ensuring `onAttempt` is called even if immediately paused.
* **Sample App (`App.kt`, `PollingViewModel.kt`, `Theme.kt`):**
* **ViewModel Architecture:**
* Introduced `PollingViewModel.kt` to manage UI state (`PollingUiState`), handle user intents (`PollingIntent`), and interact with the `PollingEngine`.
* `App.kt` now observes `StateFlow` from the ViewModel and dispatches intents.
* **State Management:**
* `PollingUiState` holds all UI-related data (running/paused state, logs, form inputs).
* Logs are now `List<LogItem>` with unique IDs for better `LazyColumn` performance.
* Uses `kotlinx.atomicfu.atomic` for unique log item IDs.
* **UI Enhancements (`App.kt`):**
* The main screen is now a `LazyColumn` for better scroll performance and structure.
* `ControlPanel.kt` (extracted from `App.kt`): A new composable for start/pause/resume/stop buttons and countdown timer.
* Properties section (`PropertiesCard` in previous commits, now inline in `App.kt` under "Basic Setup"):
* Layout improved with section headers (`SectionHeader`) and dividers (`SectionDivider`).
* `LabeledField` now supports `placeholder`, `suffix`, `supportingText`, `isError`, and `KeyboardType`.
* Retry strategy selection uses `Button` with visual distinction for the selected item.
* The "Apply Backoff at Runtime" button is moved inside the properties panel.
* **Theming (`Theme.kt`):**
* Extracted `MaterialTheme` setup into `PollingEngineTheme` in `Theme.kt`.
* **Dependencies (`composeApp/build.gradle.kts`, `gradle/libs.versions.toml`):**
* Added `kotlinx-atomicfu` dependency for `PollingViewModel`.
* **Documentation (`README.md`, `docs/`):**
* Removed `docs/ci-setup.md` and `docs/PollingEngine.md` as they were marked ARCHIVED or superseded.
* `README.md`: Updated links to point to new `docs/pollingengine.md` and `docs/DeveloperGuide.md`.
* **Build & Configuration:**
* `pollingengine/publishing.gradle.kts`: Minor formatting and null-check adjustments.
* Minor code style cleanups across various files (e.g., trailing commas, import organization).
* **New Builder DSL (`pollingengine/polling/builder/PollingConfigBuilder.kt`):**
* Introduced `PollingConfigBuilder` with `@PollingBuilderMarker` DSL for creating `PollingConfig` instances more fluently.
* The `Polling.startPolling` facade now has an overload accepting this builder.
**Impact:**
* The `PollingEngine` API is now more idiomatic for Kotlin Coroutines users, leveraging `Flow`.
* The sample application is more robust, maintainable, and demonstrates a cleaner architecture using a ViewModel.
* The UI of the sample app is more organized and user-friendly.
This commit addresses an issue where the overall countdown timer for polling operations did not accurately reflect the paused state.
**Key Changes:**
* **`PollingEngine.kt`:**
* The internal delay mechanism within the polling loop has been refined. Instead of a single `delay()` call, it now uses a `while` loop with smaller delay steps (`delayStep = 100L`).
* During each step, it checks if the poll is paused. If paused, it suspends execution using `control.state.map { it == State.Running }.first { it }` until resumed.
* This ensures that the overall timeout countdown effectively freezes when a poll is paused and resumes correctly when the poll is resumed.
* **`PollingViewModel.kt` (Sample App):**
* The logic for identifying the `pollingSession` ID after starting a new poll has been made more robust.
* It now captures the set of active polling IDs before and after launching the new poll. The difference between these sets is used to identify the newly created session ID.
* Includes a short retry mechanism (`repeat(10) { delay(50) }`) to allow time for the new session to be registered in `Polling.listActiveIds()`.
* A fallback is added: if a unique ID cannot be determined via the diff, but there's exactly one active poll, that ID is used.
* Error logging is added if the session ID cannot be reliably determined, as this would affect pause/resume functionality in the sample app.
* The `startCountdown()` is now called after the session ID is likely established to ensure the countdown is associated with the correct polling operation.
* Added `try-catch` around the polling start logic to handle potential errors during initialization and update UI state accordingly.
* **`PollingEngineCoreTests.kt`:**
* Removed an unused `outcome` variable read in `testMaxAttemptsReachedWithRetryFalse`.
**Functionality Improvements:**
* The overall timeout countdown timer in the `PollingEngine` now accurately pauses when a polling operation is paused and resumes when it's resumed.
* The sample application's `PollingViewModel` is more reliable in tracking the active polling session, improving the behavior of its pause/resume feature.
This commit updates the CodeQL workflow to improve analysis for Kotlin Multiplatform and Swift:
* **Java/Kotlin Analysis:**
* Adds Java 17 (Temurin) setup using `actions/setup-java@v4` with Gradle caching.
* Replaces `codeql-action/autobuild` with a specific Gradle command (`./gradlew :pollingengine:compileKotlinMetadata`) to build only the common Kotlin metadata. This avoids unnecessary Android/iOS toolchain setup and potential conflicts during CodeQL's Java analysis.
* **Swift Analysis:**
* Replaces `codeql-action/autobuild` with an explicit `xcodebuild` command to build the iOS app for the simulator. This ensures the Swift code is compiled in a way that CodeQL can effectively analyze, with `CODE_SIGNING_ALLOWED=NO` to avoid signing issues in CI.
These changes provide more targeted and reliable builds for CodeQL analysis, focusing on the relevant source code for each language.
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a public polling API facade and internalizes the engine implementation to provide a cleaner, more stable public interface for consumers. The refactoring focuses on simplifying the API surface while maintaining backward compatibility for core functionality.
- Introduces
Pollingfacade object as the primary entry point withPollingApiinterface - Internalizes
PollingEngineand related implementation details - Replaces
LoggerandMetricsinterfaces with callback hooks for observability - Updates package coordinates from
io.github.bosankustoin.androidplay
Reviewed Changes
Copilot reviewed 24 out of 27 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| PollingEngineCoreTests.kt | New comprehensive test suite covering success, failure, timeout, and compose scenarios |
| PollingEngineCancellationTest.kt | Updated to use new Polling.run API |
| BackoffPolicyTest.kt | Reformatted test assertions for better readability |
| PollingConfigBuilder.kt | New builder class with comprehensive documentation and validation |
| PollingSession.kt | New lightweight session handle replacing internal Handle |
| PollingEngine.kt | Internalized with Flow-based API and improved pause/resume logic |
| PollingAPI.kt | New public interface defining stable API contract |
| Polling.kt | New facade object implementing PollingApi and delegating to internal engine |
| Core.kt | Internalized strategies and error codes, renamed DefaultRetryPredicates to RetryPredicates |
| App.kt | Updated sample app to use new facade with comprehensive ViewModel implementation |
Comments suppressed due to low confidence (1)
pollingengine/src/commonMain/kotlin/in/androidplay/pollingengine/polling/PollingConfigBuilder.kt:1
- The hardcoded error code
-1is a magic number. Consider extracting this as a named constant to improve code maintainability and make the default behavior more explicit.
package `in`.androidplay.pollingengine.polling
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
...gengine/src/commonTest/kotlin/in/androidplay/pollingengine/polling/PollingEngineCoreTests.kt
Show resolved
Hide resolved
pollingengine/src/commonMain/kotlin/in/androidplay/pollingengine/polling/PollingEngine.kt
Show resolved
Hide resolved
composeApp/src/commonMain/kotlin/in/androidplay/pollingengine/PollingViewModel.kt
Show resolved
Hide resolved
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit refactors the PollingEngine by introducing a public
PollingApiinterface and aPollingfacade object as the primary entry point for consumers. ThePollingEngineitself is now internal, encapsulating the implementation details.Key Changes:
Polling.kt,PollingApi.kt):PollingApiinterface defining the stable public contract for interacting with the polling functionality.Pollingobject that implementsPollingApiand delegates calls to the internalPollingEngine. This is now the recommended way for apps to use the library.PollingEngine.Handleis replaced by a publicPollingSessiondata class, which is returned byPolling.startPollingand contains the sessionid.PollingEngine.ktInternalization:PollingEngineobject and itsStateenum are nowinternal.PollingEngine(e.g.,startPolling,pollUntil,cancel,pause,resume,updateBackoff,cancelAll,shutdown,compose) are nowinternalorfun(if they were already part of the internal API). Consumers should use thePollingfacade.metricsrecording calls (recordAttempt,recordResult,recordComplete) fromPollingEngineas theMetricsinterface was removed.PollingConfig.kt,Core.kt):LoggerandMetricsinterfaces and their usage inPollingConfigandPollingConfigBuilder. Observability is now primarily throughonAttempt,onResult, andonCompletehooks.ErrorCodesobject is nowinternal.FetchStrategy,SuccessStrategy,RetryStrategyinterfaces are nowinternal.DefaultRetryPredicatesobject renamed toRetryPredicatesand itsretryOnNetworkServerTimeoutpredicate is renamed tonetworkOrServerOrTimeout.throwableMapperinPollingConfigandPollingConfigBuildernow uses a stable public error code (-1) instead of the internalErrorCodes.UNKNOWN_ERROR_CODE.groupID inpollingengine/build.gradle.ktsfromio.github.bosankustoin.androidplay.in.androidplay:pollingengine.Pollingfacade andRetryPredicates.Polling.startPolling,pause,resume,updateBackoff,cancel.RetryPredicates.PollingEngineApi->PollingApi, usePollingfacade).docs/PollingEngine.md(renamed fromcomposeApp/src/androidMain/kotlin/in/androidplay/pollingengine/Platform.android.ktwhich was an empty file): This new Markdown file serves as a developer guide, explaining the public API, concepts, and usage.docs/ci-setup.mdwith the new timestamp.gradle/libs.versions.toml:pollingengineartifact path updated toin.androidplay:pollingengine.kotlinx-coroutinesversion updated to1.10.2.App.kt):Pollingfacade instead ofPollingEnginedirectly.Polling.runfor one-shot polling instead ofPollingEngine.startPollingwith a job for simplified start/stop logic.PollingEngine.Handleand usesPollingSession(though direct handle usage is reduced due toPolling.run).isPausedstate that controls the countdown timer, asPolling.runis synchronous. The actual engine pause/resume is not used in this simplified flow.pollingJoblaunched forPolling.run.RetryPredicates(Always,Never,Network/Server/Timeout).AnimatedVisibilityandRoundedCornerShape.kotlin.math.round.PollingEngineCoreTests.kt,PollingEngineCancellationTest.kt):PollingEngineCoreTests.ktwith basic tests for success, non-retryable failure, max attempts, overall timeout, andcomposebehavior.PollingEngineCancellationTest.ktto usePolling.run.composeApp/src/commonMain/kotlin/in/androidplay/pollingengine/Greeting.ktcomposeApp/src/commonMain/kotlin/in/androidplay/pollingengine/Platform.ktcomposeApp/src/androidMain/kotlin/in/androidplay/pollingengine/Platform.android.kt(content moved todocs/PollingEngine.md)Impact:
Pollingfacade.