https://kotlinlang.org logo
#glance
Title
# glance
u

undermark5

10/02/2023, 11:36 PM
I've got an app that I'm working on that we're trying to use Glance to make the widgets with. Things generally work, but in some instances we are running into issues that seem like our recompositions aren't applying correctly, we only seem to get 2 seconds of recomposition time (if that), and then nothing else. It is documented that recomposition should be happening for ~45 seconds, 2 is a lot less than that. Does anyone have any ideas as to why this would be the case? We've come up with a very hacky workaround that involves using a
LaunchedEffect
that manually updates the widget via
update
but this is almost certainly definitely not how it is supposed to be done.
We seem to have found a slightly more reasonable work around involving delaying the emission of new state values from our flow.
Is this a limitation? That you can't have multiple state updates within a very short time frame?
j

James O'Claire

10/03/2023, 8:56 PM
I wonder, I recently made a little game and noticed that after around 10 or so taps the widget process would be stopped by the OS. I got around it by saving to disk each time.
u

undermark5

10/03/2023, 10:10 PM
The data we are loading is ultimately coming from roomDB, but the issue isn't the data, it's just the state we have transitioning from loading to error too quickly. We threw a
forEach { delay250) }
to the flow before collecting it and that generally seems to have solved the problem, but nothing that we can see from the documentation really gives any indication that quick successive recompositions may be ignored (though perhaps we are just missing that)
w

Willie Koomson

10/04/2023, 4:37 PM
To understand this better, is the issue that recomposition stops after 2 seconds, or that it updates too quickly? You're right that the widget should continue to recompose for at least 45 seconds after an update request is received or the widget is interacted with. We recommend saving state to disk to avoid losing it once the session is finished. We did recently make a change so that, if another recomposition happens while we are still processing the results of the last recomposition, we will cancel processing the last one and send the next one instead. This helped improve latency by avoiding processing results that will immediately be overridden by the next result.
It is also better to avoid showing intermediate loading states in the widget if possible. Ideally, you should preload any data before calling
provideContent
so that the widget goes from showing "stale but valid data -> fresh data" instead of "stale data -> loading state -> fresh data"
Re: "Is this a limitation? That you can't have multiple state updates within a very short time frame?" We do control the rate of recomposition so that it does not run more than 5 times a second in most cases, though we increase the rate to 20 times a second when the widget is being interacted with or resized to allow quicker response. Your widget should still display correctly regardless of the rate because all state changes are coalesced and applied to the composition on the next run.
If you are able to use a
LaunchedEffect
to call
GlanceAppWidget.update
, that means your composition is still running. In that case, you should be able to update state variables to trigger recomposition instead of calling
update
manually
u

undermark5

10/04/2023, 5:14 PM
Hmm, I'll see if I can get some sample code, but we were adding the widget, and it would get stuck showing our loading state even though we were successfully recomposing with the error state (as indicated by the
LaunchedEffect
triggering), removing and adding the widget (or adding another copy of it) it would correctly show the error state. Notably this was happening after a fresh install of the app (debug build).
Is it recommended to not use a compose based initial loading UI but instead provide that 100% via the XML so that the time before
provideContent
that is the UI, and then our first compose based frame is using the fresh and valid data?
> It is also better to avoid showing intermediate loading states in the widget if possible. Ideally, you should preload any data before calling
provideContent
so that the widget goes from showing "stale but valid data -> fresh data" instead of "stale data -> loading state -> fresh data" To me this implies that
provideGlance
would be called again when the next update is triggered (assuming that
provideGlance
is not still running). Is that accurate?
w

Willie Koomson

10/04/2023, 7:16 PM
Yes, provideGlance will get triggered again if it is not already running.
it would get stuck showing our loading state even though we were successfully recomposing with the error state
Is this the layout defined in
android:initialLayout
in your
<appwidget-provider />
info or a loading state composed by your widget?
u

undermark5

10/04/2023, 7:21 PM
a loading state composed by the widget.
I believe that we have to wait for a network request then save some data to room, then read out via a flow from room. The initial loading definitely could be replaced with the
android:initialLayout
though.
That being said, the behavior that we have been seeing without the delays still seems odd/unintentional (that is, it shouldn't make a difference what our states are) we weren't seeing the most recently composed state represented in the UI of the widget unless we delay the emission and thus the recomposition or manually trigger update from the
LaunchedEffect
w

Willie Koomson

10/04/2023, 7:33 PM
right, that is definitely unexpected behavior. UI should reflect the most recently composed state. Do you see any errors in logcat when this happens, under the
GlanceAppWidget
tag? Also is there a sample I can try to reproduce this?
u

undermark5

10/04/2023, 7:46 PM
I'll get back to you on that.
So, when reproducing we don't see anything in logcat under the
GlanceAppWidget
tag. I'll see if I can get a sample project spun up that reproduces as well, but so far I've had no luck.
w

Willie Koomson

10/12/2023, 7:28 PM
If it's possible for me to look at the code that's causing the issue, that would be very helpful as well
u

undermark5

10/12/2023, 7:43 PM
Here are some screenshots of the code that we are able to fairly consistently reproduce with. UI is build with data coming from room. Yes I'm aware of the slight oddity of how the flow produced by the usecase is being collected and emitted to the uiState. Adding a
forEach{ delay(250) }
just before that
collectLatest
and the issue goes away.
w

Willie Koomson

10/16/2023, 10:48 PM
The code looks fine for the most part. One thing that might be an issue here is that your
uiState
flow is a member of your GlanceAppWidget class. One instance of this class can be used to compose for multiple widgets, so they will share the
uiState
variable. You should declare it in
provideGlance
instead.
A couple questions as well: 1. what version of glance are you using? 2. when you see this behavior (widget does not reflect latest composition), does the widget show the correct UI if you switch the device orientation (e.g. landscape -> portrait or vice versa)?
u

undermark5

10/17/2023, 3:13 PM
We are seeing the issue with a instance of the widget and that code (I believe we have since tweaked the code further, but I'll definitely look at moving that into
provideGlance
) 1: we are using 1.0.0 2: Rotating the device has no impact, item is still stuck, I believe if we wait around long enough that the configured refresh time happens then the widget correctly updates (but we shouldn't need to wait for that as we should still be within the available window)
18 Views