iOS
Download the InvestSuite SDK from your Codemagic dashboard¶
The InvestSuite team will provide you with a Codemagic dashboard where you can download (new) versions of the SDK as we release them. Download the latest one and store it in a convenient location where you can easily access it later.
Unzip the "artifacts" file you downloaded from Codemagic. This should give you a .zip file.
The version number will of course vary based on which version you're loading.
If you unzip this file you'll not only find the SDK packages, but also a fully working example iOS project that embeds the InvestSuite SDK and gives you a nice boilerplate.
Example app¶
Each SDK embedding module ships with a functional example app that shows how to use the SDK.
You can find this by unzipping the .zip file you downloaded from Codemagic inside the example folder.
In order to use this example app, run pod install in the example folder and open the .xcworkspace file in Xcode and run the project.
Requirements¶
- iOS 18.0 or later (for the async/await gRPC-based handover service)
- Xcode 16.0 or later
Install the SDK as a dependency¶
First put the SDK folder in your project, then add the following to your Podfile:
require_relative 'path_to_investsuite_sdk/podhelper'
platform :ios, '18.0'
target 'YourApp' do
install_all_flutter_pods()
target 'YourAppTests' do
inherit! :search_paths
# Pods for testing
end
target 'YourAppUITests' do
# Pods for testing
end
post_integrate do |installer|
ivs_sdk_post_integrate('YourApp')
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '18.0'
end
end
end
end
Run pod install to install the dependencies.
Usage of the add-to-app SDK¶
Import the SDK¶
import flutter_embedding
import investsuite_embedding
Implementing the HandoversToHostService¶
Before starting the engine, you need to implement the HandoversToHostService which handles callbacks from Flutter to your app. This uses Swift async/await with the gRPC protocol:
@available(iOS 18.0, *)
class ExampleHandoversToHostService: HandoversToHostService.SimpleServiceProtocol {
var accessToken: String = ""
weak var viewController: UIViewController?
func provideAccessToken(
request: ProvideAccessTokenRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> ProvideAccessTokenResponse {
var response = ProvideAccessTokenResponse()
response.accessToken = accessToken
return response
}
func provideAnonymousAccessToken(
request: ProvideAnonymousAccessTokenRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> ProvideAnonymousAccessTokenResponse {
var response = ProvideAnonymousAccessTokenResponse()
response.anonymousAccessToken = ""
return response
}
func receiveAnalyticsEvent(
request: ReceiveAnalyticsEventRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> ReceiveAnalyticsEventResponse {
// Log analytics to your service
print("Analytics: \(request.name) at \(request.eventLocation)")
return ReceiveAnalyticsEventResponse()
}
func receiveDebugLog(
request: ReceiveDebugLogRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> ReceiveDebugLogResponse {
print("[\(request.level)] \(request.message)")
return ReceiveDebugLogResponse()
}
func receiveError(
request: ReceiveErrorRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> ReceiveErrorResponse {
print("Error: \(request.errorCode)")
return ReceiveErrorResponse()
}
func onExit(
request: OnExitRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> OnExitResponse {
await MainActor.run {
// Dismiss the Flutter view controller
viewController?.dismiss(animated: true)
}
return OnExitResponse()
}
func startFaq(
request: StartFaqRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartFaqResponse {
// Navigate to your FAQ screen
return StartFaqResponse()
}
func startOnboarding(
request: StartOnboardingRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartOnboardingResponse {
// Start your onboarding flow
var response = StartOnboardingResponse()
response.success = true
return response
}
func startFundPortfolio(
request: StartFundPortfolioRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartFundPortfolioResponse {
// Start your funding flow
var response = StartFundPortfolioResponse()
response.success = true
return response
}
func startAddMoney(
request: StartAddMoneyRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartAddMoneyResponse {
// Start your add money flow
var response = StartAddMoneyResponse()
response.success = true
return response
}
func startAuthorization(
request: StartAuthorizationRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartAuthorizationResponse {
// Perform authorization (e.g., Face ID, Touch ID, PIN)
return await withCheckedContinuation { continuation in
Task { @MainActor in
let alert = UIAlertController(
title: "Authorization",
message: "Please authorize this action",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Authorize", style: .default) { _ in
var response = StartAuthorizationResponse()
response.success = true
continuation.resume(returning: response)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
var response = StartAuthorizationResponse()
response.success = false
continuation.resume(returning: response)
})
viewController?.present(alert, animated: true)
}
}
}
func startTransactionSigning(
request: StartTransactionSigningRequest,
context: FlutterEmbeddingGRPCCore.ServerContext
) async throws -> StartTransactionSigningResponse {
// Perform transaction signing
return await withCheckedContinuation { continuation in
Task { @MainActor in
let alert = UIAlertController(
title: "Transaction Signing",
message: "Sign transaction for \(request.amount)?",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "Sign", style: .default) { _ in
var response = StartTransactionSigningResponse()
response.success = true
continuation.resume(returning: response)
})
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
var response = StartTransactionSigningResponse()
response.success = false
continuation.resume(returning: response)
})
viewController?.present(alert, animated: true)
}
}
}
}
Creating StartParams¶
Create the start parameters with language, theme mode, and environment:
func createStartParams() -> StartParams {
var startParams = StartParams()
startParams.environment = "TST" // "MOCK", "TST", "UAT", "PROD"
startParams.language = .en
startParams.themeMode = .system
return startParams
}
Available Languages:
- .en - English
- .nl - Dutch
- .fr - French
- .ar - Arabic
- .tr - Turkish
Available Theme Modes:
- .light
- .dark
- .system
Starting the Flutter engine¶
Before using the SDK, you need to start the engine:
@available(iOS 18.0, *)
func startEngine() {
let handoversToHostService = ExampleHandoversToHostService()
handoversToHostService.accessToken = "your-access-token"
handoversToHostService.viewController = self
let startParams = createStartParams()
InvestSuiteEmbedding.shared.startEngine(
startParams: startParams,
handoversToHostService: handoversToHostService
) { success, error in
if success != nil {
print("Successfully started engine")
// Engine is ready, you can now show the Flutter UI
} else {
print("Error when starting engine: \(error?.localizedDescription ?? "Unknown error")")
}
}
}
Showing the Flutter SDK as a modal view controller¶
do {
let vc = try InvestSuiteEmbedding.shared.getViewController()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
} catch {
print("Error when starting screen: \(error)")
}
Embedding the Flutter SDK in a container view¶
You can also embed the Flutter SDK within a container in your view:
do {
let flutterViewController = try InvestSuiteEmbedding.shared.getViewController()
// Add as child view controller
addChild(flutterViewController)
flutterContainer.addSubview(flutterViewController.view)
flutterViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
flutterViewController.view.topAnchor.constraint(equalTo: flutterContainer.topAnchor),
flutterViewController.view.leadingAnchor.constraint(equalTo: flutterContainer.leadingAnchor),
flutterViewController.view.trailingAnchor.constraint(equalTo: flutterContainer.trailingAnchor),
flutterViewController.view.bottomAnchor.constraint(equalTo: flutterContainer.bottomAnchor)
])
flutterViewController.didMove(toParent: self)
} catch {
print("Error when starting Flutter in view: \(error)")
}
To remove the Flutter view:
for child in children {
if child.view.superview == flutterContainer {
child.willMove(toParent: nil)
child.view.removeFromSuperview()
child.removeFromParent()
}
}
Communicating with the Flutter SDK (HandoversToFlutterService)¶
Once the engine is started, you can call methods on the Flutter SDK using async/await:
Change Language¶
Task {
var request = ChangeLanguageRequest()
request.language = .fr
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<ChangeLanguageRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().changeLanguage(request: clientRequest)
}
Change Theme Mode¶
Task {
var request = ChangeThemeModeRequest()
request.themeMode = .dark
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<ChangeThemeModeRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().changeThemeMode(request: clientRequest)
}
Reset the SDK¶
Task {
// Reset to initial state (home screen)
var request = ResetRequest()
request.clearData_p = false
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<ResetRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().reset(request: clientRequest)
}
// Reset and clear all cached data
Task {
var request = ResetRequest()
request.clearData_p = true
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<ResetRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().reset(request: clientRequest)
}
Navigate to a specific screen (Deep linking)¶
Task {
var request = NavigateToRequest()
request.deeplink = "/self/portfolio/DEMO/more"
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<NavigateToRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().navigateTo(request: clientRequest)
}
Handle a notification¶
Task {
var notificationData = InvestSuiteNotificationData()
notificationData.id = "notification-123"
notificationData.title = "Deposit Received"
notificationData.body = "Your deposit has been processed"
notificationData.type = "CASH_DEPOSIT_EXECUTED"
notificationData.module = "SELF"
notificationData.createdAt = Int64(Date().timeIntervalSince1970 * 1000)
notificationData.data = ["portfolio_id": "DEMO"]
var request = HandleNotificationRequest()
request.notificationData = notificationData
let clientRequest = FlutterEmbeddingGRPCCore.ClientRequest<HandleNotificationRequest>(message: request)
try await InvestSuiteEmbedding.shared.handoversToFlutterService().handleNotification(request: clientRequest)
}
Stop the Flutter engine¶
After using the SDK, you can stop the engine to free up resources:
InvestSuiteEmbedding.shared.stopEngine()
Handling notifications¶
See notifications
Deeplinks¶
See deeplinks
Analytics Events¶
See analytics
Handovers¶
See handovers