refactor(amethyst): drop PlayAmethyst — appfunctions doesn't need it

The previous wiring forced Amethyst to be `open`, added a 30-line
PlayAmethyst subclass that only implemented AppFunctionConfiguration
.Provider, and used tools:replace="android:name" in the play manifest
to swap classes. The justification was that the appfunctions runtime
discovers @AppFunction host classes via Application.appFunctionConfiguration.

Reading the KSP-generated dispatcher
($AmethystAppFunctions_AppFunctionInvoker.kt) shows that's only half
true. The invoker passes a default-construction fallback lambda when
instantiating the host class, and ConfigurableAppFunctionFactory takes
that fallback as a constructor argument. Provider is only consulted to
*override* construction — required for classes with non-default
constructors, optional otherwise.

AmethystAppFunctions has a no-arg constructor, so:
  * PlayAmethyst is deleted entirely
  * Amethyst goes back to `class Amethyst : Application()` (no `open`)
  * Play manifest reverts to plain `android:name=".Amethyst"`, no
    tools:replace gymnastics

Verified by assemblePlayDebug (APK builds clean) and the merged play
manifest still pinning the appfunctions service. If we ever add a
host class with constructor parameters (an Account-injected one, say),
we'll need to add Provider back — kdoc on AmethystAppFunctions
documents that.
This commit is contained in:
Claude
2026-05-24 23:10:37 +00:00
parent 29236d7801
commit b150556f1e
4 changed files with 10 additions and 61 deletions
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.service.nests.AppForegroundRecycleHook
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.LogLevel
open class Amethyst : Application() {
class Amethyst : Application() {
init {
Log.minLevel = if (BuildConfig.DEBUG) LogLevel.DEBUG else LogLevel.ERROR
Log.d("AmethystApp") { "Creating App $this" }
+1 -6
View File
@@ -2,13 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Play flavor overrides android:name with PlayAmethyst, which is
the same Amethyst Application class plus the
AppFunctionConfiguration.Provider surface required by
androidx.appfunctions. F-Droid keeps the plain Amethyst. -->
<application
android:name=".PlayAmethyst"
tools:replace="android:name">
android:name=".Amethyst">
<service
android:name=".service.notifications.PushNotificationReceiverService"
@@ -1,52 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst
import androidx.appfunctions.service.AppFunctionConfiguration
import com.vitorpamplona.amethyst.appfunctions.AmethystAppFunctions
/**
* Play-flavor Application subclass that adds the
* [AppFunctionConfiguration.Provider] surface required by
* androidx.appfunctions.
*
* Lives in the `play` source set only because [AppFunctionConfiguration]
* (and the entire appfunctions library) is a play-channel `playImplementation`
* dependency — the F-Droid build does not ship Google AI libraries and
* continues to use the unmodified [Amethyst] Application class.
*
* Registered via `tools:replace="android:name"` in
* `amethyst/src/play/AndroidManifest.xml`.
*/
class PlayAmethyst :
Amethyst(),
AppFunctionConfiguration.Provider {
override val appFunctionConfiguration: AppFunctionConfiguration
// Lazy single instance — AmethystAppFunctions is stateless (it
// reaches into Amethyst.instance on every call), but the runtime
// is free to invoke functions concurrently, so we hand back the
// same object for every dispatch rather than rebuilding.
get() =
AppFunctionConfiguration
.Builder()
.addEnclosingClassFactory(AmethystAppFunctions::class.java) { AmethystAppFunctions() }
.build()
}
@@ -46,8 +46,14 @@ import kotlinx.coroutines.withTimeoutOrNull
* ships without any Google AI dependencies.
*
* Plain class, no inheritance — the KSP compiler discovers `@AppFunction`
* methods and generates the dispatcher glue. Construction is wired in
* [PlayAmethyst.appFunctionConfiguration].
* methods and generates the dispatcher glue (see
* `amethyst/build/generated/ksp/playDebug/.../$AmethystAppFunctions_AppFunctionInvoker.kt`).
* The generated invoker constructs this class via its default no-arg
* constructor, so no `AppFunctionConfiguration.Provider` is required on
* the Application. If we ever add an @AppFunction host class with
* constructor parameters, we'll need to register a factory via Provider —
* the docs nudge that direction, but the runtime does not require it for
* default-constructed classes.
*
* Only read-only verbs are exposed so far. Write verbs (post, follow, zap)
* are intentionally deferred until we resolve the signer-prompt flow for