refresh composables without having the widget disappear for 1/3 second

26 Views Asked by At

My widget manages habits, tracking the time spent on a given habit each day. Each day is associated with a square whose color changes according to the time spent on the habit for this day. Roughly it is like in GitHub for tracking commits.

2 Callbacks are called to update the widget:

  • to increase daytime, it is called when the user clicks on one of 3 buttons (to add 5', 15', or 60').
  • to change the current day. It can be called in 2 ways: either by clicking on a button (for debugging), or by a worker which triggers at 0:00 each day.

While the daytime increase is smooth, the current day change, when run, hides the widget for 1/3 second, which is annoying and totally unexpected.

The buttons, for example, are not expected to refresh because their signatures do not contain the current date:

@Composable
fun TimeButton(
    modifier: Modifier = Modifier,
    colors: List<ColorElement>? = null,
    treshholds: List<Int>? = null,
    text: String,
    time: Int,
    onClick: Action
)

onClick manages the side effects (call of the callback).

Even the background disappears, so I think the callback of day changing triggers some expensive computations. Can you validate this idea?

Here is my callback code:

object ChangeTodayCallback : ActionCallback {
    override suspend fun onAction(
        context: Context,
        glanceId: GlanceId,
        parameters: ActionParameters
    ) {
        val newDate: String =
            parameters[ActionParameters.Key("newDateKey")] ?: LocalDate.MIN.toString()


        if (!HabitWidget.state.jours.doesContainDay(HabitWidget.state.today))
            context.dataStore.updateData {
                it.copy(
                    today = LocalDate.parse(newDate),
                    jours = HabitWidget.state.jours.setWeeksWithDay(HabitWidget.state.today)
                )
            }
        else
            context.dataStore.updateData {
                it.copy(today = LocalDate.parse(newDate))
            }

        HabitWidget().update(context, glanceId)
    }
}

the code either simply updates the "today" property in the state, or, if the new date is not in the state, it also adds data to display the new week (in case of the shift from Sunday to Monday). Indeed, day's squares are grouped in line of one week.

Here is the method setWeekWithDay, is it time-consuming?

fun setWeeksWithDay(
    jour: LocalDate,
    maxWeeks: Int = 4
): Jours {
    if (days.any { it.day == jour })
        throw IllegalArgumentException("addWeekWithDay called, but date ${jour} already in Jours")

    val daysToRemove = jour.dayOfWeek.ordinal
    val monday = jour.minusDays(daysToRemove.toLong())

    var resultDays = (days +
            Jour(monday, 0) +
            Jour(monday.plusDays(1), 0) +
            Jour(monday.plusDays(2), 0) +
            Jour(monday.plusDays(3), 0) +
            Jour(monday.plusDays(4), 0) +
            Jour(monday.plusDays(5), 0) +
            Jour(monday.plusDays(6), 0)).toMutableList()

    val toRemoveCount = resultDays.size - maxWeeks * 7
    if (toRemoveCount > 0){
        (1..toRemoveCount).forEach{
            resultDays.removeFirst()
        }
    }

    if (resultDays.size % 7 != 0)
        throw IllegalStateException("nombre d'éléments dans Jours : ${days.size}, c'est anormal")

    return Jours(days = resultDays.toImmutableList())
}

Am I demanding too much of a simple widget, or did I make a/some bad practices ?

thank you.

1

There are 1 best solutions below

0
lolveley On

I got rid of the blinking of my widget by moving the calculus of the new weeks' data to the composable function, i.e. the caller, encoding the result in String with Json, and decoding in my callback. It seems not to support too heavy calculus.