In‐app chat - infobip/mobile-messaging-sdk-ios GitHub Wiki
Find more info about Live chat product on Infobip docs.
- Prerequisites
- Example application
- Display In-app chat view
- Note on SwiftUI
- Chat setup error screen
- Customer's Chat History
- Authenticated chat
- Customize In-app chat view
- Sending attachments
- Unread chat push messages counter
- Changing localization
- Sending Contextual Data
- Multiple chat threads
- Chat and push notifications
- Chat badge count on app icon
- Library events
- Livechat Widget API
- Troubleshooting
InAppChat is the mobile component for connecting and interacting with Infobip's LiveChat product. Built on top of Mobile Messaging SDK, InAppChat requires from you a careful setup, both in your mobile app and in Infobip's portal. The following steps must be prepared in order to ensure the chat communication:
Step 1:
You must include and setup the Mobile Messaging SDK in your application. If you haven't already, please follow its quick start guide carefully (Important: note how the first point in this guide is about defining a mobile application in Infobip's Portal page: you will need this application instance in the next step). Only by being able to receive a pushRegistrationId in your device at the end of the quick start guide (it is a good idea to enable and check the console debug logs for its response), you'll be able to successfully connect to InAppChat.
Note: When creating a mobile app instance in portal (and generating your p12 file), make sure you target for your desired enviroment: sandbox certificates will work while debugging in Xcode, but Apple Store or Testflight requires a production one.
Step 2:
You need to create a LiveChat Widget in Infobip's portal page. Once your widget is created with the parameters you want, save it. Once saved, you must link it to your application created in the previous step. For doing so, in your widget's tabs, navigate to Audience and Availability → Mobile Applications, and select your mobile application profile. Then save again, and your widget will be ready to be used.
Step 3:
If you added Mobile Messaging as a dependency using Swift Package Manager, you will need to include InAppChat as library embedded in your app's target, and then import it with:
import MobileMessaging
import InAppChat
If you added Mobile Messaging using cocoapods, you can add InAppChat as dependency by including the following line in your Podfile:
pod 'MobileMessaging/InAppChat'
and simply importing:
import MobileMessaging
In order for your app to retrieve the chat widget parameters, you need to include the command withInAppChat() before you start MobileMessaging, as this example:
MobileMessaging
.withApplicationCode(<# your application code #>, notificationType: <# your notifications types preferences #>)?
.withInAppChat() // this line prepares In-app chat service to start
.start()Mobile Messaging In-app chat SDK provides a built-in chat view which you can quickly embed into your own application. Key component to use is the UIKit class MMChatViewController. You need to create an instance of this class, and present it based on your needs.
The SDK provides MMChatView, a ready-to-use SwiftUI view that wraps the underlying MMChatViewController. It automatically handles UIKit-specific adjustments (navigation bar appearance, keyboard resizing) so you don't need to configure them manually. See the Note on SwiftUI section for details. Important: consider using only one MMChatView as active/presented at a time to avoid potential delegation issues.
Simplest usage:
NavigationStack {
NavigationLink("Chat") {
MMChatView()
}
}With back button handling (needed for widgets supporting multi-threading):
struct ChatScreen: View {
@Environment(\.dismiss) private var dismiss
@State private var chatCoordinator: MMChatView.Coordinator?
var body: some View {
MMChatView()
.onMakeChatCoordinator { chatCoordinator = $0 }
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
if chatCoordinator?.handleBackAction() ?? true {
dismiss()
}
} label: {
Image(systemName: "chevron.backward")
}
}
}
}
}MMChatView supports chainable modifiers for common configurations:
| Modifier | Description |
|---|---|
.onMakeChatCoordinator { } |
Receive the coordinator for back button handling and VC access |
.onChatStateChange { state in } |
Observe chat view state changes |
.useExternalChatInput() |
Hide the built-in compose bar and use your own input |
.chatComposer(composer) |
Provide a custom MMChatComposer implementation |
.jwtProvider { return jwt } |
Provide a JWT for authenticated chat |
.onUnreadMessagesCountChange { count in } |
Receive unread message count updates |
.onChatException { exception in return .noDisplay } |
Handle chat exceptions |
.onChatEnabled { enabled in } |
Observe chat availability status |
See the ChatSwiftUIDemo and ChatExample projects for full examples.
Given that MMChatViewController is already a view controller, presenting the chat in UIKit is the easier and recommended way. You can generate a chat view controller with its own navigation (and a dismiss(x) button), with the following code.
Note: rootVC below is the existing root view controller of your main window:
let chatVC = MMChatViewController.makeRootNavigationViewController()
rootVC.present(chatVC, animated: true)or, if you have a navigation controller already in place, you can push the chat view controller to it, and navigation bar will have back button. This is the recommended way for widget supporting multi-threading (a list of conversations):
let vc = MMChatViewController.makeChildNavigationViewController()
navigationController?.pushViewController(vc, animated: true)You can also present the chat modally, without top bar, using:
let chatVC = MMChatViewController.makeModalViewController()
rootVC.present(chatVC, animated: true)In order for the chat to connect and load, the SDK should internally retrieve correct values for it. When this happens, you will be informed by receiving "true" from MMInAppChatDelegate's method inAppChatIsEnabled(). Trying to present the chat while not ready of after receiving errors (the method mentioned has not returned true) will result in an error screen.
If you use MMChatView (recommended), the UIKit-specific adjustments described below are handled automatically — you can skip this section.
If you are building your own UIViewControllerRepresentable wrapper around MMChatViewController, there are a couple of UIKit-based modifications you may want to opt out of before presenting the chat:
- Navigation bar appearance override — the SDK applies the widget's main colour to the navigation bar. To prevent this from affecting your SwiftUI navigation:
MMChatSettings.sharedInstance.shouldSetNavBarAppearance = false- Keyboard frame handling — UIKit keyboard resizing logic can conflict with SwiftUI's own keyboard avoidance:
MMChatSettings.sharedInstance.shouldHandleKeyboardAppearance = false-
External chat input — if you want to define your chat input in SwiftUI and only show the message list from the SDK. With
MMChatView, use the.useExternalChatInput()modifier instead:
// Using MMChatView (recommended)
MMChatView()
.useExternalChatInput()
// Using your own wrapper
MMChatSettings.sharedInstance.shouldUseExternalChatInput = trueFor a deeper control on the chatInput's and chatMessages's view frames, you can find them in MMChatViewController's messagesViewFrame and chatInputViewFrame. The ChatExample project provides a full example of external input in SwiftUI.
There are cases where chat cannot connect and/or load, for reasons such as misconfiguration in Infobip's portal side (improper environment, expired certificate, person removed, etc.), a system error, throttling or a mistake in the integration. When this happen, MMInAppChatDelegate's method inAppChatIsEnabled() will not return true. If you display the chat before this happens, a specific error screen will be displayed instead of the chat as shown below.
This screen is entirely customisable, so you can hide its content or alter it. For that, search "chatSetupError" in the customisation keys. The easiest way to make the error screen appear is by setting an incorrect mmApplicationCode value, and then trying to show the chat.
When errors happen, the SDK sends exceptions with valuable error codes in MMInAppChatDelegate's method didReceiveException, for further troubleshooting.
When your app is installed, it is assigned a set of ids for receiving push notifications and connecting to LiveChat. And also a "person" entity is created on Infobip side, automatically, and mostly empty, for that installation.
While having an anonymous customer/person fits some use cases, most likely you have an authenticated customer you can/want to identify (for example by his/her email, phone number, or some other unique id). Fortunately, you can use the Personalize feature of Mobile messaging to not only to link your customer to your app installation, but also for recovering the chat history of that customer.
Once your app has received the push registration, personalize your customer and, next time the chat is shown, previously sent messages will be presented, recovering the open conversation.
There is also option to depersonalize, that reverts the customer to its original, anonymous state, emptying the chat history. This is a common/suggested practice in log-out flows.
InAppChat offer three levels of customisation, all of them optional, each level allowing more freedom (thus being more complex as result).
The first level is defined as "General Chat Settings", and consist of values that are shared with the chat web widget, but also can be modified in runtime through code. The way to access this general chat settings, and customising the basics of the chat appearance, is through the instance: MMChatSettings.settings, and consists of the following variables:
-
title(String) - title for navigation bar when using NavigationViewController -
sendButtonTintColor(UIColor) - tint color for Send button -
navBarItemsTintColor(UIColor) - tint color of navigation bar items when using NavigationViewController -
navBarColor(UIColor) - color of navigation bar when using NavigationViewController -
navBarTitleColor(UIColor) - color of title of navigation bar when using NavigationViewController
The second level is defined as "Advanced Chat Settings". It allows you to modify anything from our UI classes, with the implication of extra effort in terms of testing and design in your end. You can access the long list of exposed variables through the instance MMChatSettings.settings.advancedSettings, which includes all fonts, icons, frames and colours our chat compose bar uses. In the ChatExample project, included within the SDK, you can see its capabilities (search for setCustomSettings)
The third level is defined as "Custom Chat Replacement". It allows you to completely exclude the compose bar we offer for interacting with the chat, and use your own input instead. There are two possible ways to achieve this: simply create the MMChatViewController() and handle the input yourself, or follow the list of requirements below:
- Create your custom view implementing our MMChatComposer protocol.
- Use the MMComposeBarDelegate methods to interact with the chat.
- Inject your view in our chat view controller before it is loaded (for example, with the provided initialiser
MMChatViewController.makeCustomViewController(with: yourCustomInputView).
In the ChatExample project, included within the SDK, you can see its capabilities (search for showReplacedChatInNavigation)
All available customisation keys and parameters are in the classes MMChatSettings and MMAdvancedChatSettings.
Note: If you want a full control on frames (i.e., due to keyboard events) and navigation bar styles, check the Note on SwiftUI section above.
In a similar manner as you can customize in-app chat input view, navigation, etc., you can customise the appearance of the messages themselves by using "Themes". These themes are defined on the widget setup (so it applies to web, not only mobile), and the use is rather simple:
- Define the name and values of your theme(s) in a css, under widget theme's Advance Customisation, in web portal.
- Once you know the names of your themes, you can just call the
setWidgetTheme("your_theme_name")method in runtime, and the customisation will automatically be applied.
UIKit:
let vc = MMChatViewController.makeModalViewController()
// This will allow you to change the widget theme in real time
do {
try await vc.setWidgetTheme("your_theme_name")
print(">>>>Theme changed with: Success")
} catch {
print(">>>>Theme changed with: \(error.localizedDescription)")
}
// If you want to define the widget theme early, before having any view controller available, this will set the theme upon initialising the chat:
// MMChatSettings.settings.widgetTheme = "your_theme_name"SwiftUI (via the coordinator):
// After obtaining chatCoordinator via .onMakeChatCoordinator { chatCoordinator = $0 }
try await chatCoordinator?.chatVC?.setWidgetTheme("your_theme_name")expand to see completion block version
let vc = MMChatViewController.makeModalViewController()
vc.setWidgetTheme("your_theme_name") { error in
print(">>>>Theme changed with: " + (error?.localizedDescription ?? "Success"))
}The correct way to deal with dark and light mode is to modify the settings and the themes in run-time, as explained in previous section. This means, you are responsible for checking the system dark or auto modes, and apply the desired colours in your implementation. The old way of dealing with dark settings and auto-reversing of colours is now deprecated, and its use is not recommended. This affects the following properties, that will be removed in a future release:
MMChatSettings.darkSettingsMMChatSettings.colorTheme
By default, when something goes wrong, In App Chat presents an alert banner with the received localised error description (if it exists). Examples could be, a no Internet connection scenario, or when trying to set a theme name that is undeclared for your widget. You don't need to do anything for this to work.
But you can control which messages are presented in this alert, and even avoid all the alerts altogether, if you wish a refined UI control or provide an error UI of your own. In order to do so, you need to declare, in your MMInAppChatDelegate, the method didReceiveException. If undeclared, the default banner will be presented. If declared, you will receice any exception before it is presented in UI, and you can decide if it is presented (returning MMChatExceptionDisplayMode.displayDefaultAlert, or no banner at all with MMChatExceptionDisplayMode.noDisplay, in which case you can present your own UI).
UIKit (MMInAppChatDelegate):
func didReceiveException(_ exception: MMChatException) -> MMChatExceptionDisplayMode {
print(exception.message ?? "Exception code \(exception.code)")
// Here you can present an error UI of your own
return .noDisplay // you can alternatively allow displaying the default banner with .displayDefaultAlert
}SwiftUI (MMChatView modifier):
MMChatView()
.onChatException { exception in
print(exception.message ?? "Exception code \(exception.code)")
return .noDisplay
}Note: For the full list of error codes and descriptions, please visit this page.
Mobile Messaging SDK supports MMNotificationInAppChatAvailabilityUpdated event - Library-events, which is posted after the in-app chat availability status received from backend server. The userInfo dictionary contains the following key: MMNotificationKeyInAppChatEnabled - contains boolean value.
Additionally, and only in case you need to process the message content separately (for example, for doing text-to-speech), you can access the raw message data using the following callback:
public var onRawMessageReceived: ((Any) -> Void)?Usage example (the callback must be initialized before InAppChat is loaded):
MobileMessaging.inAppChat?.onRawMessageReceived = { rawMessage in
// You can use raw message for further processing on your side
}The In-app chat SDK provides MMInAppChatWidgetAPI interface with comprehensive set of methods to interact with the Livechat widget. This API allows developers to perform various actions such as managing the widget's state, sending messages (text, attachments, or custom data with different levels of visibility in Inbox (formerly Conversations)), drafts or contextual data. It enables seamless interaction with the Livechat Widget without the need to display the InAppChat in UI, making it ideal for applications that require only background operations.
To get Livechat Widget API follow:
var api = MobileMessaging.inAppChat?.apiSwiftUI: For building fully custom chat UI in SwiftUI, the SDK provides MMChatWidgetAPIObservable — an ObservableObject wrapper that bridges widget API delegate callbacks into @Published properties:
struct CustomChatUI: View {
@State private var chat: MMChatWidgetAPIObservable?
var body: some View {
Group {
if let chat {
CustomChatContent(chat: chat)
} else {
Text("Chat unavailable")
}
}
.onAppear {
if chat == nil { chat = MMChatWidgetAPIObservable() }
}
}
}
private struct CustomChatContent: View {
@ObservedObject var chat: MMChatWidgetAPIObservable
var body: some View {
Text("State: \(chat.viewState)")
Button("Send") {
Task { try await chat.api.send("Hello".livechatBasicPayload) }
}
}
}Because MMChatWidgetAPIObservable.init?() is failable (returns nil when MobileMessaging was not started with .withInAppChat()), the instance is created via @State + .onAppear and observed through @ObservedObject in a child view. The observable exposes viewState, lastError, and lastRawMessage as @Published properties, and provides direct access to the widget API via the api property for calling async methods.
Note: Payload data models are explained in Sending message payloads section.
/// Pauses the chat by closing its socket. As a result, the connection is considered terminated,
/// and remote push notifications will be delivered to the device.
/// This is useful when the app moves to the background or becomes inactive.
func stopConnection()
/// Resumes the chat by reopening its socket. As a result, the connection is reestablished,
/// and remote push notifications will no longer be delivered to the device.
/// - This is useful when the app moves to the foreground or becomes active.
func restartConnection()
/// Reset the widget, stop connection and load blank page
func reset()
/// Manually preloads the widget. Note: widget state is checked by all other API methods, so this method is not necessary in conjunction with them. You may only need loadWidget() after using reset() above, and in case you just want to listen to incoming widget events.
func loadWidget()/// Sends message payload to the chat.
/// - Parameter payload: message payload to be sent. Max texts length allowed is 4096 characters. Max attachment size is defined on web account level.
/// - Returns: Void
/// - Throws: Error if the operation fails
func send(_ payload: MMLivechatPayload) async throws
/// Create thread with a message payload
/// - Parameter payload: message payload to be sent to the newly created thread. Max texts length allowed is 4096 characters. Max attachment size is defined on web account level.
/// - Returns: MMLiveChatThread - the newly created thread
/// - Throws: Error if the operation fails
func createThread(_ payload: MMLivechatPayload) async throws -> MMLiveChatThread
/// Get all threads
/// - Returns: Array of MMLiveChatThread
/// - Throws: Error if the operation fails
func getThreads() async throws -> [MMLiveChatThread]
/// Open specific thread
/// - Parameter id: Thread ID to open
/// - Returns: MMLiveChatThread - the opened thread
/// - Throws: Error if the operation fails
func openThread(with id: String) async throws -> MMLiveChatThread
/// Get currently active thread
/// - Returns: MMLiveChatThread? - the active thread or nil
/// - Throws: Error if the operation fails
func getActiveThread() async throws -> MMLiveChatThread?/// Sets the language of the LiveChat widget.
/// - Parameter language: The enum with supported languages.
/// - Throws: Error if the operation fails
func setLanguage(_ language: MMLanguage) async throws
/// Set the theme of the Livechat Widget.
/// - Parameter themeName: unique theme name defined in portal
/// - Throws: Error if the operation fails
func setWidgetTheme(_ themeName: String) async throws
/// Set contextual data of the Livechat Widget.
/// - Parameter metadata: The mandatory data, sent as string, in the format of Javascript objects and values (for guidance, it must be accepted by JSON.stringify())
/// - Parameter multiThreadStrategy:
/// - `ACTIVE`: Sends metadata to the current active conversation for the widget.
/// - `ALL`: Sends metadata to all non-closed conversations for the widget.
/// - `ALL_PLUS_NEW`: Sends metadata to all non-closed conversations for the widget and to any newly created conversations within the current session.
/// - Throws: Error if the operation fails
func sendContextualData(_ metadata: String, multiThreadStrategy: MMChatMultiThreadStrategy) async throws
/// Navigates to the thread list if the widget supports multiple threads.
/// Otherwise, this function has no effect.
/// - Throws: Error if the operation fails
func showThreadsList() async throws
/// Opens a new thread
/// - Throws: Error if the operation fails
func openNewThread() async throwsexpand to see completion block versions
func setLanguage(_ language: MMLanguage, completion: @escaping (Error?) -> Void)
func send(_ payload: MMLivechatPayload, completion: @escaping ((Error)?) -> Void)
func createThread(_ payload: MMLivechatPayload, completion: @escaping (MMLiveChatThread?, (Error)?) -> Void)
func sendContextualData(_ metadata: String, multiThreadStrategy: MMChatMultiThreadStrategy, completion: @escaping (Error?) -> Void)
func setWidgetTheme(_ themeName: String, completion: @escaping (Error?) -> Void)
func getThreads(completion: @escaping (Result<[MMLiveChatThread], Error>) -> Void)
func openThread(with id: String, completion: @escaping (Result<MMLiveChatThread, Error>) -> Void)
func getActiveThread(completion: @escaping (Result<MMLiveChatThread?, Error>) -> Void)
func showThreadsList(completion: @escaping (Error?) -> Void)
func openNewThread(completion: @escaping (Error?) -> Void)To receive widget API events, assign a delegate as shown below:
// Example
MobileMessaging.inAppChat?.api.delegate = selfThe delegate provides the following callback methods:
/// Receive errors happened within `MMInAppChatWidgetAPI`
func didReceiveError(exception: MMChatException)
/// Send changes in `MMNotificationKeyInAppChatViewChanged` of ChatWebView
func didChangeState(to state: MMChatWebViewState)
/// In case you need to process the message content separately
func onRawMessageReceived(_ message: Any)All contents sent with the Livechat SDK API share the same protocol, MMLivechatPayload, as seen in the definition of the method below, which will complete with nil in case of success, or return error in case of a failure. (If an error is returned, it will include a description of the original payload that failed to be sent).
func send(_ payload: MMLivechatPayload, completion: @escaping ((Error)?) -> Void)There are three kind of payloads you can send:
- Drafts, which are seen only by the agents in Inbox (formerly Conversations).
- Basic, for sending either a text or an attachment.
- Custom Data, which allows you to send a javascript object, and optionally messages only visible for the agents, or visible by both, the agent and your Livechat user.
Creating a payload is rather simple, as seen in the examples below. Keep in mind restrictions on content applies (for instance, max lenght for any text is 4096 characters, and your account setup will also define a max attachment size).
let draftPayload = "my draft text".livechatDraftPayload
let basicTextPayload = "my basic text".livechatBasicPayload
let basicAttachmentPayload = MMLivechatBasicPayload(fileName: "image.png", data: pictureData)
let customDataPayload = MMLivechatCustomPayload(
customData: "{ \"key\": \"value\" }",
agentMessage: "Text to be seen only by agents",
userMessage: "Text to be seen by the livechat user and agents)")If you know the thread Id you want to send a payload to, you can also include its Id when initialising the payload. Otherwise, if left nil, the payload will be sent, by default, to the current active thread in the widget.
let draftPayloadToThreadId = MMLivechatDraftPayload(text: text, threadId: "123-abc-...")
let basicTextPayload = MMLivechatBasicPayload(text: text, threadId: "123-abc-...")
let basicAttachmentPaylad = MMLivechatBasicPayload(fileName: "image.png", data: pictureData, threadId: "123-abc-...")
let customDataPayload = MMLivechatCustomPayload(
customData: "{ \"key\": \"value\" }",
agentMessage: "Text to be seen only by agent",
userMessage: "Text to be seen by user and agent)",
threadId: "123-abc-...")If your widget supports multiple threads, you can create a new thread anytime, with a payload within, with the following method:
func createThread(_ payload: MMLivechatPayload, completion: @escaping ((MMLiveChatThread?, (Error)?) -> Void)Note:
Payload constructions and mentions (from send method above) still applies. Only exception is: a Draft payload won't be able to create a thread (only Basic and Custom Data ones can do so), and any threaId included will be ignored by the method.
Starting from 8.1.0 SDK version, we added sending attachments feature. In order to be able to choose attachment, you application need to be able to open photo library to choose the file or use camera and microphone to capture photo and video. By the Apple policies you will need to add following keys with text descriptions to your App's Info.plist file:
- NSPhotoLibraryUsageDescription
- NSCameraUsageDescription
- NSMicrophoneUsageDescription
To enable document picker, you will need to turn on the iCloud Documents capabilities in Xcode.
Starting from 8.3.0 SDK version, we added attachments preview feature. Supported file types are Images, Videos and PDFs.
In order to be able to save attachment to photo library, by the Apple policies you will need to add following key with text description to your App's Info.plist file:
- NSPhotoLibraryAddUsageDescription
By default, In-app chat supports the file types listed in the table below. If the Livechat widget is configured with custom attachment settings, In-app chat will follow that configuration instead.
| Media type | File size limit | File format |
|---|---|---|
| image | 10MB | .jpg, .jpeg, .jpe, .bmp, .gif, .svg, .svgz, .png, .tiff, .tif |
| video | 10MB | .avi, .mpeg, .mpg, .mpe, .m1v, .m2v, .qt, .mov, .mp4, .mp4v, .mpg4 |
| audio | 10MB | .aac, .mpga, .mp2, .mp2a, .mp3, .m2a, .m3a, .oga, .ogg, .spx, .opus, .wav |
| others | 10MB | .doc, .dot, .docx, .xls, .xlm, .xla, .xlc, .xlt, .xlw, .xlsx, .pdf, .txt, .text, .conf, .def, .list, .log, .in, .xml, .xsl |
Starting from version 9.2, new API is available to get and reset current unread chat push messages counter. The counter increments each time the application receives chat push message (this usually happens when chat screen is inactive or the application is in background/terminated state). In order to get current counter value use following API:
if let count = MobileMessaging.inAppChat?.getMessageCounter {
//use the count the way that suits you
}MobileMessaging SDK automatically resets the counter to 0 whenever user opens the chat screen. However, use the following API in case you need to manually reset the counter:
MobileMessaging.inAppChat?.resetMessageCounter()You can setup a callback in order to get updates of the counter in runtime, see examples below. Keep in mind, if your MMChatView stops being presented (and is deallocated), you may find useful to use some UIKit instance as MMInAppChatDelegate instead.
SwiftUI (MMChatView modifier):
MMChatView()
.onUnreadMessagesCountChange { count in
badgeCount = count
}UIKit (MMInAppChatDelegate):
import MobileMessaging
class MyViewController: UIViewController, MMInAppChatDelegate {
override func viewDidLoad() {
super.viewDidLoad()
MobileMessaging.inAppChat?.delegate = self
}
//...
func didUpdateUnreadMessagesCounter(_ count: Int) {
//use the count the way that suits you
}
}The predefined messages prompted within the In-app chat (such as status updates, button titles, input field prompt) by default are localized using the system locale settings, but you can easily change the language any time you need.
For example, before showing the chat view controller, you can provide your locale string, and it wil be used the next time chat is presented:
// These are all valid formats for "Spanish" locale
MobileMessaging.inAppChat?.setLanguage("es_ES")
MobileMessaging.inAppChat?.setLanguage("es-ES")
MobileMessaging.inAppChat?.setLanguage("es")And if you have the chat presented and loaded, you can change the language directly into it in real time (using the predefined MMLanguage's values):
Localization in chat happens only within the app, not outside. This means, your push notification content won't be translated in real time: push notification will only reflect the default language of your Livechat widget, as defined in Infobip web portal. But there is a solution: you can entirely customise the title and body of the push notifications, including applying localizations, by using a Notification Service Extension as explained here.
UIKit:
let vc = MMChatViewController.makeModalViewController()
// chat is presented, loaded, and you need to change its language
do {
try await vc.setLanguage(.es)
// all went as expected or chat is not presented yet
} catch {
print("Error changing language: \(error)")
}SwiftUI (via the coordinator):
@State private var chatCoordinator: MMChatView.Coordinator?
MMChatView()
.onMakeChatCoordinator { chatCoordinator = $0 }
// Then, when you need to change language:
try await chatCoordinator?.chatVC?.setLanguage(.es)expand to see completion block version
let vc = MMChatViewController.makeModalViewController()
vc.setLanguage(.es) { error in
// if error is nil, all went as expected or chat is not presented yet
}It is possible to send contextual data / metadata to Infobip’s Inbox (formerly Conversations) via In-App Chat SDK. The data can be send any time, several times, with only one restriction: the chat must be already loaded and presented, and the communication should have started (meaning, there are messages visible and not the initial “Start the chat” button). The data sent will be automatically linked to the conversationId and accountId internally.
There are two parameters:
- The mandatory data, sent as string, in the format of Javascript objects and values (for guidance, it must be accepted by JSON.stringify())
- And optionally, a multithread strategy that can be left empty, and will use
ACTIVEas the default. Possible values are:-
ACTIVE: Sends metadata to the current active conversation for the widget. -
ALL: Sends metadata to all non-closed conversations for the widget. -
ALL_PLUS_NEW: Sends metadata to all non-closed conversations for the widget and to any newly created conversations within the current session. If you send an event of a different type in the same session after usingALL_PLUS_NEW, it will override the previous metadata settings, and newly created chats will no longer receive metadata from the earlier usage ofALL_PLUS_NEW.
-
UIKit:
// Having created your MMChatViewController instance
let vc = MMChatViewController(type: .back)
// Present and wait till the chat is loaded and ready, then simply call
do {
try await vc.sendContextualData("{name: 'Robert'}")
} catch {
print("Error: \(error)")
}SwiftUI (via the coordinator):
@State private var chatCoordinator: MMChatView.Coordinator?
MMChatView()
.onMakeChatCoordinator { chatCoordinator = $0 }
// Then, when the chat is loaded and ready:
try await chatCoordinator?.chatVC?.sendContextualData("{name: 'Robert'}")expand to see completion block version
let vc = MMChatViewController(type: .back)
vc.sendContextualData("{name: 'Robert'}") { error in ... }There are two possible scenarios:
- Contextual data is sent before chat is present and loaded. In-app chat SDK stores the data and automatically sends it once the chat is loaded.
- Contextual data is sent when chat is present and loaded. In a single thread, the data will be sent to an open conversation. In multi-thread, LiveChat widget tracks a list of open conversations, and based on the strategy, it will either send it to a currently
ACTIVEconversation orALLconversations.
In-app chat supports having multiple conversations between an agent and a customer. Considering this, In-app chat needs to know to which conversations send contextual data:
-
ACTIVE- sends metadata to a currently active conversation -
ALL- sends metadata to all conversations between an agent and a customer (This field has no impact in case of a single-thread LiveChat widget.)
In-app chat SDK function to send contextual data has two parameters:
- The mandatory data, sent as string, in a format of JavaScript objects and values.
- An optional parameter, a multithread strategy that can be omitted and will use
ACTIVEas a default value. Possible values are:
-
ALLmetadata sent to all non-closed conversations for a widget -
ACTIVEmetadata sent to active only conversation for a widget
Usage:
// Send contextual data (async/await - recommended)
do {
try await MobileMessaging.inAppChat?.api?.sendContextualData("{name: 'Robert'}", multiThreadStrategy: .ACTIVE)
} catch {
print("Error: \(error)")
}
// or with ALL multithread strategy
do {
try await MobileMessaging.inAppChat?.api?.sendContextualData("{name: 'Robert'}", multiThreadStrategy: .ALL)
} catch {
print("Error: \(error)")
}expand to see non-async version
// Present and wait till the chat is loaded and ready, then simply call
MobileMessaging.inAppChat?.sendContextualData("{name: 'Robert'}")
// or with multithread flag
MobileMessaging.inAppChat?.sendContextualData("{name: 'Robert'}", multiThreadStrategy: .ALL)Default LiveChat widget (name of the channel InAppChat uses) works with single chat threads: one customer can only have one single open conversation. But the LiveChat widget could be created, in Customer Portal, with support for multiple chat threads.
When the setting above is enabled, the InAppChat UI will automatically offer in mobile:
- A list (initially empty) of all the unsolved conversation threads the user has opened.
- A button to "Start new chat" thread.
- The capability to navigate to each specific chat thread upon tapping in them.
If you are using the provided Chat Input offered by the SDK, the functionality for its presentation works out of the box: there is no need for you to take care of its hiding logic, as the SDK does it for you. But if you replaced our chat input with a view entirely of your own, you'll need to react to chat state changes. For example, hiding the chat input for any state that represents a "chatting unavailable" scenario, such as those containing "list", "loading" or "close" on its name. You can find further details in the definition of MMChatWebViewState.
SwiftUI: Use the .onChatStateChange modifier:
@State private var chatState: MMChatWebViewState = .unknown
MMChatView()
.useExternalChatInput()
.onChatStateChange { state in
chatState = state
}
// Show/hide your custom input based on chatStateUIKit: Use MMInAppChatDelegate's chatDidChange(to state:) method or the event MMNotificationInAppChatViewChanged.
When multiple threads functionality is enabled, the MMChatViewController may need to take control over the back button of the navigation bar to allow a return to the threads list. In other words: the chat view controller needs to replace the default back button and its logic if the internal state is "In Thread List". For this reason, it is recommended not to use InAppChat's multiple threads feature without a navigation bar (for example, when chat is presented as modal view) as the "return to thread list" would be impossible. Nevertheless, we allow to customise the style and intercept the actions of this back button, if you need so, as seen in the code below:
// Optional definition of a custom back button (but we recommend leaving the default one to handle the navigation for you)
let vc = MMChatViewController.makeRootNavigationViewController()
vc.initialLeftNavigationItem = UIBarButtonItem(image:style:target:action)The chat will then decide to consume or propagate the 'action' added to the button based on the chat needs.
It is possible to authenticate a user before accesing the chat, given you have enabled this feature in the LiveChat widget. The authentication is accomplished by combining the SDK's Personalization method with a JWT (JSON Web Token); a token that needs to be generated by your end (see instructions here). As tokens cannot be reused, the SDK will trigger the MMInAppChatDelegate's method getJWT() whenever a new token is required. In reality, this will be predictable: the method will be triggered only when a new chat view (or navigation) controller is created, or the first time you use an API method. Note that defining this delegate method is only necessary if your widget requires JWT for authentication, as defined on its setup.
The authentication will use a unique identifier for your user, that can be an email address, a phone number, or an external identifier. It is crucial for this identifier to be valid, and to match the identity defined in the Personalization - there will be an authentication error otherwise.
The ChatExample provided within the Mobile Messaging SDK offers a functional interface for testing the authentication use case (you just need to set your own Ids, keys and secret keys for it to work). A simplified use would be as follows:
SwiftUI (MMChatView modifier):
/*
1 - The customer authenticates in your system, and you recognise his/her unique identifier
2 - You call Personalize with this unique identifier
3 - Now display the chat with a JWT provider:
*/
MMChatView()
.jwtProvider {
return generateFreshJWT() // freshly new generated token each time it's called
}UIKit (MMInAppChatDelegate):
extension YourVC: MMInAppChatDelegate {
func getJWT() -> String? {
return yourValidJWT // freshly new generated token. See documentation linked above and/or ChatExample demo for more details
}
}
class YourVC: UIViewController {
func yourPresentingChatMethod() {
let vc = MMChatViewController.makeModalViewController() // Or your preferred presentation method/style.
self.navigationController?.present(vc, animated: true, completion: nil)
}
}Apple requires from you to describe the reason for sharing your user's identity data. Before doing personalization of your user with actual identity values (such as phone number, email address, name, etc.), remember to include the privacy category affected, as well as the purpose for the sharing, within your PrivacyInfo file, as described in the official documentation.
In order to customise the content (title and text body) of the push notifications received for chat messages, you need to create a Notification Service Extension and apply your desired modifications as explained here.
InAppChat cannot show itself on its own, it needs a parent application (your app) for it to be visible. We offer you two options to detect when a push notification has been tapped: delegation, or library events. Once you have detected a tap event, and have access to the received message, you need to check its boolean isChatMessage within:
-
Implement
MMMessageHandlingDelegateprotocol and its methoddidPerform(action:forMessage:completion:), i.e.:class MyMessageHandlingDelegateClass : MMMessageHandlingDelegate { func didPerform(action: MMNotificationAction, forMessage message: MM_MTMessage?, notificationUserInfo: [String: Any]?, completion: @escaping () -> Void) { if action.isTapOnNotificationAlert, message.isChatMessage { // Here you do as you need: showChat, deal with your app's badge message count, etc. } // don't forget to call `completion`, it's very important to tell the system that action handling is finished completion() } }
-
Pass your message handling delegate to MobileMessaging SDK:
MobileMessaging.messageHandlingDelegate = (your instance of the MyMessageHandlingDelegateClass class described above)
You can detect the tap event by listening to our library-events. For this case, the event you are looking for is called MMNotificationMessageTapped.
InAppChat decides automatically when to display/receive push notifications for incoming messages events based on the following rules:
- If Chat is not loaded/disconnected => Push notifications will be received
- If your app is in the background or a device is locked regardless of its connection status => Push notifications will be received
- If the app is in the foreground and chat is loaded/connected => Push notification will NOT be received
Given the fact that a chat could be loaded/connected but your app may have it hidden (e.g. behind another view) we provide you with the flexibility to control the logic above: You can still receive or stop push notifications in the foreground with these methods: stopConnection and restartConnection.
UIKit:
let vc = MMChatViewController.makeModalViewController() // Or your preferred presentation method/style
self.navigationController?.present(vc, animated: true, completion: nil)
// Hide the chat, e.g., by displaying a new modal on top of it
vc.stopConnection()
// Push notifications for new message events will start coming to the device
// When you detect that a chat is visible again to a user, you can reload the chat and stop push notifications by invoking:
vc.restartConnection()SwiftUI (via the coordinator):
// Stop connection when chat is hidden
chatCoordinator?.chatVC?.stopConnection()
// Restart when chat becomes visible again
chatCoordinator?.chatVC?.restartConnection()Important: note that calling stopConnection() with the chat being visible to a user may produce latency issues (for example, few-second delays within the chat UI which affects the quality of user experience).
Badge count logic (the count of unread messages, and its reset to zero), as a number seen in red, near your app's icon, needs to be adjusted to your app's needs, thus cannot be applied automatically by our SDK. In order to see the options and what's needed, please read the Notification Service Extension section and examples.
If you face any issue using the chat, specially on the first time integration, we encourage to try your application code/certificates in our ChatExample application, as this may give you a hint of potential mistakes. An example of the most common issues our integrators face:
- The chat content appears blank, and the text input fields is disabled.
Chat should only be presented once the MMInAppChatDelegate's inAppChatIsEnabled() method returns true. If you try to present it otherwise, the connection won't be establish, and chat will be disabled. There are many reasons for inAppChatIsEnabled() not returning true: from incorrect codes/ids in your setup, to badly defined livechat widget in Infobip's web portal. Usually, the console logs (if you enabled debug logs in our SDK) will give you a hint of the issue, and re-checking this guide, or comparing with our ChatExample, should be enough to successfully integrate InAppChat. But if the issue continues, don't exitate to contact our support or ask for help here in our repository's Issues section.
- I do receive inAppChatIsEnabled() as true, but the chat keeps appearing blank.
Please confirm, within the security and authentication (last) section of your widget setup (in Infobip's web portal), if "Mobile app customer authentication - Authenticate users on mobile using JSON Web Token" is enabled. If you enabled it by mistake, disable it. If you want to authenticate mobile users, make sure you are sending a correct JSON Web Token (otherwise, similar to the point #1, the chat will remain disabled).
- I get in the logs an error about no push registration id.
Please re-check the quick start guide and the steps mentioned above, specially the part about the difference between Sandbox and Production environments, and how they need different p12 files uploaded to Infobip's web portal for Apple push notification's to work.
- When a chat push notification is tapped, the app is invoked, and remains wherever it previously was - but I want it to display the chat related to the push notification I tapped.
InAppChat cannot present itself: if needs the parent application (your app) to present it. So, when a push notification is tapped, your app needs to recognise that event, and present the chat if you wish so. You can detect when a push notification is tapped, its nature and content, by listening to our library-events. For this case, the event you are looking for is called MMNotificationMessageTapped.