XPush Flutter SDK

Flutter plugin for integrating xNotify/XPush push notifications, in-app notifications, notification center, unread notification count, Android notification tap handling, app lifecycle handling, user session handling, and deep linking.

⚠️ Android 13+ requires runtime notification permission.

The XPush Flutter SDK does not request notification permission automatically. The host application is responsible for requesting and managing notification permission before initializing the SDK.


Features

  • Android push notification support
  • In-app notification popup support
  • Notification center support
  • Unread notification count support
  • Foreground, background, and terminated state handling
  • Notification tap handling
  • Deep linking support
  • User login/contact update support
  • Logout/session clearing support
  • Android 13+ notification permission compatible, host app managed

Installation

Add the package in the host Flutter app pubspec.yaml:

dependencies:
  xpush_flutter: ^1.0.0

Then run:

flutter pub get

Android Setup

1. Add JitPack Repository

Open:

android/settings.gradle

Add JitPack inside both repository blocks:

pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
        maven { url 'https://jitpack.io' }
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

For older Android projects, add JitPack in:

android/build.gradle
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

2. Kotlin SDK Dependency

The native Android SDK dependency used by this Flutter plugin is:

implementation("com.github.dilawarabbas1:kotlin-sdk-v2:stg-4.2.0")

This dependency should be added inside the plugin Android module:

android/build.gradle

Example:

dependencies {
    implementation("com.github.dilawarabbas1:kotlin-sdk-v2:stg-4.2.0")
}

3. Android Permissions

Open:

android/app/src/main/AndroidManifest.xml

Add these permissions above the <application> tag:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

POST_NOTIFICATIONS is required for Android 13 and above.


4. Android 13+ Notification Permission

Android 13, API level 33, requires runtime notification permission before push notifications can be displayed.

The XPush Flutter SDK does not request notification permission automatically.

The host application is responsible for:

  • Requesting notification permission
  • Handling permission denial
  • Redirecting users to settings if permission is permanently denied

Example using permission_handler

Add this dependency in the host app:

dependencies:
  permission_handler: ^11.3.1

Import:

import 'package:permission_handler/permission_handler.dart';

Create this method:

Future<void> requestNotificationPermission() async {
  final status = await Permission.notification.request();

  if (status.isGranted) {
    debugPrint("Notification permission granted");
  } else {
    debugPrint("Notification permission denied");
  }
}

Call it before SDK initialization:

Future<void> initializeApp() async {
  await requestNotificationPermission();

  await XpushSdkController.initializeSdk();

  await XpushSdkController.onForeground();
}

Important: If notification permission is not granted on Android 13+, push notifications may not be displayed even though the SDK is initialized correctly.


5. MainApplication Setup

Create or update:

android/app/src/main/kotlin/YOUR_PACKAGE_NAME/MainApplication.kt

Example:

package YOUR_PACKAGE_NAME

import android.app.Application
import com.tencent.mmkv.MMKV

class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        MMKV.initialize(this)
    }
}

Register it in:

android/app/src/main/AndroidManifest.xml

Inside the <application> tag:

<application
    android:name=".MainApplication"
    android:label="@string/app_name"
    android:icon="@mipmap/ic_launcher">
</application>

6. Firebase Messaging Service

Inside the <application> tag in:

android/app/src/main/AndroidManifest.xml

Add:

<service
    android:name="com.xpush.sdk.MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

This service is required for receiving push notifications from Firebase Cloud Messaging.


7. MainActivity Setup

In:

android/app/src/main/AndroidManifest.xml

Make sure MainActivity uses:

android:launchMode="singleTop"

Example:

<activity
    android:name=".MainActivity"
    android:exported="true"
    android:launchMode="singleTop"
    android:theme="@style/LaunchTheme"
    android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
    android:hardwareAccelerated="true"
    android:windowSoftInputMode="adjustResize">

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

</activity>

singleTop is required so notification tap and deep link intents can be handled correctly through the existing activity.


Flutter Usage

Import the SDK controller.

For the package/example project:

import 'package:xpush_flutter_example/sdk/xpush_sdk_controller.dart';

For pub.dev package usage, the recommended import should be:

import 'package:xpush_flutter/sdk/xpush_sdk_controller.dart';

Note: Before publishing, make sure xpush_sdk_controller.dart is available inside the package lib/ folder and not only inside the example/ app.


SDK Initialization

Initialize the SDK after app startup or after splash:

await XpushSdkController.initializeSdk();

Example:

Future<void> initSdk() async {
  try {
    await XpushSdkController.initializeSdk();
    await XpushSdkController.onForeground();
  } catch (e) {
    debugPrint("XPush SDK init failed: $e");
  }
}

Recommended flow:

await requestNotificationPermission();
await XpushSdkController.initializeSdk();
await XpushSdkController.onForeground();

Contact Update After Login

After user login, call contactUpdate with the user's phone number:

await XpushSdkController.contactUpdate(phoneNumber);

Example:

if (isLoggedIn && phoneNumber != null) {
  await XpushSdkController.contactUpdate(phoneNumber);
}

This links the current device/subscriber with the logged-in user.


Open Notification Center

To open the SDK notification center:

await XpushSdkController.openNotifications();

Example:

IconButton(
  icon: const Icon(Icons.notifications),
  onPressed: () async {
    await XpushSdkController.openNotifications();
  },
)

Get Unread Notification Count

To get unread notification count:

final unreadCount = await XpushSdkController.getUnreadCount();

Example:

int unreadCount = 0;

Future<void> refreshUnreadCount() async {
  final count = await XpushSdkController.getUnreadCount();

  setState(() {
    unreadCount = count;
  });
}

Example badge UI:

Stack(
  children: [
    IconButton(
      icon: const Icon(Icons.notifications),
      onPressed: () async {
        await XpushSdkController.openNotifications();
        await refreshUnreadCount();
      },
    ),
    if (unreadCount > 0)
      Positioned(
        right: 6,
        top: 6,
        child: Container(
          padding: const EdgeInsets.all(4),
          decoration: const BoxDecoration(
            color: Colors.red,
            shape: BoxShape.circle,
          ),
          child: Text(
            unreadCount.toString(),
            style: const TextStyle(
              color: Colors.white,
              fontSize: 10,
            ),
          ),
        ),
      ),
  ],
)

App Lifecycle Handling

The host Flutter app should notify the SDK when the app enters foreground or background.

Example:

class XBankApp extends StatefulWidget {
  const XBankApp({super.key});

  @override
  State<XBankApp> createState() => _XBankAppState();
}

class _XBankAppState extends State<XBankApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();

    WidgetsBinding.instance.addObserver(this);

    Future.microtask(() async {
      await requestNotificationPermission();
      await XpushSdkController.initializeSdk();
      await XpushSdkController.onForeground();
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      XpushSdkController.onForeground();
    } else if (state == AppLifecycleState.paused ||
        state == AppLifecycleState.inactive ||
        state == AppLifecycleState.detached) {
      XpushSdkController.onBackground();
    }
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

Deep Linking Setup

The SDK sends deep link events from native Android/iOS to Flutter through this MethodChannel:

MethodChannel('xpush_flutter_deeplink')

Native channel name:

let deepLinkChannel = FlutterMethodChannel(
    name: "xpush_flutter_deeplink",
    binaryMessenger: registrar.messenger()
)

Add the MethodChannel handler inside your main Flutter app widget.

Example:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:xpush_flutter/sdk/xpush_sdk_controller.dart';

import 'deep_link_router.dart';
import 'screens/splash_screen.dart';
import 'screens/login_screen.dart';
import 'screens/dashboard_screen.dart';
import 'screens/payment_screen.dart';

class XBankApp extends StatefulWidget {
  const XBankApp({super.key});

  static final GlobalKey<NavigatorState> navigatorKey =
      GlobalKey<NavigatorState>();

  @override
  State<XBankApp> createState() => _XBankAppState();
}

class _XBankAppState extends State<XBankApp> with WidgetsBindingObserver {
  static const MethodChannel _deepLinkChannel =
      MethodChannel('xpush_flutter_deeplink');

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

    WidgetsBinding.instance.addObserver(this);

    _deepLinkChannel.setMethodCallHandler((call) async {
      if (call.method == 'routeDeepLink') {
        final args = Map<String, dynamic>.from(call.arguments ?? {});

        final deepLink = args['deepLink']?.toString();
        final messageId = args['messageId']?.toString();

        if (deepLink != null && deepLink.isNotEmpty) {
          DeepLinkRouter.route(
            navigatorKey: XBankApp.navigatorKey,
            deepLink: deepLink,
            messageId: messageId,
          );
        }
      }
    });

    Future.microtask(() async {
      try {
        await requestNotificationPermission();
        await XpushSdkController.initializeSdk();
        await XpushSdkController.onForeground();
      } catch (e) {
        debugPrint("XPush SDK init failed: $e");
      }
    });
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      XpushSdkController.onForeground();
    } else if (state == AppLifecycleState.paused ||
        state == AppLifecycleState.inactive ||
        state == AppLifecycleState.detached) {
      XpushSdkController.onBackground();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: XBankApp.navigatorKey,
      debugShowCheckedModeBanner: false,
      home: const SplashScreen(),
      routes: {
        '/login': (_) => const LoginScreen(),
        '/dashboard': (_) => const DashboardScreen(),
        '/payment': (_) => const PaymentScreen(),
      },
    );
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
}

DeepLinkRouter Example

Create:

lib/deep_link_router.dart

Add:

import 'package:flutter/material.dart';

class DeepLinkRouter {
  static void route({
    required GlobalKey<NavigatorState> navigatorKey,
    required String deepLink,
    String? messageId,
  }) {
    final uri = Uri.tryParse(deepLink);

    if (uri == null) return;

    if (uri.scheme == 'xpush' && uri.host == 'pay') {
      final paymentId = uri.queryParameters['id'];

      navigatorKey.currentState?.pushNamed(
        '/payment',
        arguments: {
          'paymentId': paymentId,
          'messageId': messageId,
        },
      );

      return;
    }

    if (uri.scheme == 'xpush' && uri.host == 'notifications') {
      navigatorKey.currentState?.pushNamed('/dashboard');
      return;
    }
  }
}

Example deep link:

xpush://pay?id=12

This navigates to:

/payment

with arguments:

{
  "paymentId": "12",
  "messageId": "MESSAGE_ID"
}

Logout

On logout, call:

await XpushSdkController.logout();

Example:

Future<void> logout() async {
  await XpushSdkController.logout();

  Navigator.pushReplacementNamed(context, '/login');
}

This clears the current user session and prevents notifications from being shown for the logged-out user.


Notification Tap Handling

Android notification tap handling is managed by the SDK.

The SDK handles:

  • App foreground state
  • App background state
  • App terminated state
  • Notification body tap
  • Notification action button tap
  • Deep link payload
  • Dialog fallback when no valid deep link exists

If a valid deep link is received, it is sent to Flutter through:

MethodChannel('xpush_flutter_deeplink')

If no valid deep link exists, the SDK opens the notification dialog.


Required SDK Methods

Method Description
initializeSdk() Initializes XPush SDK
contactUpdate(phoneNumber) Updates logged-in user/contact
openNotifications() Opens notification center
getUnreadCount() Returns unread notification count
onForeground() Sets app state as foreground
onBackground() Sets app state as background
logout() Clears user/session data
restartSocket() Restarts socket if required
handlePushTap() Handles Android notification tap

App Start

await requestNotificationPermission();
await XpushSdkController.initializeSdk();
await XpushSdkController.onForeground();

User Login

await XpushSdkController.contactUpdate(phoneNumber);

Open Notification Center

await XpushSdkController.openNotifications();

Refresh Unread Count

final count = await XpushSdkController.getUnreadCount();

App Resume

await XpushSdkController.onForeground();

App Background

await XpushSdkController.onBackground();

Logout

await XpushSdkController.logout();

Troubleshooting

Push notification is not received on Android 13+

Check that this permission exists:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

Also make sure runtime notification permission is requested and granted.

await Permission.notification.request();

Notification tap opens app but does not route

Check that MainActivity has:

android:launchMode="singleTop"

Also check that the Flutter app has registered:

MethodChannel('xpush_flutter_deeplink')

Make sure the app has a valid navigatorKey:

static final GlobalKey<NavigatorState> navigatorKey =
    GlobalKey<NavigatorState>();

And pass it to MaterialApp:

MaterialApp(
  navigatorKey: XBankApp.navigatorKey,
)

Unread count is always zero

Make sure contactUpdate(phoneNumber) is called after login:

await XpushSdkController.contactUpdate(phoneNumber);

Also refresh unread count when app resumes:

await XpushSdkController.getUnreadCount();

In-app notification appears on login screen after logout

Make sure logout is called:

await XpushSdkController.logout();

Also clear the local login session from the host app.


Package Publishing Checklist

Before publishing to pub.dev, make sure the package root contains:

README.md
CHANGELOG.md
LICENSE
pubspec.yaml
lib/
android/
ios/
example/

Run:

flutter pub get
dart pub publish --dry-run

If dry run succeeds:

dart pub publish

License

This package is distributed under the license mentioned in the LICENSE file.