Skip to content

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)
}
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

See deeplinks

Analytics Events

See analytics

Handovers

See handovers