What is the purpose of getSnapshot method from WidgetKit?

I'm trying to figure out what exactly should I do in getSnapshot method. However, it is not easy. In Apple's documentation, there is a very general description and in their demo app its implementation is also very simple.

There is one obvious use-case when we need to return TimelineEntry for widget gallery preview when context.isPreview = true. However, what should be done when context.isPreview = false?
  1. Should I return there the last entry prepared in getTimeline method?

  2. Should I verify if the configuration has changed between calls of getTimeline and getSnapshot?

  3. My widget is using data from API. Should I call it in getSnapshot or only in getTimeline?

  4. If I don't have cached entry, what should I return then? Placeholder entry?

  5. Why they need getSnapshot if they could just take the entry from Timeline?


Currently, I'm returning here either preview entry or the last cached entry during getTimeline also checking if it matches the new configuration. However, I'm not sure if this is the correct approach.

Accepted Reply

Think of the lifecycle of the Widget as follows:
  1. The user chooses to add a widget to their home screen so they press the + button, and they select your app. They haven't yet added the widget, they're just looking at it.

At this point, you should be showing a placeholder representation of your widget, so return something from the placeholder() func. For my widget, I'm just returning a fake event (an event is my object type) that gets rendered by iOS and shows as a fake event in the preview.

2. The user taps the "Add Widget" button.

Now, the widget is gonna flip around and settle on your home screen. Here, is where the getSnapshot() func comes in - it is shown whenever there's a transition.

In my widget, I check if the context.isPreview, and if so, I display the fake data again. I also check if the configuration.event == nil. If so, at this point the user hasn't chosen an event to display (from the dynamic selection intent) so I'm showing a widget that tells the user to edit the widget and select an event. You could, however, set a default event and have that returned here instead.

Finally, if it's not context.isPreview and configuration.event != nil I then get the actual event the user has chosen so the transition shows that event.

3. The widget is now on-screen. In my case it's telling the user to edit the widget and choose an event. (If you've set a default in the IntentHandler then they wouldn't see this bit asking to edit the widget.) They edit the widget and choose an event.

Now, getSnapshot() is called again to show the transition, but this time it has the actual event (configuration.event != nil) so it displays the actual event in the transition.

getTimeline() is also called so it can provide the timeline of when the widget should refresh.

So, in your case just think about when you want to show actual data. Do you want actual data showing in the placeholder? If so, remember that the placeholder should return quickly, so show *something* then update it with the actual data. If you have that data, you can cache it and use it in getSnapshot() and getTimeline().

Replies

Think of the lifecycle of the Widget as follows:
  1. The user chooses to add a widget to their home screen so they press the + button, and they select your app. They haven't yet added the widget, they're just looking at it.

At this point, you should be showing a placeholder representation of your widget, so return something from the placeholder() func. For my widget, I'm just returning a fake event (an event is my object type) that gets rendered by iOS and shows as a fake event in the preview.

2. The user taps the "Add Widget" button.

Now, the widget is gonna flip around and settle on your home screen. Here, is where the getSnapshot() func comes in - it is shown whenever there's a transition.

In my widget, I check if the context.isPreview, and if so, I display the fake data again. I also check if the configuration.event == nil. If so, at this point the user hasn't chosen an event to display (from the dynamic selection intent) so I'm showing a widget that tells the user to edit the widget and select an event. You could, however, set a default event and have that returned here instead.

Finally, if it's not context.isPreview and configuration.event != nil I then get the actual event the user has chosen so the transition shows that event.

3. The widget is now on-screen. In my case it's telling the user to edit the widget and choose an event. (If you've set a default in the IntentHandler then they wouldn't see this bit asking to edit the widget.) They edit the widget and choose an event.

Now, getSnapshot() is called again to show the transition, but this time it has the actual event (configuration.event != nil) so it displays the actual event in the transition.

getTimeline() is also called so it can provide the timeline of when the widget should refresh.

So, in your case just think about when you want to show actual data. Do you want actual data showing in the placeholder? If so, remember that the placeholder should return quickly, so show *something* then update it with the actual data. If you have that data, you can cache it and use it in getSnapshot() and getTimeline().
@darkpaw thank you very much for the detailed answer!

Just to summarise:
  • getSnapshot is called only during transition from gallery to home screen and from configuration intent to home screen. Probably could be during some other events, but it most likely shouldn't affect the widget's logic.

  • getSnapshot should return data as quickly as possible, so probably remote requests are not recommended here. However, if I already have data from previous getTimeline call, I could use it.

  • when using cached data in getSnapshot, it should be validated if configuration is the same as it was at the moment of creating cache entry (It could be changed - transition from configuration to home screen).

  • when context.isPreview = true - show sample data for widgets gallery

Could you confirm that points above are correct?

I'm still curious what's the purpose of this method. Why they couldn't just use getPlaceholder and getTimeline?
  1. Yes, getSnapshot() is called whenever there's a transition, so make sure what you return from there is correct for the time the snapshot is being called, i.e. if context.isPreview then show what you should see in the preview, etc.

  2. I don't know if getSnapshot should return data as quickly as possible. placeholder() definitely should. In my getSnapshot() I'm getting data from the defaults, which is pretty quick, so maybe I haven't seen any problems with timings.

  3. Same as 1, above.

  4. Yes.

getTimeline() is called whenever the widget is going to be displayed on the home screen, and it allows you to give it a timeline of what should show in the widget at what time, and when the widget should be refreshed.