Appgain SDK Plugin

Appgain SDK

This is a Flutter plugin that implemented Appgain SDK.

Appgain.io helps mobile apps grow with deep links that power referral systems, sharing links and invites with full attribution and analytics.

Table of contents

To integrate the Appgain SDK in your flutter app, you have to use Appgain.io pub dev For a flutter application integrated with Appgain SDK for ios and android, please refer to our Appgain Flutter TestApp

Getting Started

Required For Setup

Run this command: With Flutter:

$ flutter pub add appgain_sdk

This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get):

dependencies:
appgain_sdk: ^0.0.10

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

  • Import it

Now in your Dart code, you can use:

import 'package:appgain_sdk/appgain_sdk.dart';

Installation

  • add those lines into **your App Entry Point** to initialize Appgain SDK
///required appId => String
///required apiKey => String
Appgain().initAppgainSDK(appId : 'YOUR APP ID',apiKey:'YOUR API KEY');

Deferred Deep Linking

After a new user has installed your app, our SDK will detect if the app was installed from a smart
deep link or not, if it's then our SDK will automatically route their flow to marketing campaign
desired location in the app (not just to the default home screen).

To achieve that, appgain.io SDK must be installed in the app, and the matching process must be
initiated

Appgain().matchLink();

The following data are returned on matching success :

Returned Field Description
smart_link_url the URL of smart deep link used to open or install the App
smart_link_id the Id of smart deep link used to open or install the App
smart_link_name the Id of smart deep link used to open or install the App
match_type how the device identification done , it could be basic(Digital fingerprinting) or advertising_id
extra_data array of more data
extra_data.userId user Id that was appended to smart link url on opening it phase
params array of all parameters appended to smart link url , like SDL URL?utm_source=fb
smart_link_primary primary redirection action


You can return data by this code If you send params in your match deep link for example => https://app.appgain.io/s/XCplh?coupon=100 you can get coupon from params

Appgain().matchLink().then((result) {
      if (result != null) {
          result['extra_data'];
          result['extra_data']['params'];
          result['extra_data']['userId'];
          result['smart_link_primary'];
          result['smart_link_url'];
          result['smart_link_id'])
      }
    });

Marketing Automation

without Personalization

// required triggerPointName
await Appgain().fireAutomator(triggerPoint: 'YOUR TRIGGER POINT NAME');

with Personalization

  • If you need personalization add data in map
  • you can add your key and value that you want {key : value}
// required triggerPoint
var personalizationData = {
        'name': 'Jone',
        'cartName': 'Sports' ,
        'key', 'value'};
await Appgain().fireAutomatorWithPersonalization(
            triggerPoint: 'YOUR TRIGGER POINT NAME'  ,
            personalizationMap: personalizationData);

Revenue Tracking

you can add new Purchase Transactions object by using the following snippet :

/// required productName => String
/// required amount => double
/// required currency => String
await Appgain().addPurchase(
        productName: 'productName',
        amount: 100,
        currency: 'USD');

Custom Events Tracking

at your app whenever you want to log AppEvent, add the following snippet :

// required type
// optional action
// optional extras map
await Appgain().logEvent(
        type: 'google',
        extras: {
            'YOUR KEY 1': 'YOUR VALUE 1',
            'YOUR KEY 2': 'YOUR VALUE 2',},
        action: 'registration');

Set Custom User Attributes

at your app whenever you want to Set Custom User Attributes , add the following snippet :

var updatedData = {
    'userEmail': 'userEmail',
    'phone': 'phone',
    'updatedField1': 'value',
    'updatedField2': 'value'};
await Appgain().updateUser(data: updatedData);

Getting Appgain userId

// Getting userID
// Output  :  callback with userID
Appgain().getUserId();

Notification Channels

This Tutorial aims to teach the user how to add user Notification and messaging preferences in
channels such as SMS and Email

 //MARK: enableReceiveNotification
 //type (String) one of theses values ("appPush","SMS","email")
 await Appgain().enableReciveNotification(type: 'Notification Type');

🔧 Android Setup

Before proceeding, please make sure you are using the latest version of the plugin.

Gradle setup

  • In Project-level build.gradle (/build.gradle):
  • in section allproject --> repositories --> add
    maven { url "http://sdk.appgain.io/repository/maven-releases/" }
  • in section dependencies add --> add
    classpath'com.google.gms:google-services:4.3.3'
  • App-level build.gradle (project/app/build.gradle):

    dependencies {
      implementation platform('com.google.firebase:firebase-bom:27.1.0')
      implementation 'com.google.firebase:firebase-messaging'
      annotationProcessor 'android.arch.lifecycle:compiler:1.1.1'
      implementation "androidx.lifecycle:lifecycle-runtime:2.2.0"
      implementation "androidx.lifecycle:lifecycle-process:2.2.0"
      implementation 'io.appgain.sdk:appgain-android:4.4.0'

      }
  • and if you are using a video player plugin add the following line
      implementation 'com.google.android.exoplayer:exoplayer:2.18.6'
  • and in the end of your file add the following line
       apply plugin: 'com.google.gms.google-services'
  • in defaultConfig Section add
    multiDexEnabled=true
    minSdkVersion 21
  • go to https://firebase.google.com and log in with a Google Account.
  • At Firebase Website, in the right corner click on GO TO CONSOLE and click on Add Project , then
    give your Project a name.
  • Click on the settings icon next to Project Overview and then click on Project Settings
  • Click on GENERAL > Add Firebase to your Android app icon then fill in fields with :
  • Android package name : you can find it's value inside application Id value in app/build.gradle
  • Debug signing certificate SHA- 1 ,you can get it from :
  • Android studio > Gradle menu in (right toolbar of android studio)
  • Click on app menu
  • Click on android menu
  • Click on android on signingReport task
  • Get SHA1 from run menu
  • Return to Firebase console click on REGISTER APP.
  • Download google-services.json file.
  • Add google-services.json file to app folder in your android project files
  • Follow Firebase instructions.

Push Notification setup

  • Open Firebase console, Go to project settings.

enter image description here

  • Go to Cloud Messaging tab and copy SenderID and Legacy server key.

enter image description here

  • Open Appgain Dashboard
  • Go to **Project Setting** > **Advanced Setting** > **Platforms**
  • Navigate to **Android push** tab
  • enter your **SenderID** and **Server key**

enter image description here

  • if you are using proguard add this proguard rule to your proguard-rules.pro , add
    keep class io.appgain.sdk.** {*;}
  • Create a new class that extends from AppgainPushReceiver class

Class Name => PushReceiver

import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import io.appgain.sdk.controller.AppgainPushReceiver;
import io.appgain.sdk.model.push.ReceiveStatus;

public class PushReceiver extends AppgainPushReceiver {
     @Override protected void onReceive(Context context, ReceiveStatus receiveStatus, Intent intent) {
     Log.e("appgain", "test notification");
      }
}
  • In your android application class, implement LifecycleObserver interface and add the following
    lines

Class Name => AppController

package com.example.appgain_io_example;

import android.content.Context;

import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDex;
import androidx.multidex.MultiDexApplication;

import io.appgain.sdk.controller.Appgain;
import io.appgain.sdk.model.User;
import io.appgain.sdk.util.Config;


/**
 * Created by developers@appgain.io on 12/28/2021. */
public class AppController extends MultiDexApplication implements LifecycleObserver {

  public static final boolean DEBUG_MODE =false;
    private static AppController mInstance;
    String TAG = "AppController";
    public static boolean DIALOG_CANCELLABLE = true;


    @Override
    public void onCreate() {
  super.onCreate();
        mInstance = this;
        ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
        Appgain.enableLog();
    }

  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
  void onAppBackgrounded() {
  Appgain.onAppBackgrounded();
    }

  @OnLifecycleEvent(Lifecycle.Event.ON_START)
  void onAppForegrounded() {
  Appgain.onAppForegrounded();
    }

  @Override
    protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
        MultiDex.install(this);
    }


  public static synchronized AppController getInstance() {
  return mInstance;
    }

}
  • Open manifest.xml file and add the following lines:
  • In "-application-" section add this line
     android:name=".AppController"
  • also add
      <receiver
           android:name=".PushReceiver"
          android:exported="false">
          <intent-filter>
             <action android:name="com.parse.push.intent.RECEIVE" />
             <action android:name="com.parse.push.intent.DELETE" />
             <action android:name="com.parse.push.intent.OPEN" />
         </intent-filter>
    </receiver>
    <service android:name="io.appgain.sdk.controller.AppgainMessagingService"
     android:exported="false">
         <intent-filter>
               <action android:name="com.google.firebase.MESSAGING_EVENT" />
         </intent-filter>
    </service>

🔧 iOS setup

General setup

1- Add appgain pod to your generated project from flutter.

pod 'Appgain'

Handling notifications

AppDelegate+Notifications+Extension.swift
//
//  AppDelegate+Extension.swift
//  PragueNow
//
import Foundation
import UIKit

import UserNotificationsUI
import UserNotifications
import Appgain

extension AppDelegate {



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

          print("DEVICE TOKEN = \(deviceToken)")
          print("Device token string ==> \(deviceToken.base64EncodedString())")
          Appgain.registerDevice(withToken: deviceToken)
          let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
          let token = tokenParts.joined()
          print("Device Token: \(token)")

      }


    func  registerForRemoteNotification(){
        if #available(iOS 10.0, *) {
            // For iOS 10 display notification (sent via APNS)
            UNUserNotificationCenter.current().delegate = self
            let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
            UNUserNotificationCenter.current().requestAuthorization(
                options: authOptions,
                completionHandler: {_, _ in })
        } else {
            let settings: UIUserNotificationSettings =
                UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            DispatchQueue.main.async {
                UIApplication.shared.registerUserNotificationSettings(settings)
            }
        }
        DispatchQueue.main.async {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }



    override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        UIApplication.shared.applicationIconBadgeNumber = 0

        if let url = response.notification.request.content.userInfo["url"] as? String{
            switch UIApplication.shared.applicationState {
            case .background, .inactive:
                    // background

                let deadlineTime = DispatchTime.now() + .seconds(😎
                DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
                    print("test")

                if  let urll = URL(string: url){

                    UIApplication.shared.open(urll) { (result) in
                        if result {
                           // The URL was delivered successfully!
                        }
                    }
                }
            }
                break
                case .active:
                    // foreground
                    if  let urll = URL(string: url){

                        UIApplication.shared.open(urll) { (result) in
                            if result {
                               // The URL was delivered successfully!
                            }
                        }
                    }

                    break
                default:
                    break
            }

        }


        Appgain.recordPushStatus(NotificationStatus.opened(), userInfo: response.notification.request.content.userInfo)

    }

    override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        UIApplication.shared.applicationIconBadgeNumber = UIApplication.shared.applicationIconBadgeNumber + 1
        completionHandler([.alert, .badge, .sound])
        Appgain.recordPushStatus(NotificationStatus.conversion(), userInfo: notification.request.content.userInfo)

    }

    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

        UIApplication.shared.applicationIconBadgeNumber = 0

        if let url = userInfo["url"] as? String{
            switch UIApplication.shared.applicationState {
            case .background, .inactive:
                    // background

                let deadlineTime = DispatchTime.now() + .seconds(😎
                DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
                    print("test")

                if  let urll = URL(string: url){

                    UIApplication.shared.open(urll) { (result) in
                        if result {
                           // The URL was delivered successfully!
                        }
                    }
                }
            }
                break
                case .active:
                    // foreground
                    if  let urll = URL(string: url){

                        UIApplication.shared.open(urll) { (result) in
                            if result {
                               // The URL was delivered successfully!
                            }
                        }
                    }

                    break
                default:
                    break
            }

        }
        Appgain.handlePush(userInfo, for: application)

    }


    override func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) {
        UIApplication.shared.applicationIconBadgeNumber = 0
        //silent notification for update content

        Appgain.handlePush(userInfo, for: application)
    }



}

3- How we start with this code

  • Inside AppDelegate.swift
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    //ios-all-supported-versions-requesting-notification-permissions
      registerForRemoteNotification()
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
  • Allow the application to access the network by adding this in the plist file.

<key>NSAppTransportSecurity</key>

<dict>

<key>NSAllowsArbitraryLoads</key>

<true/>

</dict>

Add Appgain Rich Notification

this integration is optional , if not done then Video,HTML,GIF Push will not work

  • Add Notification Service Extension : go to your xcodeproj in xcode ---> Add Target -->

Notification Service Extension >Next > Finish > Activate Scheme Content Appgain SDK

Go to your service target folder --> info.plist , and add below content

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
  <dict>
      <key>NSExtensionPointIdentifier</key>
      <string>com.apple.usernotifications.service</string>
      <key>NSExtensionPrincipalClass</key>
      <string>NotificationService</string>
      <key>UNNotificationExtensionCategory</key>
      <string>rich-apns</string>
  </dict>
</dict>
</plist>

also need to Go to your service target folder --> NotificationService , and add below content

//  NotificationService.swift
//  service

import UserNotifications
import Appgain_Rich

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)

      if let bestAttemptContent = bestAttemptContent {
          // Modify the notification content here...
          bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
          AppgainRich.didReceive(request, andNotificationContent: bestAttemptContent, withContentHandler: contentHandler)

          contentHandler(bestAttemptContent)
      }
  }

  override func serviceExtensionTimeWillExpire() {
      // Called just before the extension will be terminated by the system.
      // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
      if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
          contentHandler(bestAttemptContent)
      }
  }

}
  • Add Notification Content Extension: Add Target --> Notification Content Extension >Next > Finish > Activate Scheme Content

  • Configure category for your notification extension: Go to your Content target folder --> info.plist , and add below content

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>UNNotificationExtensionCategory</key>
            <array>
                <string>rich-apns</string>
            </array>
            <key>UNNotificationExtensionInitialContentSizeRatio</key>
            <real>1</real>
            <key>UNNotificationExtensionUserInteractionEnabled</key>
            <true/>
        </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.content-extension</string>
    </dict>
</dict>
</plist>
  • also need to modify MainInterface.storyboard change NotificationViewController window size to be height 300 and remove all outlet form it Go to your Content target folder --> MainInterFace , and add below content
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="19455" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="M4Y-Lb-cyx">
    <device id="retina6_1" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19454"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Notification View Controller-->
        <scene sceneID="cwh-vc-ff4">
            <objects>
                <viewController id="M4Y-Lb-cyx" userLabel="Notification View Controller" customClass="NotificationViewController" customModule="content" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" simulatedAppContext="notificationCenter" id="S3S-Oj-5AN">
                        <rect key="frame" x="0.0" y="0.0" width="320" height="300"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <viewLayoutGuide key="safeArea" id="2BE-c3-nQJ"/>
                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                    </view>
                    <extendedEdge key="edgesForExtendedLayout"/>
                    <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
                    <size key="freeformSize" width="320" height="300"/>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="vXp-U4-Rya" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="91" y="79"/>
        </scene>
    </scenes>
</document>

Go to your Content target folder --> NotificationViewController , and add below content

import UIKit
import UserNotifications
import UserNotificationsUI
import Appgain_Rich

class NotificationViewController: UIViewController, UNNotificationContentExtension {

  @IBOutlet var label: UILabel?

  override func viewDidLoad() {
      super.viewDidLoad()
      // Do any required interface initialization here.
  }

  func didReceive(_ notification: UNNotification) {
      AppgainRich.didReceive(notification, in: self)
  }

}

Rich Notification Setup

  • Install pods open you workspace pod file add the following to it
target ‘#your content target #’

pod 'Appgain-Rich'

end
target ‘#your service target #’

pod 'Appgain-Rich'

end

Apple IDFA Tracking Setup

ATTrackingManager.requestTrackingAuthorizationWithCompletionHandler: This function is advised on the first app launch to ensure the value is captured. The prompt only shows if the app is a fresh install and the user consent status is unknown. For the majority of applications, only enable tracking if the status is authorized on becoming active (new in iOS 14), as below:

  • Allow the application to access the IDFA by adding this in the plist file.
<key>NSUserTrackingUsageDescription</key>
<string>Privacy of our platform users is extremely important and we at Customer App are committed to protecting the same. Your personal information is collected  only to understand your journey within the platform and as a part of the search process.</string>
  • add following code in your AppDelegate file
import AppTrackingTransparency

@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
 func applicationDidBecomeActive(_ application: UIApplication) {
        if #available(iOS 14, *) {
            ATTrackingManager.requestTrackingAuthorization { status in
                switch status {
                    case .authorized:
                        print("enable tracking")
                    case .denied:
                        print("disable tracking")
                    default:
                        print("disable tracking")
                }
            }
        }
    }
}

Finally You Can Use Appgain SDKTestApp to Test Appgain Features Before Integrate Your App with Appgain