LogoLogo
SupportGithubSign InGet Access
  • Introduction
  • GETTING STARTED
    • SDK Quick Start
    • API Quick Start
  • Guides
    • Web
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Web authentication methods
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Configure a custom subdomain
      • Eject a wallet
      • Using the EIP-1193 Provider
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • iOS
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Manage ERC20 tokens
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
          • Passkey + Enclave Storage
        • Recover a wallet
      • Troubleshooting Tips
      • Feature Flags
    • Android
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • React Native
      • Create a wallet
      • Send tokens
      • Sign a transaction
      • Simulate a transaction
      • Back up a wallet
      • Recover a wallet
      • Cross-device sessions
      • Manage wallet lifecycle states
      • Connect with WalletConnect
      • Build a WebView
      • Perform swaps
      • Add custom signature hooks
      • MPC progress callbacks
      • Portal API methods
      • Eject a wallet
      • Legacy documentation
        • Back up a wallet
          • Backup Options
        • Recover a wallet
    • Enclave MPC API
      • Create a client
      • Create a wallet
      • Send tokens
      • Sign Ethereum transactions
      • Sign Solana transactions
      • Sign Tron transactions
      • Sign Stellar Transaction
      • Concurrent Transactions
      • Back up a wallet
      • Eject a wallet
  • Reference
    • iOS
      • createWallet
      • backupWallet
      • recoverWallet
      • ejectPrivateKeys
      • registerBackupMethod
      • setGDriveConfiguration
      • setPasskeyConfiguration
      • setPasskeyAuthenticationAnchor
      • setPassword
      • availableRecoveryMethods
      • doesWalletExist
      • isWalletBackedUp
      • isWalletOnDevice
      • isWalletRecoverable
      • getBalances
      • getAssets
      • getNftAssets
      • getTransactions
      • sendSol
      • evaluateTransaction
      • buildEip155Transaction
      • buildSolanaTransaction
      • getWalletCapabilities
    • Android
      • Reference Documentation
    • React Native
      • @portal-hq/core
      • Storage adapters
        • Cloud storage
          • @portal-hq/gdrive-storage
          • @portal-hq/icloud-storage
        • Mobile storage
          • @portal-hq/keychain
          • @portal-hq/mobile-key-values
    • Enclave MPC API
      • V1 endpoints
    • Client API
      • V3 endpoints
      • V1 endpoints
    • Custodian API
      • V3 endpoints
      • V1 endpoints
    • Swaps API
      • V3 endpoints
      • V1 endpoints
  • Resources
    • Flutter
      • iOS
      • Android
    • Error codes
      • Overview
      • MPC errors
      • Network errors
      • General errors
      • Encryption errors
      • Portal Connect errors
    • Portal's MPC architecture
    • Authentication and API Keys
    • Self-Managed Backups
    • Alert Webhooks
    • Wallet lifecycle
    • Backup options
      • Password/PIN
      • GDrive
      • iCloud
      • Passkey + Enclave
    • WalletConnect metadata
    • Advanced security scanning
    • Account abstraction
    • Security firewall
    • Eject
    • Security
    • Blockchain support
    • Chain ID formatting
    • Testnet faucets
    • Going to Production
    • Rate Limits
    • Multi-backup migration guide
    • Multi-wallet migration guides
      • Migrating from Android SDK v3.x.x to v4.x.x
      • Migrating from iOS SDK v3.0.x to v3.2.x
  • Support
    • Changelog
      • Android
      • iOS
      • React Native
      • Web
      • Past Releases
        • 2024 Releases
        • 2023 Releases
    • Celo Hackathon Hub
    • Glossary
Powered by GitBook
On this page
  • Add Portal implementation for iOS:
  • Calling platform-specific code using platform channels

Was this helpful?

  1. Resources
  2. Flutter

iOS

Follow this guide to integrate Portal into your Flutter App.

PreviousFlutterNextAndroid

Last updated 5 months ago

Was this helpful?

In order to integrate with Portal into your Flutter App currently you will need to write custom platform-specific code using platform channels.

On the client side, enables sending messages that correspond to method calls. On the platform side, MethodChannel on Android () and FlutterMethodChannel on iOS () enable receiving method calls and sending back a result. These classes allow you to develop a platform plugin with very little 'boilerplate' code. You can check the Flutter official documentation for . You can check our example.

Add Portal implementation for iOS:

  1. Open the iOS host portion of your Flutter app in Xcode.

  2. Open AppDelegate.swift located under Runner > Runner in the project navigation.

Override the application:didFinishLaunchingWithOptions: function and create a FlutterMethodChannel

The client and host sides of a channel are connected through a channel name passed in the channel constructor. All channel names used in a single app must be unique; prefix the channel name with a unique 'identifier', for example: your.bundle.identifier/portal.

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let portalChannel = FlutterMethodChannel(name: "your.bundle.identifier/portal",
                                              binaryMessenger: controller.binaryMessenger)
    
    setCallHandler(channel: portalChannel)

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

Then define a callback to handle any method invocation that comes from the Flutter side and they Switch on those Methods then execute the proper Swift implementation for each one and reply back to Flutter using a regular callback of type FlutterResult which expected to be passed from Flutter side as a completion handler.

Here is an example implementation:

private func setCallHandler(
    channel: FlutterMethodChannel
) {
    channel.setMethodCallHandler { [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) in
        Task {
            switch call.method {
            case "initializePortal":
                if let apiKey = call.arguments as? String {
                    PortalWrapper.initializePortal(apiKey: apiKey, result: result)
                } else {
                    result(FlutterError(code: "INVALID_ARGUMENT", message: "Expected a String for apiKey", details: nil))
                }
                
            case "createWallet":
                PortalWrapper.createWallet(result: result)
                
            // TODO: - Handle all the messages here.

            default:
                result(FlutterMethodNotImplemented)
            }
        }
    }
}

And here is an PortalWrapper implementation:

import Flutter
import PortalSwift

class PortalWrapper {
    static var portal: Portal?
    
    static func initializePortal(apiKey: String, result: @escaping FlutterResult) {
        // Initialize portal here using the portal iOS SDK
        Task {
            do {
                portal = try Portal(
                    apiKey,
                    withRpcConfig: [
                        "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" : "https://api.mainnet-beta.solana.com",
                        "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": "https://api.devnet.solana.com"
                    ]
                )
                
                result([
                    "success": true,
                    "message": "Portal initialized"
                ])
            } catch {
                result(FlutterError(code: "FAILED",
                                    message: "Error registering portal with exception: \(error.localizedDescription)",
                                    details: nil))
            }
        }
    }
    
    static func createWallet(result: @escaping FlutterResult) {
        // Create wallet here using the portal iOS SDK
        guard let portal = portal else {
            result(FlutterError(code: "UNAVAILABLE",
                                message: "Portal is not initialized",
                                details: nil))
            return
        }
        
        Task {
            do {
                let (ethereum, solana) = try await portal.createWallet()
                
                result([
                    "success": true,
                    "addresses": [
                        "ethereum": ethereum,
                        "solana": solana
                    ]
                ])
            } catch {
                result(FlutterError(code: "FAILED",
                                    message: "Error creating portal wallet with exception: \(error.localizedDescription)",
                                    details: nil))
            }
        }
    }
}

Calling platform-specific code using platform channels

Now Portal iOS implementation is ready for Flutter, Let's create the Flutter platform client: We will use the same channel name we defined above to initialize MethodChannel

static const platform = MethodChannel('your.bundle.identifier/portal');

Next, invoke a method on the method channel, specifying the concrete method to call using the String identifier initializePortal. The call might fail, so wrap the invokeMethod call in a try-catch statement.

Future<void> initializePortal(String apiKey) async {
  String message;
  try {
    final result = await platform.invokeMethod('initializePortal', apiKey);
    message = 'Success with message ${result?['message']}';
  } on PlatformException catch (e) {
    message = "Failed to initialize portal with error: '${e.message}'.";
  }
}

Finally, your Flutter function initializePortal to initialize portal is ready to be called. You can call it from Flutter side passing the apiKey to initialize Portal.

Add PortalSwift to the iOS project, you can follow this .

MethodChannel
MethodChannelAndroid
MethodChanneliOS
platform channels
Portal Flutter
Platform channels overview
Setup