Skip to content

Commit ec95f2f

Browse files
committed
- Update room clients to use subscription with callback instead of AsyncSequence.
- Update example app to use callbacks.
1 parent b08d00e commit ec95f2f

35 files changed

+1320
-727
lines changed

Example/AblyChatExample/ContentView.swift

Lines changed: 78 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,18 @@ struct ContentView: View {
200200
do {
201201
let room = try await room()
202202

203+
printConnectionStatusChange(duration: 30) // stops printing after 30 seconds
204+
subscribeToReactions(room: room)
205+
subscribeToRoomStatus(room: room)
206+
subscribeToTypingEvents(room: room)
207+
subscribeToOccupancy(room: room)
208+
subscribeToPresence(room: room)
209+
203210
try await room.attach()
211+
try await showOccupancy(room: room)
204212
try await room.presence.enter(data: ["status": "📱 Online"])
205213

206214
try await showMessages(room: room)
207-
showReactions(room: room)
208-
showPresence(room: room)
209-
try await showOccupancy(room: room)
210-
showTypings(room: room)
211-
showRoomStatus(room: room)
212-
printConnectionStatusChange()
213215
} catch {
214216
print("Failed to initialize room: \(error)") // TODO: replace with logger (+ message to the user?)
215217
}
@@ -234,136 +236,110 @@ struct ContentView: View {
234236
}
235237

236238
func showMessages(room: Room) async throws {
237-
let messagesSubscription = try await room.messages.subscribe()
238-
let previousMessages = try await messagesSubscription.getPreviousMessages(params: .init())
239-
240-
for message in previousMessages.items {
239+
let subscription = try await room.messages.subscribe { message in
241240
switch message.action {
242-
case .create, .update, .delete:
241+
case .create:
243242
withAnimation {
244-
listItems.append(.message(.init(message: message, isSender: message.clientID == chatClient.realtime.clientId)))
245-
}
246-
}
247-
}
248-
249-
// Continue listening for messages on a background task so this function can return
250-
Task {
251-
for await message in messagesSubscription {
252-
switch message.action {
253-
case .create:
254-
withAnimation {
255-
listItems.insert(
256-
.message(
257-
.init(
258-
message: message,
259-
isSender: message.clientID == chatClient.realtime.clientId
260-
)
261-
),
262-
at: 0
263-
)
264-
}
265-
case .update, .delete:
266-
if let index = listItems.firstIndex(where: { $0.id == message.id }) {
267-
listItems[index] = .message(
243+
listItems.insert(
244+
.message(
268245
.init(
269246
message: message,
270247
isSender: message.clientID == chatClient.realtime.clientId
271248
)
249+
),
250+
at: 0
251+
)
252+
}
253+
case .update, .delete:
254+
if let index = listItems.firstIndex(where: { $0.id == message.id }) {
255+
listItems[index] = .message(
256+
.init(
257+
message: message,
258+
isSender: message.clientID == chatClient.realtime.clientId
272259
)
273-
}
260+
)
274261
}
275262
}
276263
}
277-
}
278-
279-
func showReactions(room: Room) {
280-
let reactionSubscription = room.reactions.subscribe()
264+
let previousMessages = try await subscription.historyBeforeSubscribe()
281265

282-
// Continue listening for reactions on a background task so this function can return
283-
Task {
284-
for await reaction in reactionSubscription {
266+
for message in previousMessages.items {
267+
switch message.action {
268+
case .create, .update, .delete:
285269
withAnimation {
286-
showReaction(reaction.displayedText)
270+
listItems.append(.message(.init(message: message, isSender: message.clientID == chatClient.realtime.clientId)))
287271
}
288272
}
289273
}
290274
}
291275

292-
func showPresence(room: Room) {
293-
// Continue listening for new presence events on a background task so this function can return
294-
Task {
295-
for await event in room.presence.subscribe(events: [.enter, .leave, .update]) {
296-
withAnimation {
297-
listItems.insert(
298-
.presence(
299-
.init(
300-
presence: event
301-
)
302-
),
303-
at: 0
304-
)
305-
}
276+
func subscribeToReactions(room: Room) {
277+
room.reactions.subscribe { reaction in
278+
withAnimation {
279+
showReaction(reaction.displayedText)
306280
}
307281
}
308282
}
309283

310-
func showTypings(room: Room) {
311-
let typingSubscription = room.typing.subscribe()
312-
// Continue listening for typing events on a background task so this function can return
313-
Task {
314-
for await typing in typingSubscription {
315-
withAnimation {
316-
// Set the typing info to the list of users currently typing
317-
typingInfo = typing.currentlyTyping.isEmpty ?
318-
"" :
319-
"Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
320-
}
284+
func subscribeToPresence(room: Room) {
285+
room.presence.subscribe(events: [.enter, .leave, .update]) { event in
286+
withAnimation {
287+
listItems.insert(
288+
.presence(
289+
.init(
290+
presence: event
291+
)
292+
),
293+
at: 0
294+
)
321295
}
322296
}
323297
}
324298

325-
func showOccupancy(room: Room) async throws {
326-
// Continue listening for occupancy events on a background task so this function can return
327-
let currentOccupancy = try await room.occupancy.get()
328-
withAnimation {
329-
occupancyInfo = "Connections: \(currentOccupancy.presenceMembers) (\(currentOccupancy.connections))"
330-
}
331-
332-
Task {
333-
for await event in room.occupancy.subscribe() {
334-
withAnimation {
335-
occupancyInfo = "Connections: \(event.presenceMembers) (\(event.connections))"
336-
}
299+
func subscribeToTypingEvents(room: Room) {
300+
room.typing.subscribe { typing in
301+
withAnimation {
302+
// Set the typing info to the list of users currently typing
303+
let reset = typing.currentlyTyping.isEmpty || typing.currentlyTyping.count == 1 && typing.change.type == .stopped
304+
typingInfo = reset ? "" : "Typing: \(typing.currentlyTyping.joined(separator: ", "))..."
337305
}
338306
}
339307
}
340308

341-
func printConnectionStatusChange() {
342-
let connectionSubsciption = chatClient.connection.onStatusChange()
309+
func showOccupancy(room: Room) async throws {
310+
let occupancy = try await room.occupancy.get()
311+
occupancyInfo = "Connections: \(occupancy.presenceMembers) (\(occupancy.connections))"
312+
}
343313

344-
// Continue listening for connection status change on a background task so this function can return
345-
Task {
346-
for await status in connectionSubsciption {
347-
print("Connection status changed to: \(status.current)")
314+
func subscribeToOccupancy(room: Room) {
315+
room.occupancy.subscribe { occupancy in
316+
withAnimation {
317+
occupancyInfo = "Connections: \(occupancy.presenceMembers) (\(occupancy.connections))"
348318
}
349319
}
350320
}
351321

352-
func showRoomStatus(room: Room) {
353-
// Continue listening for status change events on a background task so this function can return
354-
Task {
355-
for await status in room.onStatusChange() {
356-
withAnimation {
357-
if status.current.isAttaching {
358-
statusInfo = "\(status.current)...".capitalized
359-
} else {
360-
statusInfo = "\(status.current)".capitalized
361-
if status.current.isAttached {
362-
Task {
363-
try? await Task.sleep(nanoseconds: 1 * 1_000_000_000)
364-
withAnimation {
365-
statusInfo = ""
366-
}
322+
func printConnectionStatusChange(duration: TimeInterval) {
323+
let subscription = chatClient.connection.onStatusChange { status in
324+
print("Connection status changed to: `\(status.current)` from `\(status.previous)`")
325+
}
326+
after(duration) {
327+
subscription.off()
328+
print("Unsubscribed from connection status changes.")
329+
}
330+
}
331+
332+
func subscribeToRoomStatus(room: Room) {
333+
room.onStatusChange { status in
334+
withAnimation {
335+
if status.current.isAttaching {
336+
statusInfo = "\(status.current)...".capitalized
337+
} else {
338+
statusInfo = "\(status.current)".capitalized
339+
if status.current.isAttached {
340+
after(1) {
341+
withAnimation {
342+
statusInfo = ""
367343
}
368344
}
369345
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Ably
2+
import AblyChat
3+
4+
/// Executes closure on the `MainActor` after a delay (in seconds).
5+
func after(_ delay: TimeInterval, closure: @MainActor @escaping () -> Void) {
6+
Task {
7+
try? await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
8+
await closure()
9+
}
10+
}
11+
12+
/// Periodically executes closure on the `MainActor`with interval (in seconds).
13+
func periodic(with interval: TimeInterval, closure: @MainActor @escaping () -> Bool) {
14+
Task {
15+
while true {
16+
try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))
17+
if await !closure() {
18+
break
19+
}
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)