0

I am trying to create a base class GenericAction to handle Glance widget updates. It needs to take two parameters: an Object GlanceStateDefinition and Class GlanceAppWidget() as each widget will have a different state definition and broadcast receiver to be updated. This is called in updateAppWidgetState and updateStart respectively.

I have followed the guidance provided here and got to the below code.

There are two problems I am facing:

  1. Compilation error in lambda of updateAppWidgetState() I get error - Unresolved reference: copy. I believe this is because the compiler doesn't know how to infer the generic type T.
  2. Runtime error on instantiation of glanceWidget. Error in Glance App Widget java.lang.NoSuchMethodException: com.example.myproject.WidgetSymbolClass.<init> [class android.content.Context]. I believe android can't find the constructor of my GlanceAppWidget WidgetSymbolClass() for some reason.

I would appreciate if anyone has any idea of how I can get rid of these errors and fix the below code in GenericAction().

abstract class GenericAction<T>(val myObject: GlanceStateDefinition<T>, val myClass: Class<out WidgetSymbolClass>) : ActionCallback {

    override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) {
        val index = parameters.get(ActionParameters.Key<Int>("IndexParam")) ?: 0
        val portfolioId = parameters.get(ActionParameters.Key<String>("PortfolioId")) ?: "Portfolio 1"
        val showPortfolioMenu = parameters.get(ActionParameters.Key<Boolean>("showMenu")) ?: false

        updateAppWidgetState(context, myObject, glanceId) { state ->
            state.copy(
                /*error - Unresolved reference: copy*/
                /*error - Type mismatch. Required: T Found: Unit*/
                showPortfolioMenu = showPortfolioMenu,
            )
        }

        val glanceWidget = myClass.getConstructor(Context::class.java).newInstance(context) /*error java.lang.NoSuchMethodException WidgetSymbolClass.<init>*/
        glanceWidget.updateStart( glanceId = glanceId, portfolioId = portfolioId, index = index)
    }
}
class WidgetSymbolShowMenu: GenericAction<WidgetSymbolState>(WidgetSymbolStateDefinition, WidgetSymbolClass::class.java)
class WidgetSymbolClass @JvmOverloads constructor() : WidgetAbstractClass() 
abstract class WidgetAbstractClass (): GlanceAppWidget() {

    fun updateStart(
        glanceId: GlanceId?,
        appWidgetId: Int? = null,
        index: Int = 0,
        portfolioId: String = "Portfolio 1"
    ) {
       /*start updating widget*/
    }
}
/*myObect1 - T is WidgetSymbolState*/
object StateDefinitionSymbol: GlanceStateDefinition<WidgetSymbolState> {
    override suspend fun getDataStore(
        context: Context,
        fileKey: String,
    ): DataStore<WidgetSymbolState> = context.widgetSymbolFile

    override fun getLocation(context: Context, fileKey: String): File {
        return context.dataStoreFile(widgetSymbolJson)
    }
}
/*myObect2 - T is WidgetTableState*/
object StateDefinitionTable: GlanceStateDefinition<WidgetTableState> {
    override suspend fun getDataStore(
        context: Context,
        fileKey: String,
    ): DataStore<WidgetTableState> = context.widgetTableFile

    override fun getLocation(context: Context, fileKey: String): File {
        return context.dataStoreFile(widgetTableJson)
    }
}
@Serializable
data class WidgetSymbolState(
    val index: Int = 0,
    val portfolio: String = "Portfolio 1",
    val portfolioSize: Int = 0,
    val showPortfolioMenu: Boolean = false,
)
@Serializable
data class WidgetTableState(
    val portfolioId: String = "",
    val showPortfolioMenu: Boolean = false,
)
mars8
  • 770
  • 1
  • 11
  • 25
  • 1
    Well, now that you showed what `WidgetSymbolClass` looks like, it is obvious isn't it? There is, as a matter of fact, no constructor in `WidgetSymbolClass` that takes a `Context`. Why are you trying to pass the `context` to `myClass` in the first place? As for the compiler error, I'm assuming `WidgetSymbolState` is a data class? What other types are possible for `T`? Please *show* them. – Sweeper Nov 12 '22 at 21:59
  • ah yes that has fixed the `NoSuchMethodException`. yes `WidgetSymbolState` is a data class. I have added the data classes and two examples of Type T - `WidgetSymbolState` and `WidgetTableState`. The reason I used generic `T` is because I would like to switch `myObject` to be `StateDefinitionSymbol : GlanceStateDefinition` or `StateDefinitionTable : GlanceStateDefinition` depending on what I feed `GenericAction`. The `myClass` param would also change based on which one i use. – mars8 Nov 12 '22 at 23:19

1 Answers1

2

For the compiler error, you need to assure the compiler that you can call copy on T. However, WidgetSymbolState.copy has a different signature from WidgetTableState.copy, despite both having a showPortfolioMenu parameter - they have different parameters because they are generated from different data classes. This means you cannot create a common interface between them and put copy there.

I would suggest that you add a updateShowPortfolioMenu method in both classes, and use that as the common interface.

interface ShowPortfolioMenuState<T: ShowPortfolioMenuState<T>> {
    fun updateShowPortfolioMenu(newValue: Boolean): T
}

@Serializable
data class WidgetSymbolState(
    val index: Int = 0,
    val portfolio: String = "Portfolio 1",
    val portfolioSize: Int = 0,
    val showPortfolioMenu: Boolean = false,
): ShowPortfolioMenuState<WidgetSymbolState> {
    override fun updateShowPortfolioMenu(newValue: Boolean) =
        copy(showPortfolioMenu = newValue)
}

@Serializable
data class WidgetTableState(
    val portfolioId: String = "",
    val showPortfolioMenu: Boolean = false,
): ShowPortfolioMenuState<WidgetTableState> {
    override fun updateShowPortfolioMenu(newValue: Boolean) =
        copy(showPortfolioMenu = newValue)
}

Then you can constraint T in GenericAction to ShowPortfolioMenuState:

abstract class GenericAction<T: ShowPortfolioMenuState<T>>(...)

And then call state.updateShowPortfolioMenu(showPortfolioMenu), instead of state.copy.

As for the runtime error, WidgetSymbolClass does not have a constructor that takes a Context. I'm not sure why you are passing a context to it. Either don't pass a context to it:

myClass.getConstructor().newInstance()

Or add a Context parameter, if other classes that would passed to myClass does take a Context.

Also, you don't need the @JvmOverloads. There are no optional parameters anywhere.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • I have raised another [question](https://stackoverflow.com/questions/74438401/how-to-inject-parameters-into-android-worker-that-are-only-known-at-runtime) following on from this one. Basically it's how can I pass `myObject` and `myClass` to a `Worker`. – mars8 Nov 14 '22 at 22:15