# Flutter iOS
>[!IMPORTANT]
>[Flutter Obfuscation issue](https://github.com/pendo-io/pendo-mobile-sdk/issues/196#issue-2605284796)
>Migration from the track-event solution to the low-code solution:
> 1. Refer to [Step 2](#step-2-pendo-sdk-integration) in the installation guide, and verify the addition of the navigationObserver and clickListener.
> 2. The migration process will take up to 24 hours to complete and be reflected in Pendo, during the processing time, you will not be able to tag Pages and Features.
>[!IMPORTANT]
>Requirements:
>- Flutter: ">=3.16.0"
>- SDK: ">=3.2.0 < 4.0.0"
>
>Supported Navigation Libraries:
>
>- GoRouter 13.0 or higher
>- AutoRoute 7.0 or higher
## Step 1. Add Pendo dependency
In the root folder of your flutter app add the Pendo package: `flutter pub add pendo_sdk`.
## Step 2. Integrate with the Pendo SDK
>[!NOTE]
>Find your API key in the Pendo UI under `Settings` > `Subscription settings` > select an app > `App Details`.
1. For optimal integration place the following code at the beginning of your app's execution:
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
var pendoKey = 'YOUR_API_KEY_HERE';
await PendoSDK.setup(pendoKey);
```
2. Initialize the Pendo Session where your visitor is being identified (e.g., login, register, etc.).
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
final String visitorId = 'John Smith';
final String accountId = 'Acme Inc.';
final dynamic visitorData = {'Age': '25', 'Country': 'USA'};
final dynamic accountData = {'Tier': '1', 'Size': 'Enterprise'};
await PendoSDK.startSession(visitorId, accountId, visitorData, accountData);
```
>[!TIP]
>To begin a session for an anonymous visitor, pass ```null``` or an empty string ```''``` as the Visitor ID. You can call the `startSession` API more than once and transition from an anonymous session to an identified session (or even switch between multiple identified sessions).
3. Add Navigation Observers
When using `Flutter Navigator API` add PendoNavigationObserver for each app Navigator:
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
// Observes the MaterialApp/CupertinoApp main Navigator
return MaterialApp(
...
navigatorObservers: [
PendoNavigationObserver()
],);
// Observes the nested widget Navigator
return Navigator(
...
observers: [
PendoNavigationObserver()
],);
```
> [!TIP]
> The Pendo SDK uses the `Route` name to uniquely identify each `Route`. Pendo highly recommends that you give a unique name to each route in the `RouteSettings`. The unique names must also be applied to the `showModalBottomSheet` api.
**Navigation Types:**
* **_GoRouter_**
When using `GoRouter`, change the `setup` API call to include the correct navigation library, like so:
```dart
PendoSDK.setup(pendoKey, navigationLibrary: NavigationLibrary.GoRouter);
```
When using `GoRouter`, apply the `addPendoListenerToDelegate()` to your `GoRouter` instance.
Make sure to add it once (e.g., adding it in the build method will be less desired)
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
final GoRouter _router = GoRouter()..addPendoListenerToDelegate()
class _AppState extends State {
@override
Widget build(BuildContext context) {
return PendoActionListener(
child: MaterialApp.router(
routerConfig: _router,
),
);
}
}
```
> [!TIP]
> Pendo SDK uses routerDelegate listener to track route change analytics, make sure your route is included in the GoRouter routes
* **_AutoRoute_**
When using `AutoRoute`, change the `setup` API call to include the correct navigation library, like so:
```dart
PendoSDK.setup(pendoKey, navigationLibrary: NavigationLibrary.AutoRoute);
```
When using `AutoRoute`, apply the `addPendoListenerToDelegate()` to your `AutoRoute.config()` instance.
Make sure to add it once (e.g., adding it in the build method will be less desired)
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
@AutoRouterConfig()
class AppRouter extends RootStackRouter {
@override
List get routes => [];
}
final AppRouter _router = AppRouter()..config().addPendoListenerToDelegate();
class _AppState extends State {
@override
Widget build(BuildContext context) {
return PendoActionListener(
child: MaterialApp.router(
routerConfig: _router.config(),
),
);
}
}
```
> [!TIP]
> Pendo SDK uses routerDelegate listener to track route change analytics, make sure your route is included in the AutoRoute routes.
4. Add a click listener
Wrap the main widget with a PendoActionListener in the root of the project:
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
Widget build(BuildContext context) {
return PendoActionListener( // Use the PendoActionListener to track action clicks
child: MaterialApp(
title: 'Title',
home: Provider(
create: (context) => MyHomePageStore()..initList(),
child: MyHomePage(title: Strings.appName),
),
navigatorObservers: [PendoNavigationObserver()], // Use Pendo Observer to track the Navigator stack transitions
);
)
}
```
> [!TIP]
> You can use track events to programmatically notify Pendo of custom events of interest:
```dart
import 'package:pendo_sdk/pendo_sdk.dart';
await PendoSDK.track('name', { 'firstProperty': 'firstPropertyValue', 'secondProperty': 'secondPropertyValue'});
```
## Step 3. Mobile device connectivity and testing
>[!NOTE]
> Find your scheme ID in the Pendo UI under `Settings` > `Subscription settings` > select an app > `App Details`.
These steps enable guide testing capabilities.
1. **Add Pendo URL scheme to **info.plist** file:**
Under App Target > Info > URL Types, create a new URL by clicking the + button.
Set **Identifier** to pendo-pairing or any name of your choosing.
Set **URL Scheme** to `YOUR_SCHEME_ID_HERE`.
2. **To enable pairing from the device:**
In the AppDelegate file add or modify the **openURL** function:
Swift Instructions - Click to expand or collapse
```swift
import Pendo
@UIApplicationMain
class AppDelegate: FlutterAppDelegate {
override func application(_ app: UIApplication,open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if url.scheme?.range(of: "pendo") != nil {
PendoManager.shared().initWith(url)
return true
}
// your code here...
return true
}
}
```
Objective-C Instructions - Click to expand or collapse
```objectivec
@import Pendo;
//your code
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options {
if ([[url scheme] containsString:@"pendo"]) {
[[PendoManager sharedManager] initWithUrl:url];
return YES;
}
// your code here ...
return YES;
}
```
## Step 4. Verify installation
1. Test using Xcode:
Run the app while attached to Xcode.
Review the Xcode console and look for the following message:
`Pendo Mobile SDK was successfully integrated and connected to the server.`
2. In the Pendo UI, go to `Settings` > `Subscription Settings`.
3. Select your application from the list.
4. Select the `Install Settings tab` and follow the instructions under `Verify Your Installation` to ensure you have successfully integrated the Pendo SDK.
5. Confirm that you can see your app as `Integrated` under subscription settings.
## Using custom navigation widgets
`With the release of version 3.6.2 custom navigation widget support was added.`
To integrate custom navigation widgets (e.g., TabBar, BottomNavigationBar, PageView), follow these steps:
1. Implement `PendoCustomNavigationWidget` in your `StatefulWidget` class.
2. Implement `PendoNavigationState` in the corresponding state class.
3. Override `getPendoCustomNavigationInfo()` to provide details about the current navigation state.
Example:
```dart
class CUSTOM_STATEFULL_WIDGET extends StatefulWidget implements PendoCustomNavigationWidget { // Added for Pendo Support
...
}
class _CUSTOM_STATEFULL_WIDGET_STATE extends State implements PendoNavigationState { // Added for Pendo Support
// Override this method to let Pendo get the info about the current selected state
@override
List getPendoCustomNavigationInfo() {
List info = [];
info.add(PendoCustomNavigationInfo(
navigationWidgetType: PendoNavigationWidgetType.Top, // specifies widget location on the screen. this must be provided
currentSelectedIndex: 1, // current selected index, optional
numberOfIndexes: 5, // total number of indexes, optional
currentSelectedTitle: 'first item', // unique title, optional
selectedIconCode: 1234)); // unique number representing the image on the selected item, optional
info.add(PendoCustomNavigationInfo(
navigationWidgetType: PendoNavigationWidgetType.Middle,
currentSelectedIndex: 2,
numberOfIndexes: 3,
currentSelectedTitle: 'second item',
selectedIconCode: 2468));
info.add(PendoCustomNavigationInfo(
navigationWidgetType: PendoNavigationWidgetType.Bottom,
currentSelectedIndex: 3,
numberOfIndexes: 6,
currentSelectedTitle: 'third item',
selectedIconCode: 1357));
return info;
}
}
```
## Limitations
- [Notes, Known Issues & Limitations](/other/flutter-notes-known-issues-limitations.md).
- To support hybrid mode in Flutter, please open a ticket.
## Developer documentation
- API documentation available [here](/api-documentation/flutter-apis.md).
- See [Native application with Flutter components](/other/native-with-flutter-components.md) integration instructions.
## Troubleshooting
- For technical issues, please [review open issues](https://github.com/pendo-io/pendo-mobile-sdk/issues) or [submit a new issue](https://github.com/pendo-io/pendo-mobile-sdk/issues).
- See our [release notes](https://developers.pendo.io/category/mobile-sdk/).
- For additional documentation, visit our [Help Center](https://support.pendo.io/hc/en-us/categories/23324531103771-Mobile-implementation).