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:
updateAppWidgetState()
I get error - Unresolved reference: copy
. I believe this is because the compiler doesn't know how to infer the generic type T
.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,
)
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.