> ## Documentation Index
> Fetch the complete documentation index at: https://docs.reown.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter

With AppKit Flutter, you can easily let people interact with multiple EVM compatible wallets and blockchains.

Let's get started with the installation and configuration!

## Installation

<Note>
  If you are just starting a new project, you can use our [CLI tool](https://pub.dev/packages/reown_cli) to get started quickly.
</Note>

1. * Add `reown_appkit` as a dependency in your `pubspec.yaml` and run `flutter pub get` (check out the [latest version](https://pub.dev/packages/reown_appkit/install))
   * Or simply run `flutter pub add reown_appkit`
2. * Locate your `/ios/Podfile` file and add the following as the first line:

```ruby theme={null}
platform :ios, '13.0'
```

3. * Run `$ pod install` inside the `/ios` folder.
4. * Depending on the Flutter version you are running, you may be required to update your app's NDK version to `27.0.12077973` instead of the default `flutter.ndkVersion`
5. * You should now be able to run your app with `flutter run --dart-define=PROJECT_ID={your_project_id}`

### Enable Installed Wallet Detection

To enable AppKit to detect installed wallets on the device, you need to make specific changes to the native sides of the project.

<Tabs>
  <Tab title="iOS">
    1. Open your `Info.plist` file (typically under `ios/Runner/Info.plist`).
    2. Locate the `<key>LSApplicationQueriesSchemes</key>` section.
    3. Add the desired wallet schemes as string entries within the `<array>`. These schemes represent the wallets you want to detect.
    4. Refer to our [Info.plist example file](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/modal/ios/Runner/Info.plist) for a detailed illustration.

    Example:

    ```xml theme={null}
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>metamask</string>
      <string>trust</string>
      <string>safe</string>
      <string>rainbow</string>
      <!-- Add other wallet schemes names here -->
    </array>
    ```

    <Note>
      Apple only allows a maximum of 50 entries in this array, so every other entry beyond the 50th will not be considered. See note here: [https://developer.apple.com/documentation/uikit/uiapplication/canopenurl(\_:)#Discussion](https://developer.apple.com/documentation/uikit/uiapplication/canopenurl\(_:\)#Discussion)
    </Note>
  </Tab>

  <Tab title="Android">
    1. Open your `AndroidManifest.xml` file (typically under `android/app/src/main/AndroidManifest.xml`).
    2. Add your `<queries>...</queries>` schemes outside of `<application />` scope.
    3. Refer to [Android Specs](https://developer.android.com/guide/topics/manifest/queries-element) for more information.

    Example:

    ```xml theme={null}
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">

        <queries>
            <package android:name="io.metamask"/>
            <package android:name="com.wallet.crypto.trustapp"/>
            <package android:name="io.gnosis.safe"/>
            <package android:name="me.rainbow"/>
            <!-- Add other wallet schemes names here -->
        </queries>

        <application>
            ...
        </application>
    </manifest>
    ```
  </Tab>
</Tabs>

### Coinbase Wallet support

<Warning>
  The new **Base Wallet** (formerly Coinbase Wallet) does not currently support the Coinbase Mobile Wallet SDK used by this connector. This means the Coinbase connector **will not work with Base Wallet** until the Base team adds support for the SDK on their end. This is a known limitation on the Base/Coinbase side and not an issue with AppKit. We will update this documentation once the Base team implements SDK compatibility.
</Warning>

Coinbase Wallet does not use the WalletConnect protocol for communication between the dApp and the wallet.

This means that pairing topic, session topic, session events and other session-related features are not available when connecting to Coinbase Wallet.

However, you can still enable it to seamlessly connect with your dApp with these additional steps.

If you still want to support it, on your iOS and Android native side make the following changes:

<Tabs>
  <Tab title="iOS">
    1. Open your `Info.plist` file.
    2. Locate the `<key>LSApplicationQueriesSchemes</key>` section.
    3. Include the `<string>cbwallet</string>` scheme as mentioned above in the previous section

    Example:

    ```xml theme={null}
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>cbwallet</string>
      <!-- Any other scheme previously added -->
    </array>
    ```

    4. Make sure pods are installed, otherwise run `pod install` inside your `/ios` folder.
    5. Open your `/ios/Runner.xcworkspace` file with Xcode and add the following code in `AppDelegate.swift` file:

    ```swift theme={null}
    import CoinbaseWalletSDK
    ```

    ```swift {2-8} {14-21} theme={null}
    override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if #available(iOS 13.0, *) {
          if (CoinbaseWalletSDK.isConfigured == true) {
            if (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true {
                return true
            }
          }
        }

        return super.application(app, open: url, options: options)
    }

    override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        if #available(iOS 13.0, *) {
          if (CoinbaseWalletSDK.isConfigured == true) {
            if let url = userActivity.webpageURL,
                (try? CoinbaseWalletSDK.shared.handleResponse(url)) == true {
                return true
            }
          }
        }

        return super.application(application, continue: userActivity, restorationHandler: restorationHandler)
    }
    ```

    Check out the [AppDelegate.swift](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/modal/ios/Runner/AppDelegate.swift) file from our sample dapp for reference.
  </Tab>

  <Tab title="Android">
    1. Open your `AndroidManifest.xml` file.
    2. Add the `<package android:name="org.toshi"/>` scheme inside `<queries>...</queries>` as mentioned above in the previous section

    Example:

    ```xml theme={null}
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">

        <queries>
            <package android:name="org.toshi"/>
            <!-- Any other scheme previously added -->
        </queries>

        <application>
            ...
        </application>
    </manifest>
    ```
  </Tab>
</Tabs>

#### Disable Coinbase Wallet

Coinbase Wallet is enabled by default. However, in order to function properly, a few steps have to be done as described in the previous section. If you don't want to include/support Coinbase Wallet in your app, you just need to pass Coinbase Wallet id `fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa` to the [excludedWalletIds](./options#excludedwalletids) options array.

<hr />

### Phantom Wallet support

<Note>
  Phantom Wallet support is available from `reown_appkit: ^1.4.0`.
</Note>

Phantom Wallet does not use the WalletConnect protocol for communication between the dApp and the wallet, instead it provides an internal API mechanism based on deep/universal links.

This means that pairing topic, session topic, session events and other session-related features are not available when connecting to Phantom Wallet and the interaction is really basic. Dapp sends a request, Phantom Wallet responds. That's all.

Furthermore, Phantom Wallet's deep/universal linking mechanism supports interaction exclusively with the Solana network. This means that if you have EVM networks configured in your AppKit instance, they will not be available for use after connecting with Phantom.

In order to support Phantom Wallet interactions, a few extra steps have to be performed (only if you haven't implemented [Link Mode](./link-mode) already).

1. First, be sure you already have your redirection back property configured in your dApp's metadata. See [Redirect to your dApp](./usage#redirect-to-your-dapp)
2. Then you will have to implement your own Deep Link mechanism on the Flutter (and native) side so that when a link is received through it, you can just call `await _appKitModal.dispatchEnvelope(link);`

As guidance, here you can see how it's done in our sample dApp:

#### First, enable Phantom Wallet detection

<Tabs>
  <Tab title="iOS">
    1. Open your `Info.plist` file.
    2. Locate the `<key>LSApplicationQueriesSchemes</key>` section.
    3. Include `<string>phantom</string>` scheme

    Example:

    ```xml theme={null}
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>phantom</string>
      <!-- Any other scheme previously added -->
    </array>
    ```
  </Tab>

  <Tab title="Android">
    1. Open your `AndroidManifest.xml` file.
    2. Add `<package android:name="app.phantom"/>` scheme inside `<queries>...</queries>` as mentioned above in previous section

    Example:

    ```xml theme={null}
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">

        <queries>
            <package android:name="app.phantom"/>
            <!-- Any other scheme previously added -->
        </queries>

        <application>
            ...
        </application>
    </manifest>
    ```
  </Tab>
</Tabs>

#### Then, on Flutter side we create an `EventChannel` where links are going to be received and passed to `dispatchEnvelope()`.

<Tabs>
  <Tab title="Flutter">
    ```dart theme={null}
    import 'package:flutter/foundation.dart';
    import 'package:flutter/services.dart';
    import 'package:reown_appkit/modal/i_appkit_modal_impl.dart';

    class DeepLinkHandler {
      static const _eventChannel = EventChannel('com.exampledapp/events');
      static const _methodChannel = MethodChannel('com.exampledapp/methods');
      static late IReownAppKitModal _appKitModal;

      static void initEventChannel() {
        try {
          _eventChannel.receiveBroadcastStream().listen(_onLink, onError: _onError);
        } catch (e) {
          print(e);
        }
      }

      static void initMethodChannel(IReownAppKitModal appKitModal) {
        _appKitModal = appKitModal;
      }

      static void checkInitialLink() async {
        try {
          _methodChannel.invokeMethod('initialLink');
        } catch (e) {
          print(e);
        }
      }

      static void _onLink(dynamic link) async {
        try {
          _appKitModal.dispatchEnvelope(link);
        } catch (e) {
          print(e);
        }
      }

      static void _onError(dynamic e) {
        print(e);
      }
    }
    ```
  </Tab>
</Tabs>

#### Then, on both native sides we leverage native APIs to capture the app opening link and send it to Flutter side.

<Tabs>
  <Tab title="iOS">
    ```swift theme={null}
    import UIKit
    import Flutter

    @main
    @objc class AppDelegate: FlutterAppDelegate {
        
        private static let EVENTS_CHANNEL = "com.exampledapp/events" // TODO: Use your own channel name
        private static let METHODS_CHANNEL = "com.exampledapp/methods" // TODO: Use your own channel name
        
        private var eventsChannel: FlutterEventChannel?
        private var methodsChannel: FlutterMethodChannel?
        var initialLink: String?
        
        private let linkStreamHandler = LinkStreamHandler()
        
        override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            GeneratedPluginRegistrant.register(with: self)
            
            if let window = self.window, let controller = window.rootViewController as? FlutterViewController {
                eventsChannel = FlutterEventChannel(name: AppDelegate.EVENTS_CHANNEL, binaryMessenger: controller.binaryMessenger)
                eventsChannel?.setStreamHandler(linkStreamHandler)
                
                methodsChannel = FlutterMethodChannel(name: AppDelegate.METHODS_CHANNEL, binaryMessenger: controller.binaryMessenger)
                methodsChannel?.setMethodCallHandler({ [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
                    if (call.method == "initialLink") {
                        if let link = self?.initialLink {
                            let handled = self?.linkStreamHandler.handleLink(link)
                            if (handled == true) {
                                self?.initialLink = nil
                            }
                        }
                    }
                })
            }
            
            // Add your deep link handling logic here
            if let url = launchOptions?[.url] as? URL {
                self.initialLink = url.absoluteString
            }

            if let userActivityDictionary = launchOptions?[.userActivityDictionary] as? [String: Any],
               let userActivity = userActivityDictionary["UIApplicationLaunchOptionsUserActivityKey"] as? NSUserActivity,
               userActivity.activityType == NSUserActivityTypeBrowsingWeb {
                
                handleIncomingUniversalLink(userActivity: userActivity)
            }
            
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        
        override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
            return linkStreamHandler.handleLink(url.absoluteString)
        }
        
        override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
            if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
                handleIncomingUniversalLink(userActivity: userActivity)
                return true
            }
            
            return false
        }
        
        private func handleIncomingUniversalLink(userActivity: NSUserActivity) {
            if let url = userActivity.webpageURL {
                // Handle the URL, navigate to appropriate screen
                print("App launched with Universal Link: \(url.absoluteString)")
                let handled = linkStreamHandler.handleLink(url.absoluteString)
                if (!handled){
                    self.initialLink = url.absoluteString
                }
            }
        }
    }

    class LinkStreamHandler: NSObject, FlutterStreamHandler {
        var eventSink: FlutterEventSink?
        var queuedLinks = [String]()
        
        func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
            self.eventSink = events
            queuedLinks.forEach({ events($0) })
            queuedLinks.removeAll()
            return nil
        }
        
        func onCancel(withArguments arguments: Any?) -> FlutterError? {
            self.eventSink = nil
            return nil
        }
        
        func handleLink(_ link: String) -> Bool {
            guard let eventSink = eventSink else {
                queuedLinks.append(link)
                return false
            }
            eventSink(link)
            return true
        }
    }
    ```
  </Tab>

  <Tab title="Android">
    ```kt theme={null}
    package dapp.example

    import io.flutter.embedding.android.FlutterActivity
    import android.content.BroadcastReceiver
    import android.content.Context
    import android.content.Intent
    import android.os.Bundle
    import io.flutter.plugin.common.EventChannel

    class MainActivity: FlutterActivity() {
      private val eventsChannel = "com.exampledapp/events"
      private var linksReceiver: BroadcastReceiver? = null

      override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        EventChannel(flutterEngine?.dartExecutor?.binaryMessenger, eventsChannel).setStreamHandler(
          object : EventChannel.StreamHandler {
            override fun onListen(args: Any?, events: EventChannel.EventSink) {
              linksReceiver = createChangeReceiver(events)
            }
            override fun onCancel(args: Any?) {
              linksReceiver = null
            }
          }
        )
      }

      override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        if (intent.action === Intent.ACTION_VIEW) {
          linksReceiver?.onReceive(this.applicationContext, intent)
        }
      }

      fun createChangeReceiver(events: EventChannel.EventSink): BroadcastReceiver? {
        return object : BroadcastReceiver() {
          override fun onReceive(context: Context, intent: Intent) {
            val dataString = intent.dataString ?:
            events.error("UNAVAILABLE", "Link unavailable", null)
            events.success(dataString)
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

Links to full examples:

* [Android's platform specific code](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/base/android/app/src/main/kotlin/com/example/dapp/MainActivity.kt) (only `eventsChannel` is important Phantom wise)
* [iOS's platform specific code](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/base/ios/Runner/AppDelegate.swift)  (only `eventsChannel` is important Phantom wise)
* [Dart's specific code](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/base/lib/utils/deep_link_handler.dart)  (only `eventsChannel` is important Phantom wise)

*Constructing a Solana transaction would depend on the library/package of your choice but in [*our sample dApp's code*](https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/base/lib/utils/crypto/helpers.dart#L170) you can see how we do it using `solana_web3` package.*

#### Disable Phantom Wallet

Phantom Wallet is enabled by default. However, in order to function properly, a few steps have to be done as described in the previous section. If you don't want to include/support Phantom Wallet in your app, you just need to pass Phantom Wallet id `a797aa35c0fadbfc1a53e7f675162ed5226968b44a19ee3d24385c64d1d3c393` to the [excludedWalletIds](./options#excludedwalletids) options array.

<hr />

### Solflare Wallet support

<Note>
  Solflare Wallet support is available from `reown_appkit: ^1.6.0`.
</Note>

Similar to Phantom Wallet, Solflare does not use the WalletConnect protocol for communication between the dApp and the wallet, instead it provides an internal API mechanism based on deep/universal links.

This means that pairing topic, session topic, session events and other session-related features are not available when connecting to Solflare Wallet and the interaction is really basic. Dapp sends a request, Solflare Wallet responds. That's all.

Furthermore, Solflare Wallet's deep/universal linking mechanism supports interaction exclusively with the Solana network. This means that if you have EVM networks configured in your AppKit instance, they will not be available for use after connecting with Solflare.

In order to support Solflare Wallet interactions, a few extra steps have to be performed (only if you haven't implemented [Link Mode](./link-mode) already).

1. First, be sure you already have your redirection back property configured in your dApp's metadata. See [Redirect to your dApp](./usage#redirect-to-your-dapp)
2. Then you will have to implement your own Deep Link mechanism on the Flutter (and native) side so that when a link is received through it, you can just call `await _appKitModal.dispatchEnvelope(link);`

As guidance, here you can see how it's done in our sample dApp:

#### First, enable Solflare Wallet detection

<Tabs>
  <Tab title="iOS">
    1. Open your `Info.plist` file.
    2. Locate the `<key>LSApplicationQueriesSchemes</key>` section.
    3. Include `<string>solflare</string>` scheme

    Example:

    ```xml theme={null}
    <key>LSApplicationQueriesSchemes</key>
    <array>
      <string>solflare</string>
      <!-- Any other scheme previously added -->
    </array>
    ```
  </Tab>

  <Tab title="Android">
    1. Open your `AndroidManifest.xml` file.
    2. Add `<package android:name="com.solflare.mobile"/>` scheme inside `<queries>...</queries>` as mentioned above in previous section

    Example:

    ```xml theme={null}
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">

        <queries>
            <package android:name="com.solflare.mobile"/>
            <!-- Any other scheme previously added -->
        </queries>

        <application>
            ...
        </application>
    </manifest>
    ```
  </Tab>
</Tabs>

<Note>
  The rest of the configuration is identical to Phantom's configuration.
</Note>

#### Disable Solflare Wallet

Solflare Wallet is enabled by default. However, in order to function properly, a few steps have to be done as described in the previous section. If you don't want to include/support Solflare Wallet in your app, you just need to pass Solflare Wallet id `1ca0bdd4747578705b1939af023d120677c64fe6ca76add81fda36e350605e79` to the [excludedWalletIds](./options#excludedwalletids) options array.

## Examples

<Card title="Basic example" icon="github" href="https://github.com/reown-com/reown_flutter/blob/develop/packages/reown_appkit/example/example.md">
  A starting point to use AppKit with Flutter
</Card>

<Card title="Full example" icon="github" href="https://github.com/reown-com/reown_flutter/tree/develop/packages/reown_appkit/example/base/lib">
  Full Flutter example using AppKit, which is also our demo dApp
</Card>

## Test Apps

Want to see AppKit in action? Download our sample AppKit apps below and explore what it can do. Enjoy! 😊

* [Android Build (Firebase)](https://appdistribution.firebase.dev/i/52c9b87bbf5fbe01)
* [iOS Build (Testflight)](https://testflight.apple.com/join/6aRJSllc)
