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.
Watchers and cached data
Our messaging core SDK has a cache layer that persists the latest fetched data from the network to a disk cache, allowing to retrieve these data without network at any time.
Every watcher returns a Response
object that contains 3 things:
- A
data
property that contains the data itself - A
isDataFresh
boolean property that indicates if the emitted data is fresh or not. - A
refreshingState
property that has 3 different states:refreshing
: the data emitted comes from the cache, we are refreshing it in background.isDataFresh
should befalse
when that happens, and you can expect another emit following with the result of the background refresh.refreshed
: the data emitted comes from the network or is local only and no follow-up attempt to refresh it will be made.isDataFresh
should betrue
when that happens.failed
: the background refresh of the data has failed and no follow-up attempt to refresh will be made.isDataFresh
should befalse
when that happens.
If no cached data are available and the network fetch fails, the
AnyPublisher
will terminate with an error, so you need to implementsink(receivedCompletion:receiveValue:)
even for cached flows.
For the vast majority of apps, showing cached data is a good user experience, especially if you're showing a hint to the user when it's not fresh. If your app needs to show only fresh data, you can filter items that aren't fresh and you can relaunch the watcher to trigger a new network fetch if needed.
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):
import Combine // Needed for `AnyCancellable`
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] response in
print("User has \(response.data.elements.count) conversations")
self?.conversations = response.data
})
watchConversations()
returns some AnyPublisher<Response<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>?
Task {
do {
guard let 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 Patient to be able to start a new conversation, you can allow to create it into your app directly:
let conversation = NablaMessagingClient.shared.startConversation()
print("New conversation created: \(conversation)")
Note that this conversation is created locally on the device first and will be created server side once the Patient sends a first message in it.
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: { response in
print("Received \(response.data.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: { response in
print("Conversation updated: \(response.data)")
})
Send a new message
Creating and sending your message
The user can send a message in an existing conversation:
Task {
do {
let messageInput = MessageInput.text(content: "Hello world!")
try await NablaMessagingClient.shared.sendMessage(
messageInput,
replyingToMessageWithId: nil,
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:
Task {
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.
Updated 6 months ago