SharedFlow with replay 1 _is_ StateFlow. I'm not quite sure why you would use it over StateFlow but up to you.
Edit: I should add, that I'm assuming you're emitting a list of events. Not just a flow of events. Because if you're not emitting a list then two back to back events will result in one event potentially not being observed.
There's essentially three patterns here:
1) SingleLiveEvent (the gist you posted). This places the responsibility on the observer to check for a flag to see if the event has already been consumed. The event itself does some under the hood magic to update the event to mark it as consumed so no further observations can happen. However, it does so by doing a deep edit of the event within the state holder and it still requires that the view model update the state holder to remove consumed events. Otherwise the list of events can grow enormously large. It's not a bad solution. I just don't like that extra work. You still need to be careful about when you observe these events to make sure you're in a good lifecycle state before acting on them.
2) My previous article use of channels, received as a flow. This pattern isn't terrible in my opinion. (Obviously, I wrote an article about it.) Channels can only ever emit once and once only. There is no need to inform the view model the event is consumed nor is there a need for the observer to check a flag to see if an event has been handled. By simply receiving the event off the channel it is certain that the event is unhandled and since the event holder is a channel it can never be observed again, so the view model doesn't need to be informed to remove consumed events from the event holder. You still need to be careful about when you observe these events to make sure you're in a good lifecycle state before acting on them.
3) Google's new guidance, which embeds events into the UI state as another state property. This often means the UI state holder is a StateFlow or LiveData or some other type of holder that can repeatedly emit the same value. This pattern is in line with UDF and compose's general patterns. This pattern places the responsibility on the view to inform the view model that the event has been consumed and it needs to remove it from the list of pending events. It's very similar to SingleLiveEvent really but without the extra burden of having an "hasBeenHandled" flag that the observer needs to care about. If the observer sees an event within the UI state, it hasn't been handled. You still need to be careful about when you observe these events to make sure you're in a good lifecycle state before acting on them.
Personally I dislike the SingleLiveEvent pattern. I dislike the observer being required to check a flag to determine if an event has been handled. I find it wasteful. There's also the need to clean up the event holder to remove processed events.
The other two patterns aren't without their own issues so it's up to you to decide which works for you.