Core module

Nabla iOS Messaging core module

📘

This guide is about the data/domain layer

This page describes how to to query the patient data, allowing you to build your own UI. If you'd rather use our built-in customisable UI components, check out the Messaging UI Components page.

Watch patient conversations list

You can watch the list of conversations a user has access to. The callback closure will be called every time there's a change in those conversations and will always return all the data (you don't need to accumulate it in your own properties):

private var conversationsWatcher: AnyCancellable?
private var conversations: PaginatedList<Conversation>?

self.conversationsWatcher = NablaMessagingClient.shared.watchConversations()
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        print("Completion \(completion)")
     }, receiveValue: { [weak self] conversations in
        print("User has \(list.conversations.count) conversations")
        self?.conversations = conversations
    })

watchConversations() returns some AnyPublisher<PaginatedList<Conversation>, NablaError>. For more details about AnyPublisher<>, visit Apple's documentation.

The sink(receiveCompletion:receiveValue) method returns an AnyCancellable. You must retain it if you want the receiveValue closure to keep getting called. If you release them, they will automatically cancel() themselves and receiveValue will never be called again.

PaginatedList provides a loadMore() method that will fetch more items in the corresponding list. If loadMore is nil, there are no more items to load. This method does not return the new items, instead, the receiveValue closure used for watchConversations().sink() will be called again with the added items. Use it as your only source of truth (you don't need to accumulate conversations in your own properties).

If the first network call made by watchConversatins() fails, it will emit an error with the receiveCompletion closure. The AnyPublisher will be terminated, neither receiveCompletion nor receiveValue will be called again. You must call watchConversations() again to get a new AnyPublisher.

If the loadMore() fails, it will throw an error, but it won't terminate the AnyPublisher from watchConversations().

private var conversations: PaginatedList<Conversation>?


do {
    guard loadMore = conversations?.loadMore else {
        print("All items have already been loaded")
        return
    }
    try await loadMore()
    print("More conversations successfully loaded")
} catch {
    print("Error \(error)")
}

Create a new conversation

If you want your user to be able to start a new conversation, you can allow to create it into your app directly:

private var createConversationAction: Cancellable?

do {
    let conversation = try await NablaMessagingClient.shared.createConversation()
    print("New conversation created: \(conversation)")
} catch {
    print("Error \(error)")
}

Similar to loadMore(), createConversation() will cause the AnyPublisher of watchConversations() to emmit again with the new conversation added to the list.

Watch a conversation

To watch the messages of a conversation, the same pagination principles applies:

private var itemsWatcher: AnyCancellable?

self.itemsWatcher = NablaMessagingClient.shared.watchItems(
  ofConversationWithId: conversationId
)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { completion in
    print("Completion \(completion)")
}, receiveValue: { conversationItems in
    print("Received \(conversationItems.elements.count) messages")
})

You can also watch for a conversation details update (like a Provider typing status):

private var conversationWatcher: AnyCancellable?

self.conversationWatcher = NablaMessagingClient.shared.watchConversation(withId: conversationId)
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        print("Completion \(completion)")
    }, receiveValue: { conversation in
        print("Conversation updated: \(conversation)")
    })

Send a new message

Creating and sending your message

The user can send a message in an existing conversation:

do {
    let messageInput  = MessageInput.Text(content: "Hello world!")
    try await NablaMessagingClient.shared.sendMessage(
        messageInput,
        inConversationWithId: conversationId
    )
    print("Message successfully sent")
} catch {
    print("Error \(error)")
}

You can send 5 types of messages:

  • Text
  • Video
  • Image
  • Document
  • Audio

Handling failure

If sending the message fails, the message will still be included in watchConversationItems but its state will be ConversationItemState.failed. You can then retry sending it:

do {
    try await NablaMessagingClient.shared.retrySending(
      itemWithId: myMessage.id,
      inConversationWithId: conversationId
    )
    print("Message successfully sent")
} catch {
    print("Error \(error)")
}

Automatic data updates

Methods like watchConversations(), watchConverversation() or watchItems(ofConversationWithId:) return some AnyPublisher that will emit new values everytime the data changes.

The SDK relies on websockets to receive updates from the server. When your application is killed, or is sent to background, those websockets will be disconnected and the data will be updated when the SDK can access the network again.

It is a good practice to re-fetch the data for watchers that are currently active when your application enters the foreground. You can register a RefetchTrigger that will do this for you on every Watcher. We provide NotificationRefetchTrigger for this specific scenario, and it can be used like this:

NablaClient.shared.addRefetchTriggers(
    NotificationRefetchTrigger(name: UIApplication.willEnterForegroundNotification)
)

📘

When using NablaMessagingUI, this step is done automatically for you.