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

# Push Notifications

> Push Notifications — Zixflow Flutter SDK integration guide.

Complete guide to implementing push notifications in Flutter with Zixflow using Firebase Cloud Messaging (FCM) for both iOS and Android.

> **💡 Track your campaigns:** The SDK automatically tracks delivery, opens, and clicks. For advanced tracking customization and implementation details, see [Push Notification Tracking](/documentation/sdk/flutter/push-notification-tracking).

### Prerequisites

Before implementing push notifications:

1. **Configure FCM in Zixflow dashboard** - Add FCM Server Key to Zixflow dashboard
2. **Firebase Project Setup** - Create Firebase project and add both iOS and Android apps
3. **Device tokens must be associated with users** - Call `identify()` before sending notifications
4. **Test on physical devices only** - Emulators/simulators cannot receive push notifications

**Important**: A device/user can't receive push notifications until you: (1) Register a device token, (2) Identify a person, (3) Check notification permissions.

***

### Platform-Specific Requirements

#### iOS Requirements

* **iOS 13.0 or higher**
* **Xcode 14.0 or higher**
* **Push Notifications capability** enabled in Xcode
* **Notification Service Extension** for rich push (images, videos)
* **Physical Device** required for testing

#### Android Requirements

* **API Level 21** (Android 5.0) or higher
* **Firebase Project** configured
* **google-services.json** in `android/app/` directory
* **POST\_NOTIFICATIONS permission** for Android 13+ (API 33+)

***

### Step 1: Firebase Project Setup

#### 1. Create Firebase Project

1. Go to [https://console.firebase.google.com](https://console.firebase.google.com)
2. Click **Create a project** or select existing project
3. Follow the setup wizard

#### 2. Add iOS App to Firebase

1. In Firebase console, click **Add app** → Select **iOS**
2. Enter iOS Bundle ID (e.g., `com.yourcompany.app`)
3. Download `GoogleService-Info.plist`
4. Add `GoogleService-Info.plist` to `ios/Runner/` directory in Xcode

#### 3. Add Android App to Firebase

1. In Firebase console, click **Add app** → Select **Android**
2. Enter Android package name (same as iOS bundle ID)
3. Download `google-services.json`
4. Place `google-services.json` in `android/app/` directory

***

### Step 2: Add Dependencies

Update your `pubspec.yaml`:

```yaml theme={null}
dependencies:
  zixflow: ^1.0.0
  firebase_core: ^3.3.0
  firebase_messaging: ^15.0.4
  flutter_local_notifications: ^19.5.0  # For foreground notifications
```

Install dependencies:

```bash theme={null}
flutter pub get
```

***

### Step 3: Configure Flutter Code

#### Initialize Firebase and Zixflow

Update your `main.dart`:

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:zixflow/zixflow.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'firebase_options.dart';  // Generated by FlutterFire CLI

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase FIRST
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // Configure foreground notification presentation (iOS)
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true,
    badge: true,
    sound: true,
  );

  // Initialize Zixflow SDK
  final config = ZixflowConfig(
    apiKey: 'YOUR_API_KEY',
    logLevel: LogLevel.debug,
  );
  await Zixflow.initialize(config: config);

  runApp(MyApp());
}
```

**Note:** To generate `firebase_options.dart`, use FlutterFire CLI:

```bash theme={null}
# Install FlutterFire CLI
dart pub global activate flutterfire_cli

# Configure Firebase for your Flutter app
flutterfire configure
```

***

### Step 4: Handle Push Notifications

Complete push notification handling implementation:

```dart theme={null}
import 'package:flutter/material.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:zixflow/zixflow.dart';

// Background message handler (must be top-level function)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  print('Handling background message: ${message.messageId}');
  // Track delivery metric if needed
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  @override
  void initState() {
    super.initState();
    _setupPushNotifications();
  }

  Future<void> _setupPushNotifications() async {
    // Register background message handler
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    // Request permission (iOS)
    await _requestPushPermission();

    // Get FCM token and register with Zixflow
    final token = await FirebaseMessaging.instance.getToken();
    if (token != null) {
      Zixflow.instance.registerDeviceToken(deviceToken: token);
      print('FCM Token: $token');
    }

    // Listen for token refresh
    FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
      Zixflow.instance.registerDeviceToken(deviceToken: newToken);
      print('FCM Token refreshed: $newToken');
    });

    // Handle notification when app is opened from terminated state
    FirebaseMessaging.instance.getInitialMessage().then((message) {
      if (message != null) {
        _handlePushNotificationClick(message, 'terminated');
      }
    });

    // Handle notification when app is in background
    FirebaseMessaging.onMessageOpenedApp.listen((message) {
      _handlePushNotificationClick(message, 'background');
    });

    // Handle notification when app is in foreground
    FirebaseMessaging.onMessage.listen((message) {
      print('Foreground message: ${message.notification?.title}');
      _showForegroundNotification(message);
    });
  }

  Future<void> _requestPushPermission() async {
    FirebaseMessaging messaging = FirebaseMessaging.instance;

    NotificationSettings settings = await messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
      provisional: false,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print('User granted push notification permission');
    } else if (settings.authorizationStatus == AuthorizationStatus.denied) {
      print('User declined push notification permission');
    } else {
      print('Push notification permission not determined');
    }
  }

  void _handlePushNotificationClick(RemoteMessage message, String appState) {
    print('Push notification clicked - App state: $appState');

    // Track push notification click with Zixflow
    Zixflow.instance.track(
      name: 'push_notification_clicked',
      properties: {
        'push_title': message.notification?.title ?? '',
        'app_state': appState,
        'message_id': message.messageId ?? '',
      },
    );

    // Handle deep link navigation
    if (message.data.containsKey('link')) {
      final deepLink = message.data['link'];
      _navigateToDeepLink(deepLink);
    }
  }

  void _showForegroundNotification(RemoteMessage message) {
    // Display notification when app is in foreground
    // Use flutter_local_notifications package (see Local Notifications section)
  }

  void _navigateToDeepLink(String deepLink) {
    // Implement deep link navigation
    // Example: Navigate to specific screen based on deepLink
    print('Navigating to deep link: $deepLink');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      home: HomeScreen(),
    );
  }
}
```

***

### Step 5: Identify Users

**Critical**: Device tokens must be associated with a user before push notifications can be delivered.

```dart theme={null}
import 'package:zixflow/zixflow.dart';

// Identify user when they log in
Future<void> handleLogin(String userId, String email) async {
  try {
    // Identify user with Zixflow
    Zixflow.instance.identify(
      userId: userId,
      traits: {
        'email': email,
        'first_name': 'John',
        'last_name': 'Doe',
        'plan': 'premium',
      },
    );

    print('User identified: $userId');
  } catch (e) {
    print('Failed to identify user: $e');
  }
}
```

**Important Notes:**

* Device tokens are automatically generated when the app starts
* Tokens are linked to Zixflow profiles when you call `identify()`
* This ensures notifications are delivered to the correct user

***

### iOS-Specific Setup

#### Step 1: Enable Push Capabilities in Xcode

1. Open `<yourAppName>.xcworkspace` in `ios/` folder
2. Select your main app target (Runner)
3. Go to **Signing & Capabilities** tab
4. Click **+ Capability** → Add **Push Notifications**
5. Click **+ Capability** → Add **Background Modes**
6. Enable **Remote notifications** under Background Modes

#### Step 2: Create Notification Service Extension

For rich push notifications (images, videos, GIFs):

**1. Create Extension in Xcode:**

1. Select **File** → **New** → **Target**
2. Choose **Notification Service Extension**
3. Name it `NotificationServiceExtension`
4. Click **Finish**
5. Click **Cancel** when asked about activation

**2. Configure Podfile:**

Update `ios/Podfile`:

```ruby theme={null}
target 'Runner' do
  use_frameworks!
  use_modular_headers!

  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))

  # Add Zixflow FCM pod
  pod 'zixflow/fcm', :path => '.symlinks/plugins/zixflow/ios'
end

# Notification Service Extension target
target 'NotificationServiceExtension' do
  use_frameworks!
  pod 'zixflow_richpush/fcm', :path => '.symlinks/plugins/zixflow/ios'
end
```

**For Swift Package Manager (Flutter 3.24+, Xcode 16.0+):**

Enable SPM:

```bash theme={null}
flutter config --enable-swift-package-manager
```

Remove old pod references from Podfile, then in Xcode:

1. Add `FlutterGeneratedPluginSwiftPackage` to NotificationServiceExtension target
2. Go to **General** → **Frameworks and Libraries**

**3. Install Dependencies:**

```bash theme={null}
cd ios
pod install --repo-update
cd ..
```

**4. Update NotificationService.swift:**

```swift theme={null}
import UserNotifications
import ZixflowMessagingPushFCM

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        // Initialize SDK for extension
        MessagingPushFCM.initializeForExtension(
            withConfig: MessagingPushConfigBuilder(apiKey: "YOUR_API_KEY")
                .build()
        )

        if let bestAttemptContent = bestAttemptContent {
            // Let Zixflow handle rich push and track delivery
            MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        // Called when extension is about to be terminated
        MessagingPush.shared.serviceExtensionTimeWillExpire()

        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}
```

**Important:** Replace `"YOUR_API_KEY"` with your actual API key.

#### Step 3: Configure AppDelegate.swift

Update `ios/Runner/AppDelegate.swift`:

```swift theme={null}
import UIKit
import Flutter
import ZixflowMessagingPushFCM
import ZixflowFirebaseWrapper
import FirebaseMessaging
import FirebaseCore

@main
class AppDelegateWitZfIntegration: ZixflowAppDelegateWrapper<AppDelegate> {}

class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        GeneratedPluginRegistrant.register(with: self)

        // Initialize Zixflow push messaging
        MessagingPushFCM.initialize(
            withConfig: MessagingPushConfigBuilder()
                .showPushAppInForeground(true)  // Show notifications in foreground
                .build()
        )

        // Set notification delegate
        UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate

        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }

    override func application(
        _ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
    ) {
        super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)

        // Register APNs token with Firebase
        Messaging.messaging().apnsToken = deviceToken
    }

    // Optional: Fine-grained control over foreground presentation
    override func userNotificationCenter(
        _ center: UNUserNotificationCenter,
        willPresent notification: UNNotification,
        withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
    ) {
        completionHandler([.banner, .list, .badge, .sound])
    }
}
```

**Foreground Notification Options:**

* Set `showPushAppInForeground(true)` to display notifications when app is active
* Customize presentation with `userNotificationCenter:willPresent:` delegate method
* Available options: `.banner`, `.list`, `.badge`, `.sound`

**Sound in Notifications:**

* Your app needs permission to play sounds (requested via `requestPermission`)
* Users can disable sound in iOS Settings
* Use "Default" sound setting for best experience (triggers vibration when sound is disabled)
* Custom sounds require additional native configuration

***

### Android-Specific Setup

Android push is automatically configured when following the Getting Started instructions. Additional customization is available:

#### Step 1: Configure Gradle

**Project-level `android/build.gradle`:**

```gradle theme={null}
buildscript {
    dependencies {
        classpath 'com.google.gms:google-services:4.4.0'
    }
}
```

**App-level `android/app/build.gradle`:**

```gradle theme={null}
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.google.gms.google-services'  // Add this line
}

android {
    compileSdkVersion 34

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 34
    }
}
```

#### Step 2: Add Permissions

Add to `android/app/src/main/AndroidManifest.xml`:

```xml theme={null}
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Required permissions -->
    <uses-permission android:name="android.permission.INTERNET" />

    <!-- Required for Android 13+ (API 33+) -->
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application>
        <!-- Your app configuration -->
    </application>
</manifest>
```

#### Step 3: Customize Notification Icon and Color

Add to `android/app/src/main/AndroidManifest.xml` inside `<application>` tag:

```xml theme={null}
<application>
    <!-- Default notification icon (white, transparent background) -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/ic_notification" />

    <!-- Default notification color (accent color) -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/colorNotificationIcon" />
</application>
```

**Create notification icon:**

1. Place white icon PNG in `android/app/src/main/res/drawable/ic_notification.png`
2. Create multiple sizes for different densities (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)

**Define notification color:**

Add to `android/app/src/main/res/values/colors.xml`:

```xml theme={null}
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorNotificationIcon">#FF6200EE</color>
</resources>
```

***

### Local Notifications (Foreground Display)

Show notifications when app is in foreground using `flutter_local_notifications`:

```dart theme={null}
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:firebase_messaging/firebase_messaging.dart';

final FlutterLocalNotificationsPlugin localNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

Future<void> setupLocalNotifications() async {
  // Android settings
  const androidSettings = AndroidInitializationSettings('@drawable/ic_notification');

  // iOS settings
  const iosSettings = DarwinInitializationSettings(
    requestAlertPermission: false,
    requestBadgePermission: false,
    requestSoundPermission: false,
  );

  // Initialization settings
  const initSettings = InitializationSettings(
    android: androidSettings,
    iOS: iosSettings,
  );

  await localNotificationsPlugin.initialize(
    initSettings,
    onDidReceiveNotificationResponse: (response) {
      // Handle local notification click
      Zixflow.instance.track(
        name: 'local_notification_clicked',
        properties: {'payload': response.payload ?? ''},
      );
    },
  );
}

Future<void> showForegroundNotification(RemoteMessage message) async {
  const androidDetails = AndroidNotificationDetails(
    'default_channel',
    'Default Channel',
    channelDescription: 'Default notification channel',
    importance: Importance.high,
    priority: Priority.high,
  );

  const iosDetails = DarwinNotificationDetails(
    presentAlert: true,
    presentBadge: true,
    presentSound: true,
  );

  const details = NotificationDetails(
    android: androidDetails,
    iOS: iosDetails,
  );

  await localNotificationsPlugin.show(
    message.hashCode,
    message.notification?.title ?? 'New Message',
    message.notification?.body ?? '',
    details,
    payload: message.data['link'],
  );
}
```

***

### Rich Push Payload Structure

Zixflow automatically handles rich push notifications. Here's how to structure payloads:

#### iOS FCM Payload

```json theme={null}
{
  "message": {
    "apns": {
      "payload": {
        "aps": {
          "mutable-content": 1,
          "alert": {
            "title": "Special Offer",
            "body": "Check out our new features!"
          },
          "badge": 1,
          "sound": "default"
        },
        "zixflow": {
          "push": {
            "link": "yourapp://product/123",
            "image": "https://example.com/image.jpg"
          }
        }
      },
      "headers": {
        "apns-priority": 10
      }
    }
  }
}
```

#### Android Payload

```json theme={null}
{
  "message": {
    "data": {
      "title": "Order Shipped",
      "body": "Your order is on the way!",
      "image": "https://example.com/shipping.jpg",
      "link": "yourapp://orders/456"
    }
  }
}
```

***

### Testing Push Notifications

#### 1. Verify Device Token Registration

Check if device token is registered:

```dart theme={null}
Future<void> verifyPushSetup() async {
  try {
    final token = await FirebaseMessaging.instance.getToken();
    print('FCM Token: $token');

    if (token != null) {
      print('✓ Device token registered');
    } else {
      print('✗ Device token not available');
    }

    final settings = await FirebaseMessaging.instance.getNotificationSettings();
    print('Permission status: ${settings.authorizationStatus}');
  } catch (e) {
    print('Push verification failed: $e');
  }
}
```

#### 2. Enable Debug Logging

Set log level to debug during development:

```dart theme={null}
final config = ZixflowConfig(
  apiKey: 'YOUR_API_KEY',
  logLevel: LogLevel.debug,  // Verbose logging
);
```

#### 3. Test on Physical Devices

**iOS:**

* Push notifications **do not work** on iOS Simulator
* Must use physical iOS device
* Ensure APNs certificate configured in Firebase

**Android:**

* Emulator works if Google Play Services installed
* Physical device always recommended
* Ensure FCM Server Key configured in Zixflow dashboard

#### 4. Send Test Push from Zixflow

1. Log into Zixflow dashboard
2. Navigate to **Messaging** → **Push Notifications**
3. Create test push notification with image
4. Send to specific user (using `userId` from `identify()`)

***

### Troubleshooting

#### Firebase Configuration Issues

**Error:** `GoogleService-Info.plist not found` (iOS)

**Solution:**

1. Ensure file is in `ios/Runner/` directory
2. Verify file is added to Xcode target (check target membership)
3. Clean and rebuild: `flutter clean && flutter pub get`

***

**Error:** `google-services.json not found` (Android)

**Solution:**

1. Ensure file is in `android/app/` directory
2. Verify Google Services plugin applied in `android/app/build.gradle`
3. Sync Gradle files

***

#### Push Notifications Not Received

**iOS Checklist:**

* [ ] Push Notifications capability enabled in Xcode
* [ ] APNs certificate uploaded to Firebase
* [ ] Testing on physical device (not simulator)
* [ ] User identified: `Zixflow.instance.identify()`
* [ ] Permission granted by user
* [ ] Foreground display configured if app is active

**Android Checklist:**

* [ ] `google-services.json` in `android/app/` directory
* [ ] Google Services plugin applied
* [ ] FCM Server Key in Zixflow dashboard
* [ ] POST\_NOTIFICATIONS permission granted (Android 13+)
* [ ] Device registered for push

***

#### Rich Push Not Working (Images)

**iOS Solutions:**

1. Ensure Notification Service Extension properly configured
2. Verify extension has correct dependencies in Podfile
3. Check `mutable-content: 1` in push payload
4. Verify image URL is accessible
5. Check extension logs for errors

**Android Solutions:**

1. Verify image URL accessible from device
2. Check internet connectivity
3. Ensure image format supported (JPEG, PNG, GIF)

***

#### Permission Denied

**Symptoms:** User denied push permission

**Solutions:**

1. Explain value before requesting permission
2. Show in-app message about benefits
3. Guide users to Settings to enable manually:
   ```dart theme={null}
   import 'package:permission_handler/permission_handler.dart';

   if (await Permission.notification.isDenied) {
     openAppSettings();
   }
   ```

***

#### Device Token Not Available

**Symptoms:** `getToken()` returns `null`

**Solutions:**

1. Verify Firebase initialized before calling `getToken()`
2. Check Google Play Services available (Android)
3. Ensure internet connectivity
4. Review native logs for errors
5. Rebuild app and retry

***

### Best Practices

1. **Initialize Firebase first** - Before Zixflow SDK
2. **Request permission thoughtfully** - Explain value before asking
3. **Identify users immediately** - After authentication
4. **Test on physical devices** - Simulators have limitations
5. **Use rich push** - Images increase engagement
6. **Handle deep links** - Provide seamless navigation
7. **Monitor metrics** - Track delivery and open rates
8. **Enable debug logging** - During development only
9. **Test both platforms** - iOS and Android behave differently
10. **Clear identity on logout** - Call `clearIdentify()`
11. **Configure App Groups** (iOS) - For reliable delivery tracking

***
