Skip to content

Upgrade library to AGP 9+#90

Open
HeyPouya wants to merge 1 commit intomasterfrom
feature/upgrade-to-agp-9
Open

Upgrade library to AGP 9+#90
HeyPouya wants to merge 1 commit intomasterfrom
feature/upgrade-to-agp-9

Conversation

@HeyPouya
Copy link
Owner

@HeyPouya HeyPouya commented Mar 20, 2026

Update Gradle to version 9.4.0, and enhance ProGuard rules

Summary by CodeRabbit

  • New Features

    • Added DSL builders for simpler and more intuitive configuration of dialogs and stores.
    • Introduced comprehensive documentation with configuration reference tables, quick-start examples, and architecture overview.
  • Bug Fixes

    • Fixed package import paths affecting download utilities.
    • Improved error handling when opening store links.
  • Improvements

    • Enhanced state management for download operations, including better failure handling.
    • Updated configuration models for immutability and safety.
    • Improved default theme handling across components.

@HeyPouya HeyPouya self-assigned this Mar 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

Walkthrough

Comprehensive refactoring spanning ~90 files: rebranded repository references; converted mutable models to immutable; replaced manual Parcelable implementations with @Parcelize plugin; introduced DSL builder classes for improved API ergonomics; refactored ViewModels to separate persistent state from side effects; fixed package name typo (donwloadapkdownloadapk); updated Gradle tooling and versions; added ProGuard consumer rules; and enhanced documentation with KDoc.

Changes

Cohort / File(s) Summary
Documentation & Branding
README.md
Updated repository/org references, restructured sections (Features, Quick Start, DSL Builders, Configuration Reference), expanded Installation instructions for dependencyResolutionManagement, changed variable names (storesListstores), updated sample code with new themes/defaults, and added full Apache 2.0 license text.
Build Configuration
build.gradle.kts, app/build.gradle.kts, gradle/libs.versions.toml, gradle/wrapper/gradle-wrapper.properties, gradle/gradle-daemon-jvm.properties, settings.gradle.kts
Removed jetbrainsKotlinAndroid plugin; moved JVM toolchain config to top-level; bumped Android Gradle Plugin (8.13.2 → 9.1.0), Kotlin (2.2.21 → 2.3.20), and Gradle (8.14.3 → 9.4.0); added Foojay JVM toolchain resolver plugin; replaced Kotlin Android plugin with kotlin-parcelize.
Build Convention Plugin
build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt
Replaced jetbrainsKotlinAndroid with kotlin.parcelize plugin; added consumerProguardFiles("consumer-rules.pro"); switched Android DSL import; removed explicit JVM toolchain configuration.
Core Model Immutability
appupdater/src/main/java/.../UpdaterDialogData.kt, appupdater/src/main/java/.../UpdaterFragmentModel.kt, directdownload/src/main/kotlin/.../DirectDownloadListItem.kt, store/src/main/java/.../StoreListItem.kt
Converted all mutable var properties to immutable val; replaced manual Parcelable implementations with @Parcelize annotation; adjusted default values and removed parcel-related boilerplate code.
Store Implementations (@Parcelize Migration)
store/src/main/java/.../stores/{GooglePlayStore, AmazonAppStore, Aptoide, CafeBazaarStore, FDroid, HuaweiAppGallery, LenovoAppCenter, MiGetAppStore, MyketStore, NineApps, OneStoreAppMarket, OppoAppMarket, SamsungGalaxyStore, TencentAppStore, VAppStore, ZTEAppCenter}.kt
Removed manual Parcelable constructors/methods (writeToParcel, describeContents, CREATOR) from all 16 store classes; added @Parcelize annotation; retained packageName property and AppStore interface implementations.
DSL Builder Classes
appupdater/src/main/java/.../dsl/DSLUtils.kt, compose/src/main/java/.../dsl/DSLUtils.kt
Introduced StoreListItemBuilder, DirectDownloadListItemBuilder, and UpdaterDialogDataBuilder (Fragment); created corresponding builders for Compose with mutable properties and build() methods; updated DSL entry points (store, directDownload, updateDialogBuilder/updaterDialogData) to use new builders with default values (theme defaults to Theme.SYSTEM_DEFAULT).
ViewModel & State Refactoring
appupdater/src/main/java/.../AppUpdaterViewModel.kt, compose/src/main/java/.../AndroidAppUpdaterViewModel.kt
Replaced single MutableStateFlow with persistent DialogScreenUiState (Idle/UpdateInProgress) and separate Channel<DialogScreenStates> for side effects; updated handleIntent to emit one-shot actions via side effects; updated observeUpdateInProgressStatus to handle new Failed download state.
Dialog & Screen UI Updates
appupdater/src/main/java/.../AppUpdaterDialog.kt, compose/src/main/java/.../AndroidAppUpdaterScreen.kt
Fixed import typo (donwloadapkdownloadapk); refactored subscribeToViewModel to split screenState and sideEffect observation; adjusted trailing commas in multi-line calls; updated KDoc descriptions.
Adapter Refactoring
appupdater/src/main/java/.../adapters/{DirectRecyclerAdapter, StoresRecyclerAdapter}.kt
Renamed ViewHolder types (SoresViewHolderDirectDownloadViewHolder/StoresViewHolder); updated adapter generic parameters and onBind/onCreateViewHolder method signatures; no binding logic changes.
Package Name Corrections
directdownload/src/main/kotlin/.../downloadapk/{APKDownloadManager, APKFileProvider, APKFileProviderImpl, DownloadAPKHelper, DownloadManagerRequestCreator}.kt, related receiver & test files
Corrected package path from com.pouyaheydari.appupdater.directdownload.utils.donwloadapk to ...utils.downloadapk across ~11 files; updated corresponding imports in receiver and test files.
Permission & Utility Updates
directdownload/src/main/kotlin/.../permission/{DownloadAPKPermissionFactory, DownloadAPKPermissionForOAndBelow}.kt, appupdater/src/main/java/.../ErrorCallbackHolder.kt
Corrected class name typo (DownloadAPKPermissionForOAndBellowDownloadAPKPermissionForOAndBelow); refactored permission resolver to single-expression if/else; updated ErrorCallbackHolder KDoc; removed unused Typeface import.
Download State & Callbacks
directdownload/src/main/kotlin/.../domain/DownloadState.kt, store/src/main/java/.../AppStoreCallback.kt
Added new Failed(reason: String?) state to DownloadState sealed interface; changed AppStoreCallback.Failure exception type from ActivityNotFoundException to generic Exception; added/enhanced KDoc across both files.
Store Domain Documentation
store/src/main/java/.../domain/{StoreFactory, StoreIntentBuilder, StoreManager, AppStore, AppStoreType}.kt
Enhanced KDoc for factory, builder, manager, and store interface; added null-check in showAppInSelectedStore for context parameter with IllegalStateException callback; adjusted trailing commas in constructor signatures.
ProGuard Consumer Rules
appupdater/consumer-rules.pro, compose/consumer-rules.pro, directdownload/consumer-rules.pro, store/consumer-rules.pro
Added new ProGuard/R8 rules for each module to preserve public API classes (e.g., AppUpdaterDialog, UpdaterDialogData, builders, model types), DSL packages, and Parcelable CREATOR fields to prevent obfuscation and removal.
Documentation Updates
store/src/main/java/.../AppStoreCallback.kt, compose/src/main/java/.../models/UpdaterDialogData.kt
Enhanced KDoc with detailed descriptions of sealed interface, callback types, builder intent, and Compose dialog data properties; clarified usage of typeface/theme and callback semantics.
Test Updates
appupdater/src/test/java/.../AppUpdaterViewModelTest.kt, directdownload/src/test/kotlin/.../downloadapk/{APKDownloadManagerTest, APKFileProviderImplTest, DownloadAPKHelperKtTest, DownloadManagerRequestCreatorTest}.kt, directdownload/src/test/kotlin/.../permission/{DownloadAPKPermissionFactoryTest, DownloadAPKPermissionForOAndBelowTest}.kt
Updated AppUpdaterViewModelTest to assert side effects instead of screen state transitions; corrected package declarations in all directdownload tests from donwloadapk to downloadapk; renamed test class and assertions for permission handler (...Bellow...Below); adjusted trailing commas and removed empty lines.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Add AndroidLibraryPlugin #82: Updates to build-logic AndroidLibraryPlugin and Gradle plugin configurations that coordinate with this PR's plugin and toolchain changes.

Poem

🐰 A hop, a skip, through code we run,
Models immutable, refactored and done!
Parcelize magic, builders so neat,
State flows divided, side effects complete.
From typos corrected to gradle refined,
A stronger foundation we leave behind! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary change: upgrading Android Gradle Plugin (AGP) from 8.x to 9+, which is the most significant infrastructure change in this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/upgrade-to-agp-9

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/build.gradle.kts`:
- Around line 36-38: The kotlin {} block requires the Kotlin Android plugin to
be applied; add alias(libs.plugins.jetbrainsKotlinAndroid) to the plugins block
alongside alias(libs.plugins.androidApplication) and
alias(libs.plugins.compose.compiler) so the org.jetbrains.kotlin.android plugin
is applied for the kotlin { jvmToolchain(17) } configuration, and ensure
gradle/libs.versions.toml defines the plugin alias as jetbrainsKotlinAndroid = {
id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } so
libs.plugins.jetbrainsKotlinAndroid resolves.

In `@appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt`:
- Around line 13-55: Extract the duplicated builder classes StoreListItemBuilder
and DirectDownloadListItemBuilder into a shared module (e.g., :store or
:dsl-core) and replace the duplicate implementations in the Compose and Fragment
DSL modules with imports from that shared module; update the build system
(module dependency) and package/imports so UpdaterDialogDataBuilder continues to
reference the shared StoreListItemBuilder/DirectDownloadListItemBuilder types
and ensure the shared builders still produce StoreListItem and
DirectDownloadListItem with the same property names (store, title, icon, url)
and build() signatures.
- Around line 14-20: Replace the lateinit store property in StoreListItemBuilder
with a nullable var (store: AppStore? = null) and add explicit validation in
build(): check that store is not null and throw a clear IllegalStateException
(e.g., "StoreListItemBuilder.store must be set") before constructing
StoreListItem; update the build() method to use the validated non-null store
when calling StoreListItem(store = ..., title = title, icon = icon) so callers
get a helpful error instead of UninitializedPropertyAccessException.

In
`@appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt`:
- Around line 139-144: Remove the unused sealed variants
DialogScreenStates.Empty, DialogScreenStates.ShowUpdateInProgress, and
DialogScreenStates.HideUpdateInProgress from the sealed interface definition in
DialogScreenStates (delete their enum/variant declarations) and delete the dead
when-branch in AppUpdaterDialog.kt that matches those three cases (the branch
containing the comment "handled by screenState"). After removal, search for any
remaining references to those symbols (e.g., usages in other files or imports)
and clean them up, then run a build to ensure no unresolved references remain.

In
`@appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt`:
- Around line 41-48: In AppUpdaterViewModel where DialogScreenIntents are mapped
to _sideEffect.trySend (for cases like DialogScreenIntents.OnDirectLinkClicked,
OnStoreClicked, OnOpeningStoreFailed producing DialogScreenStates.DownloadApk,
OpenStore, ExecuteErrorCallback), handle the trySend result: inspect the
ChannelResult (use onFailure or check isFailure) and either log the failure
(include a TAG and context such as which DialogScreenState failed) or switch to
calling send() from a coroutine scope for critical events (e.g., DownloadApk) so
failures are not dropped silently.

In
`@appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt`:
- Around line 21-28: UpdaterDialogData was changed to immutable by turning its
public properties (title, description, storeList, directDownloadList,
isForceUpdate, typeface, errorWhileOpeningStoreCallback, theme) into vals, which
is a breaking API change for callers that mutate the instance; restore the
original mutability by making those properties vars again on the
UpdaterDialogData data class (or alternatively provide explicit copy/with or
builder APIs preserving binary compatibility) so existing consumers that set
properties after construction continue to work.

In `@compose/src/main/java/com/pouyaheydari/appupdater/compose/dsl/DSLUtils.kt`:
- Around line 13-19: The builder currently uses lateinit var store which causes
an UninitializedPropertyAccessException if unset; change store to a nullable var
store: AppStore? = null in StoreListItemBuilder and add an explicit validation
in build() (e.g., requireNotNull(store) { "StoreListItemBuilder.store must be
set before build()" }) before constructing and returning StoreListItem (use the
non-null value after validation). This provides a clear error message and avoids
lateinit.
- Around line 24-29: The builder DirectDownloadListItemBuilder currently allows
title and url to remain empty; update build() to validate required fields (title
and url) before constructing DirectDownloadListItem: check that title is not
blank and url is a non-empty/valid string (or matches a simple URL pattern) and
throw a clear IllegalStateException or IllegalArgumentException if validation
fails, so callers get an early, descriptive error instead of creating an invalid
DirectDownloadListItem.

In
`@directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt`:
- Around line 13-15: The data class DirectDownloadListItem changed its public
properties title and url from var to val which removes setters and is a breaking
API/ABI change; restore compatibility by making the properties mutable again
(use var for title and url on DirectDownloadListItem) so existing consumers and
generated setters remain available, ensuring the public API surface is
unchanged.

In
`@directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProvider.kt`:
- Line 1: The package rename changed the FQCN of the public interface
APKFileProvider and will break downstream imports; restore a temporary
compatibility shim by adding a deprecated typealias named APKFileProvider in the
old package that aliases the new APKFileProvider (so existing imports still
resolve), annotate it with `@Deprecated` with a message pointing to the new
package/FQCN and a ReplaceWith suggestion, and keep the shim for at least one
release before removal to give users time to migrate.

In
`@directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelper.kt`:
- Line 1: The package rename changed the FQN of checkPermissionsAndDownloadApk
and will break existing consumers; restore a deprecated compatibility wrapper in
the old package by adding a new file in the previous package namespace
(utils.donwloadapk) that declares a public function
checkPermissionsAndDownloadApk annotated `@Deprecated` which simply forwards its
parameters to the relocated implementation (the new
checkPermissionsAndDownloadApk) and re-exports any needed types, and include a
deprecation message pointing callers to the new package and note removal in the
next release.

In `@gradle/libs.versions.toml`:
- Around line 6-7: The agp version declared as agp = "9.1.0" in
libs.versions.toml is invalid; update the agp entry to a real released Android
Gradle Plugin version (for example "2.3.0" or the intended target) by replacing
the value for the agp key, then verify resolution against Maven Central and that
the Gradle wrapper (9.4.0) and Kotlin (kotlin = "2.3.20") remain compatible;
after changing the agp value, run a Gradle sync/build to ensure the plugin
resolves correctly.

In `@README.md`:
- Line 155: Update the two heading lines "Fragment DSL" and "Compose DSL" to use
one-level deeper headings (change from #### to ###) so they correctly follow the
parent "DSL Builders" (which is ##) and comply with MD001; locate the headings
by their exact text ("Fragment DSL" and "Compose DSL") in README.md and replace
the four-hash markers with three-hash markers.
- Line 2: The image tag(s) in README.md are missing alt text; update each <img>
element (e.g., the <img
src="https://raw.githubusercontent.com/HeyPouya/AndroidAppUpdater/master/pics/icon.png"
width="250">) to include a descriptive alt attribute (for example alt="Android
App Updater icon" or equivalent) to satisfy accessibility/MD045 requirements and
apply the same change to the other image(s).
- Around line 314-322: Update the fenced code block that starts with the line
"AndroidAppUpdater/" so the opening triple backticks include a language
identifier (e.g., change ``` to ```text) to satisfy MD040; locate the block
containing the ASCII architecture diagram (the lines showing
"AndroidAppUpdater/" and its subdirectories) and add the identifier to the
opening ``` only.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt`:
- Around line 26-28: In Builder.withPackage (the function that sets
storePackageName) the require message has a typo "most" — update the exception
string to read "must not be empty" (i.e., change the require message for
storePackageName in withPackage to "Store's package name must not be empty") so
the validation message is correct; locate the require(...) call in
Builder.withPackage and replace the message accordingly.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt`:
- Around line 15-18: The default for StoreListItem.icon was changed to 0 which
breaks previews/tests that relied on the previous drawable default; update any
preview/test instantiations of StoreListItem to pass an explicit drawable
resource (e.g., R.drawable.appupdater_ic_cloud or another appropriate drawable)
when constructing StoreListItem, or alternatively revert/change the default in
the StoreListItem declaration; search for usages of StoreListItem in
preview/test files and add the icon argument to each call to restore the
expected icon behavior.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt`:
- Around line 4-15: Add the Kotlin Parcelize plugin to every module that uses
`@Parcelize` (modules: store, directdownload, appupdater) so files like NineApps
(class NineApps in NineApps.kt) compile; update each module's Gradle settings to
apply the Kotlin parcelize plugin (the kotlin-parcelize /
org.jetbrains.kotlin.plugin.parcelize plugin) in the plugins block of that
module's build.gradle.kts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa7b2ed7-9ddd-4b2b-bdd4-5aad6589f9cb

📥 Commits

Reviewing files that changed from the base of the PR and between 42eb560 and 8b500a3.

📒 Files selected for processing (64)
  • README.md
  • app/build.gradle.kts
  • appupdater/consumer-rules.pro
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/DirectRecyclerAdapter.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/adapters/StoresRecyclerAdapter.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterDialogData.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/UpdaterFragmentModel.kt
  • appupdater/src/main/java/com/pouyaheydari/appupdater/main/utils/ErrorCallbackHolder.kt
  • appupdater/src/test/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModelTest.kt
  • build-logic/convention/src/main/java/com/pouyaheydari/appupdater/convention/plugins/AndroidLibraryPlugin.kt
  • build.gradle.kts
  • compose/consumer-rules.pro
  • compose/src/main/java/com/pouyaheydari/appupdater/compose/dsl/DSLUtils.kt
  • compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterScreen.kt
  • compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/AndroidAppUpdaterViewModel.kt
  • compose/src/main/java/com/pouyaheydari/appupdater/compose/ui/models/UpdaterDialogData.kt
  • directdownload/consumer-rules.pro
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/data/DirectDownloadListItem.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/domain/DownloadState.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/receiver/DownloadFinishedReceiver.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManager.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProvider.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImpl.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelper.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreator.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactory.kt
  • directdownload/src/main/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelow.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKDownloadManagerTest.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/APKFileProviderImplTest.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadAPKHelperKtTest.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/downloadapk/DownloadManagerRequestCreatorTest.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionFactoryTest.kt
  • directdownload/src/test/kotlin/com/pouyaheydari/appupdater/directdownload/utils/permission/DownloadAPKPermissionForOAndBelowTest.kt
  • gradle/gradle-daemon-jvm.properties
  • gradle/libs.versions.toml
  • gradle/wrapper/gradle-wrapper.properties
  • settings.gradle.kts
  • store/consumer-rules.pro
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/AppStoreCallback.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreFactory.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreManager.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AmazonAppStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/AppStoreType.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/Aptoide.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/CafeBazaarStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/FDroid.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/GooglePlayStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/HuaweiAppGallery.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/LenovoAppCenter.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MiGetAppStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/MyketStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OneStoreAppMarket.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/OppoAppMarket.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/SamsungGalaxyStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/TencentAppStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/VAppStore.kt
  • store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/ZTEAppCenter.kt
💤 Files with no reviewable changes (1)
  • build.gradle.kts

Comment on lines 13 to +55
*/
inline fun store(block: StoreListItem.() -> Unit): StoreListItem = StoreListItem().apply(block)
class StoreListItemBuilder {
lateinit var store: AppStore
var title: String = ""
var icon: Int = 0

fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon)
}

/**
* Mutable builder for [DirectDownloadListItem] used in DSL context.
*/
class DirectDownloadListItemBuilder {
var title: String = ""
var url: String = ""

fun build(): DirectDownloadListItem = DirectDownloadListItem(title = title, url = url)
}

/**
* Mutable builder for [UpdaterDialogData] used in DSL context.
*/
class UpdaterDialogDataBuilder {
var title: String = ""
var description: String = ""
var storeList: List<StoreListItem> = listOf()
var directDownloadList: List<DirectDownloadListItem> = listOf()
var isForceUpdate: Boolean = false
var typeface: Typeface? = null
var errorWhileOpeningStoreCallback: ((String) -> Unit)? = null
var theme: Theme = Theme.SYSTEM_DEFAULT

fun build(): UpdaterDialogData = UpdaterDialogData(
title = title,
description = description,
storeList = storeList,
directDownloadList = directDownloadList,
isForceUpdate = isForceUpdate,
typeface = typeface,
errorWhileOpeningStoreCallback = errorWhileOpeningStoreCallback,
theme = theme,
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider extracting shared builders to a common module.

StoreListItemBuilder and DirectDownloadListItemBuilder are duplicated between the Compose and Fragment DSL modules. Since both build the same model classes (StoreListItem, DirectDownloadListItem), consider extracting them to a shared module (e.g., :store or a new :dsl-core) to reduce duplication and ensure consistent behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt`
around lines 13 - 55, Extract the duplicated builder classes
StoreListItemBuilder and DirectDownloadListItemBuilder into a shared module
(e.g., :store or :dsl-core) and replace the duplicate implementations in the
Compose and Fragment DSL modules with imports from that shared module; update
the build system (module dependency) and package/imports so
UpdaterDialogDataBuilder continues to reference the shared
StoreListItemBuilder/DirectDownloadListItemBuilder types and ensure the shared
builders still produce StoreListItem and DirectDownloadListItem with the same
property names (store, title, icon, url) and build() signatures.

Comment on lines +14 to +20
class StoreListItemBuilder {
lateinit var store: AppStore
var title: String = ""
var icon: Int = 0

fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid lateinit in builder — use nullable with validation for clearer errors.

Same issue as the Compose version: if the user forgets to set store, the UninitializedPropertyAccessException provides poor guidance. Use a nullable property with explicit validation.

Proposed fix
 class StoreListItemBuilder {
-    lateinit var store: AppStore
+    var store: AppStore? = null
     var title: String = ""
     var icon: Int = 0

-    fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon)
+    fun build(): StoreListItem = StoreListItem(
+        store = requireNotNull(store) { "StoreListItemBuilder requires 'store' to be set" },
+        title = title,
+        icon = icon
+    )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class StoreListItemBuilder {
lateinit var store: AppStore
var title: String = ""
var icon: Int = 0
fun build(): StoreListItem = StoreListItem(store = store, title = title, icon = icon)
}
class StoreListItemBuilder {
var store: AppStore? = null
var title: String = ""
var icon: Int = 0
fun build(): StoreListItem = StoreListItem(
store = requireNotNull(store) { "StoreListItemBuilder requires 'store' to be set" },
title = title,
icon = icon
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@appupdater/src/main/java/com/pouyaheydari/appupdater/main/dsl/DSLUtils.kt`
around lines 14 - 20, Replace the lateinit store property in
StoreListItemBuilder with a nullable var (store: AppStore? = null) and add
explicit validation in build(): check that store is not null and throw a clear
IllegalStateException (e.g., "StoreListItemBuilder.store must be set") before
constructing StoreListItem; update the build() method to use the validated
non-null store when calling StoreListItem(store = ..., title = title, icon =
icon) so callers get a helpful error instead of
UninitializedPropertyAccessException.

Comment on lines +139 to +144

// Persistent states are handled by screenState collector above
DialogScreenStates.HideUpdateInProgress,
DialogScreenStates.ShowUpdateInProgress,
DialogScreenStates.Empty,
-> { /* handled by screenState */ }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if these DialogScreenStates variants are used anywhere else
echo "Searching for usages of HideUpdateInProgress, ShowUpdateInProgress, Empty..."
rg -n "DialogScreenStates\.(HideUpdateInProgress|ShowUpdateInProgress|Empty)" --type kotlin

Repository: HeyPouya/AndroidAppUpdater

Length of output: 569


🏁 Script executed:

fd -e kt -type f | xargs grep -l "sealed class DialogScreenStates" | head -5

Repository: HeyPouya/AndroidAppUpdater

Length of output: 240


🏁 Script executed:

fd DialogScreenStates .

Repository: HeyPouya/AndroidAppUpdater

Length of output: 160


🏁 Script executed:

cat -n ./appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/model/DialogScreenStates.kt

Repository: HeyPouya/AndroidAppUpdater

Length of output: 788


🏁 Script executed:

rg -n "(HideUpdateInProgress|ShowUpdateInProgress|Empty)" --type kotlin appupdater/src/main/java/com/pouyaheydari/appupdater/

Repository: HeyPouya/AndroidAppUpdater

Length of output: 1531


Remove unused DialogScreenStates variants from sealed interface definition.

These three variants (Empty, ShowUpdateInProgress, HideUpdateInProgress) are defined in the sealed interface but never emitted to the sideEffect channel anywhere in the codebase. They only appear in the dead code branch at lines 139–144 of AppUpdaterDialog.kt. Remove the variant definitions from DialogScreenStates.kt and the corresponding dead code branch from the when statement.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterDialog.kt`
around lines 139 - 144, Remove the unused sealed variants
DialogScreenStates.Empty, DialogScreenStates.ShowUpdateInProgress, and
DialogScreenStates.HideUpdateInProgress from the sealed interface definition in
DialogScreenStates (delete their enum/variant declarations) and delete the dead
when-branch in AppUpdaterDialog.kt that matches those three cases (the branch
containing the comment "handled by screenState"). After removal, search for any
remaining references to those symbols (e.g., usages in other files or imports)
and clean them up, then run a build to ensure no unresolved references remain.

Comment on lines +41 to +48
is DialogScreenIntents.OnDirectLinkClicked ->
_sideEffect.trySend(DialogScreenStates.DownloadApk(intent.item.url))

is DialogScreenIntents.OnStoreClicked ->
_sideEffect.trySend(DialogScreenStates.OpenStore(intent.item.store))

is DialogScreenIntents.OnOpeningStoreFailed ->
_sideEffect.trySend(DialogScreenStates.ExecuteErrorCallback(intent.store.getUserReadableName()))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider handling trySend result for critical side effects.

trySend() can fail silently if the channel buffer is full (returns ChannelResult.isFailure). For non-critical UI events this is typically acceptable, but if any of these side effects are critical (e.g., DownloadApk), consider logging failures or using send() within a coroutine scope.

Example: Log on failure
_sideEffect.trySend(DialogScreenStates.DownloadApk(intent.item.url))
    .onFailure { Log.w(TAG, "Failed to send DownloadApk side effect") }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@appupdater/src/main/java/com/pouyaheydari/appupdater/main/ui/AppUpdaterViewModel.kt`
around lines 41 - 48, In AppUpdaterViewModel where DialogScreenIntents are
mapped to _sideEffect.trySend (for cases like
DialogScreenIntents.OnDirectLinkClicked, OnStoreClicked, OnOpeningStoreFailed
producing DialogScreenStates.DownloadApk, OpenStore, ExecuteErrorCallback),
handle the trySend result: inspect the ChannelResult (use onFailure or check
isFailure) and either log the failure (include a TAG and context such as which
DialogScreenState failed) or switch to calling send() from a coroutine scope for
critical events (e.g., DownloadApk) so failures are not dropped silently.

Both Compose and Fragment APIs offer Kotlin DSL builders for a more expressive syntax.

AppUpdaterDialog.getInstance(data).show(supportFragmentManager, "UPDATE_DIALOG")
#### Fragment DSL
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix heading level increment.

The headings "Fragment DSL" and "Compose DSL" use #### (h4) but should use ### (h3) since the parent heading "DSL Builders" is ## (h2). Heading levels should only increment by one level at a time (MD001).

Proposed fix
-#### Fragment DSL
+### Fragment DSL
-#### Compose DSL
+### Compose DSL

Also applies to: 183-183

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 155-155: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 155, Update the two heading lines "Fragment DSL" and
"Compose DSL" to use one-level deeper headings (change from #### to ###) so they
correctly follow the parent "DSL Builders" (which is ##) and comply with MD001;
locate the headings by their exact text ("Fragment DSL" and "Compose DSL") in
README.md and replace the four-hash markers with three-hash markers.

Comment on lines +314 to 322
```
AndroidAppUpdater/
├── core/ # Theme enum and shared constants (pure Kotlin)
├── store/ # Store implementations, StoreFactory, icons
├── directdownload/ # APK download via DownloadManager + installation
├── appupdater/ # Fragment/XML UI (published as "main")
├── compose/ # Jetpack Compose UI
└── app/ # Sample/demo application
```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding a language identifier to the code block.

The architecture diagram code block lacks a language specifier (MD040). While ASCII diagrams don't have a specific language, using text or an empty identifier can silence the linter warning.

Proposed fix
-```
+```text
 AndroidAppUpdater/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
AndroidAppUpdater/
├── core/ # Theme enum and shared constants (pure Kotlin)
├── store/ # Store implementations, StoreFactory, icons
├── directdownload/ # APK download via DownloadManager + installation
├── appupdater/ # Fragment/XML UI (published as "main")
├── compose/ # Jetpack Compose UI
└── app/ # Sample/demo application
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 314-314: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 314 - 322, Update the fenced code block that starts
with the line "AndroidAppUpdater/" so the opening triple backticks include a
language identifier (e.g., change ``` to ```text) to satisfy MD040; locate the
block containing the ASCII architecture diagram (the lines showing
"AndroidAppUpdater/" and its subdirectories) and add the identifier to the
opening ``` only.

Comment on lines 26 to 28
fun withPackage(storePackageName: String): Builder {
require(storePackageName.isNotBlank()) { "Store's package name most not be empty" }
this.storePackageName = storePackageName
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in exception message (mostmust).

Line 27 has a wording typo in the thrown error string.

✏️ Proposed fix
-            require(storePackageName.isNotBlank()) { "Store's package name most not be empty" }
+            require(storePackageName.isNotBlank()) { "Store's package name must not be empty" }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun withPackage(storePackageName: String): Builder {
require(storePackageName.isNotBlank()) { "Store's package name most not be empty" }
this.storePackageName = storePackageName
fun withPackage(storePackageName: String): Builder {
require(storePackageName.isNotBlank()) { "Store's package name must not be empty" }
this.storePackageName = storePackageName
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreIntentBuilder.kt`
around lines 26 - 28, In Builder.withPackage (the function that sets
storePackageName) the require message has a typo "most" — update the exception
string to read "must not be empty" (i.e., change the require message for
storePackageName in withPackage to "Store's package name must not be empty") so
the validation message is correct; locate the require(...) call in
Builder.withPackage and replace the message accordingly.

Comment on lines 15 to +18
data class StoreListItem(
var store: AppStore = StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, ""),
var title: String = "",
var icon: Int = R.drawable.appupdater_ic_cloud,
) : Parcelable {
private constructor(parcel: Parcel) : this(
ParcelCompat.readParcelable(parcel, AppStore::class.java.classLoader, AppStore::class.java) ?: StoreFactory.getStore(AppStoreType.GOOGLE_PLAY, ""),
parcel.readString().orEmpty(),
parcel.readInt(),
)

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(store, flags)
parcel.writeString(title)
parcel.writeInt(icon)
}

override fun describeContents(): Int = 0

companion object CREATOR : Parcelable.Creator<StoreListItem> {
override fun createFromParcel(parcel: Parcel): StoreListItem {
return StoreListItem(parcel)
}

override fun newArray(size: Int): Array<StoreListItem?> {
return arrayOfNulls(size)
}
}
}
val store: AppStore,
val title: String = "",
val icon: Int = 0,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for usages of StoreListItem constructor to assess impact
rg -n "StoreListItem\(" --type kotlin -A 2

Repository: HeyPouya/AndroidAppUpdater

Length of output: 16449


Breaking change: icon default changed from drawable resource to 0, affecting preview and test data.

The icon parameter default was changed from a drawable resource to 0, which breaks code paths that relied on the default icon value. Multiple preview and test files instantiate StoreListItem without explicitly providing the icon parameter, expecting the previous drawable default. These will now render without an icon.

The store parameter is now required (no default value), but all codebase usages already provide it explicitly, so this has limited impact.

Update preview data files to explicitly pass icon values (e.g., R.drawable.appupdater_ic_cloud or a similar default drawable), or document why 0 is the correct new default.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/StoreListItem.kt`
around lines 15 - 18, The default for StoreListItem.icon was changed to 0 which
breaks previews/tests that relied on the previous drawable default; update any
preview/test instantiations of StoreListItem to pass an explicit drawable
resource (e.g., R.drawable.appupdater_ic_cloud or another appropriate drawable)
when constructing StoreListItem, or alternatively revert/change the default in
the StoreListItem declaration; search for usages of StoreListItem in
preview/test files and add the icon argument to each call to restore the
expected icon behavior.

Comment on lines +4 to +15
import kotlinx.parcelize.Parcelize

internal const val NINE_APPS_STORE_URL = "nineapps://AppDetail?id="
internal const val NINE_APPS_PACKAGE = "com.gamefun.apk2u"

/**
* Opens application's page in [9-Apps](https://www.9apps.com/)
*/
internal data class NineApps(val packageName: String) : AppStore {
constructor(parcel: Parcel) : this(parcel.readString().orEmpty())

@Parcelize
internal data class NineApps(
val packageName: String,
) : AppStore {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Modules/files using `@Parcelize` =="
rg -n --glob '**/*.kt' '@Parcelize'

echo
echo "== Gradle files declaring parcelize plugin =="
fd -HI 'build.gradle.kts' | while read -r f; do
  if rg -n --no-heading -e 'kotlin-parcelize' -e 'org.jetbrains.kotlin.plugin.parcelize' "$f" >/dev/null; then
    echo "-- $f"
    rg -n --no-heading -e 'kotlin-parcelize' -e 'org.jetbrains.kotlin.plugin.parcelize' "$f"
  fi
done

echo
echo "Expected result: each module containing `@Parcelize` classes has a corresponding parcelize plugin declaration."

Repository: HeyPouya/AndroidAppUpdater

Length of output: 2158


Add the Kotlin Parcelize plugin declaration to all affected modules.

@Parcelize is used across 19 files in 3 modules (store, directdownload, appupdater), but the parcelize plugin (kotlin-parcelize or org.jetbrains.kotlin.plugin.parcelize) is not declared in any build.gradle.kts file. This will cause compilation failure. Add the plugin to each module's Gradle configuration:

plugins {
    kotlin("plugin.parcelize")
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@store/src/main/java/com/pouyaheydari/appupdater/store/domain/stores/NineApps.kt`
around lines 4 - 15, Add the Kotlin Parcelize plugin to every module that uses
`@Parcelize` (modules: store, directdownload, appupdater) so files like NineApps
(class NineApps in NineApps.kt) compile; update each module's Gradle settings to
apply the Kotlin parcelize plugin (the kotlin-parcelize /
org.jetbrains.kotlin.plugin.parcelize plugin) in the plugins block of that
module's build.gradle.kts.

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