# Link SDKs in Flutter In this guide we will walk you through the steps to integrate the Link SDKs into your Flutter app. You can integrate Enode’s Link SDKs for iOS and Android into your Flutter app by making them accessible through platform channels. Using this guide, you will: 1. Ensure that you have the prerequisites to get started 2. Expose the Link SDKs in iOS and/or Android via platform channels 3. Import the exposed native modules into your Flutter app ## Prerequisites - An existing Flutter application configured to build iOS and Android apps, with native code under `/android` and `/ios` directories - A server that calls [Link User](https://developers.enode.com/api/reference?prerelease#postUsersUseridLink) with API version `2024-01-01`. You should be able to generate a `linkToken` to pass back to your Flutter client ## Implement the SDK on iOS ### Set up your Xcode project Open the iOS project in your Flutter app (found under `/ios/*.xcworkspace`) in Xcode. Follow the **[Set up your Xcode project](https://developers.enode.com/docs/link-sdks/ios#set-up-your-xcode-project)** steps from our iOS SDK guide to add LinkKit and configure Bluetooth access. CocoaPods installation: For Flutter projects, you can add the SDK via CocoaPods using the podspec URL instead of manual xcframework integration. See the **[CocoaPods](https://developers.enode.com/docs/link-sdks/ios#c-using-cocoa-pods)** section in the iOS guide. ### Implement a platform channel (iOS) Decide where in your existing iOS code you'd like to call Link UI via a [platform-specific implementation](https://docs.flutter.dev/platform-integration/platform-channels?tab=type-mappings-kotlin-tab#step-4-add-an-ios-platform-specific-implementation). The following example is implemented within the existing `AppDelegate.swift` file that is part of every Flutter app, demonstrating the most critical piece of creating a `Handler` instance and calling its `present()` method within `setMethodCallHandler`. Flutter 3.38 UISceneDelegate migration: Flutter 3.38 introduces UISceneDelegate adoption, which changes how iOS apps are initialized. The code below uses the new `FlutterImplicitEngineDelegate` pattern. For more details, see the [Flutter UISceneDelegate migration guide](https://docs.flutter.dev/release/breaking-changes/uiscenedelegate). ```swift import UIKit import Flutter import LinkKit @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { var handler: Handler? override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { return super.application(application, didFinishLaunchingWithOptions: launchOptions) } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) let linkChannel = FlutterMethodChannel( name: "com.your-app-name/linkkit", binaryMessenger: engineBridge.applicationRegistrar.messenger() ) linkChannel.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in guard call.method == "showLinkUI" else { result(FlutterMethodNotImplemented) return } guard let args = call.arguments as? Dictionary else { result(FlutterError(code: "INVALID_ARGUMENTS", message: "Arguments must be a dictionary", details: nil)) return } guard let token = args["linkToken"] else { result(FlutterError(code: "MISSING_LINK_TOKEN", message: "linkToken is required", details: nil)) return } self?.handler = Handler(linkToken: token) { (code: LinkResultCode, errMsg: String?) in if let errMsg = errMsg { result(FlutterError(code: "LINK_ERROR", message: errMsg, details: nil)) } else { result(["result": code.rawValue]) } } guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, let rootViewController = windowScene.windows.first?.rootViewController else { result(FlutterError(code: "NO_VIEW_CONTROLLER", message: "Unable to obtain root view controller", details: nil)) return } self?.handler?.present(from: rootViewController) }) } } ``` You will also need to update your `Info.plist` to include the UISceneDelegate configuration. Add the following inside the main `` element: ```xml UIApplicationSceneManifest UIApplicationSupportsMultipleScenes UISceneConfigurations UIWindowSceneSessionRoleApplication UISceneClassName UIWindowScene UISceneDelegateClassName FlutterSceneDelegate UISceneConfigurationName flutter UISceneStoryboardFile Main ``` ## Implement the SDK in Android ### Import the SDK to your project Open the Android project in your Flutter app (found under `/android`), ideally in Android Studio. Follow our [Android SDK guide](https://developers.enode.com/docs/link-sdks/android) to include the SDK in your app’s `build.gradle`. (In a Flutter project, you should add the dependency in `/android/app/build.gradle`.) ### Configure your project #### **SDK versions** If necessary, you may have to increase your compile SDK version (34) and your minimum SDK version (24). For example, depending how you define the properties, your `/android/app/build.gradle` should resemble: ```jsx android { compileSdkVersion 34 ... defaultConfig { minSdkVersion 24 targetSdkVersion 34 ... } } ``` #### Styles & permissions The Link SDK makes use of a few style attributes and permissions. Follow the guide for our [Android SDK](https://developers.enode.com/docs/link-sdks/android) to ensure that you are defining and applying these appropriately to avoid errors. #### Configure ProGuard Flutter’s code obfuscation can impact release builds. To avoid this, you can prevent any optimization of LinkKit by adding this to your `proguard-rules.pro`: ```kotlin -keep class io.enode.link.** { *; } ``` ### Implement a platform channel (Android) Decide where in your existing Android code you'd like to call Link UI via a [platform-specific implementation](https://docs.flutter.dev/platform-integration/platform-channels?tab=type-mappings-kotlin-tab#step-3-add-an-android-platform-specific-implementation). The following simple example is directly implemented within the existing `MainActivity.kt` file that is part of every Flutter app, demonstrating the most critical piece of starting Link UI via `showLinkUI()` within `setMethodCallHandler`. ```kotlin package com.your-app-name import android.app.Activity import android.content.Intent import androidx.annotation.NonNull import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import io.enode.link.LinkKit const val LINK_UI_REQUEST = 99 class MainActivity: FlutterActivity() { private val CHANNEL = "com.your-app-name/linkkit" private lateinit var _result: MethodChannel.Result; private fun showLinkUI(token: String) { val intent = Intent(activity, LinkKit::class.java) intent.putExtra(LinkKit.INTENT_LINK_TOKEN, token); activity.startActivityForResult(intent, LINK_UI_REQUEST, null); } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { if (requestCode == LINK_UI_REQUEST) { if (resultCode == Activity.RESULT_OK) { _result.success(hashMapOf("result" to "success")) } else { _result.error( intent?.getStringExtra(LinkKit.ERROR_CODE).toString(), intent?.getStringExtra(LinkKit.ERROR_DETAILS), "Error" ) } } else { super.onActivityResult(requestCode, resultCode, intent); } } override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> if (call.method == "showLinkUI") { val token: String? = call.argument("linkToken"); if (token != null) { _result = result; showLinkUI(token); } } } } } ``` ## Call your platform code from Flutter Now that you have implementations of the SDKs in your native iOS and Android projects, you can leverage them in your Dart code via method channels. First, [create a link session](https://developers.enode.com/docs/link-sdks/introduction#create-a-link-session) on your server and make sure the resulting `linkToken` is available in your React Native app. (For Android, remember to supply an app deeplink as your redirect URI.) Next, decide where you want to show Link UI and create a `MethodChannel` there: ```dart static const platform = MethodChannel('com.your-app-name/linkkit'); ``` (Ensure the name of your channel is the same one as you used in your native implementations.) When you are ready to open Link UI, call `platform.invokeMethod()`, being sure to specify a return type `T` and passing in the method name that matches your native implementations. In the full example below, we use `Map` and `showLinkUI`. ```dart String linkToken = /* comes from server */ ; try { final result = await platform.invokeMethod( 'showLinkUI', {'linkToken': linkToken} ); if (result != null) { setState(() { _result = result["result"].toString(); }); } } on Exception catch (e) { setState(() { _error = e.message; }); } ``` ## Other ways to integrate [Link SDK for iOS](https://developers.enode.com/docs/link-sdks/ios) [Link SDK for Android](https://developers.enode.com/docs/link-sdks/android)