Home > Software engineering >  How to implement base class that takes object and class as parameter for Glance Widgets?
How to implement base class that takes object and class as parameter for Glance Widgets?

Time:11-14

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,
)

CodePudding user response:

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.

  • Related