Flutter Integration Guide

Pngme's Data SDK is used to fetch user authenticated data from an Android
device using the Flutter development framework.

Introduction

Install the Native Android SDK

In Flutter, go to /Android folder, then open build.gradle and add maven into repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' } // <-- add this line
    }
}

Upgrade Kotlin version on your project, go to /Android folder, then open build.gradle and update ext.kotlin_version to '1.4.32' if you are using an older version, if you are using a newer version please ignore this step

buildscript {
    ext.kotlin_version = '1.4.32' // <-- update version here
    repositories {
        google()
        jcenter()
    }

Now, go to Android/app and open build.gradle and change Y.X.Z for our latest stable version.

**The current stable version is: Jitpack 1.0.34

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.2.0'
    // Add from here
    implementation ('com.github.pngme:android-sdk:vY.X.Z') {
        // Exclude is not needed in all projects so if your app does not compile fue do a 
        // missing dependency, please remove this exclusion
        exclude group: "org.jetbrains.kotlinx",
        module: "kotlinx-coroutines-core"
    }
    implementation 'androidx.appcompat:appcompat:1.2.0'
    // to here
}

Build the Flutter Wrapper

The first step is to create a loading screen in Flutter that initiates loading the SDK permission flow.

Create a Loading Screen

To add assets to the wrapper, enter onto the Android Drawable folder android/app/src/main/res/drawable and add a new ic_pngme_logo.xml and paste the following code into ic_pngme_logo.xml.

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="41dp"
    android:height="40dp"
    android:viewportWidth="41"
    android:viewportHeight="40">
  <group>
    <path
        android:pathData="M20.2092,40C31.0768,40 39.8867,31.0457 39.8867,20C39.8867,8.9543 31.0768,0 20.2092,0C9.3417,0 0.5318,8.9543 0.5318,20C0.5318,31.0457 9.3417,40 20.2092,40"
        android:strokeWidth="1"
        android:fillColor="#060403"
        android:fillType="nonZero"
        android:strokeColor="#00000000"/>
  </group>
  <path
      android:pathData="M12.1851,12.459C11.4373,12.459 10.8324,12.6913 10.3705,13.156L10.3705,12.6393L8.9189,12.6393L8.9189,21.5192L10.3705,21.5192L10.3705,18.1186C10.8324,18.5833 11.4373,18.8155 12.1851,18.8155C12.9108,18.8155 13.5414,18.5152 14.0766,17.9144C14.6117,17.3135 14.8793,16.5565 14.8793,15.6433C14.8793,14.7301 14.6099,13.9711 14.0711,13.3662C13.5322,12.7615 12.9035,12.459 12.1851,12.459ZM11.9322,17.4337C11.5216,17.4337 11.1587,17.2675 10.8434,16.9351C10.5281,16.6026 10.3705,16.172 10.3705,15.6433C10.3705,15.1146 10.5281,14.682 10.8434,14.3456C11.1587,14.0091 11.5216,13.8409 11.9322,13.8409C12.3793,13.8409 12.7477,14.0051 13.0373,14.3335C13.3269,14.6619 13.4717,15.0986 13.4717,15.6433C13.4717,16.188 13.3269,16.6226 13.0373,16.9471C12.7477,17.2715 12.3793,17.4337 11.9322,17.4337ZM19.1572,12.459C18.446,12.459 17.8705,12.6994 17.4307,13.18L17.4307,12.6393L15.979,12.6393L15.979,18.6353L17.4307,18.6353L17.4307,15.9797C17.4307,14.6019 17.8852,13.9129 18.7943,13.9129C19.1315,13.9129 19.401,14.0412 19.6026,14.2975C19.8041,14.5538 19.905,14.8943 19.905,15.3188L19.905,18.6353L21.3566,18.6353L21.3566,14.9824C21.3566,14.2374 21.155,13.6306 20.7518,13.162C20.3485,12.6933 19.817,12.459 19.1572,12.459ZM28.2738,12.6393L26.8221,12.6393L26.8221,13.156C26.3602,12.6913 25.7554,12.459 25.0076,12.459C24.2891,12.459 23.6604,12.7615 23.1216,13.3662C22.5827,13.9711 22.3133,14.7301 22.3133,15.6433C22.3133,16.5565 22.5809,17.3135 23.1161,17.9144C23.6513,18.5152 24.2818,18.8155 25.0076,18.8155C25.7554,18.8155 26.3602,18.5833 26.8221,18.1186L26.8221,18.5392C26.8221,19.0439 26.67,19.4284 26.3657,19.6927C26.0614,19.9571 25.6674,20.0892 25.1836,20.0892C24.839,20.0892 24.5604,20.0232 24.3478,19.891C24.1351,19.7588 23.9152,19.5366 23.6879,19.2241L22.5112,20.3175C23.0171,21.1988 23.9079,21.6393 25.1836,21.6393C26.0926,21.6393 26.8349,21.387 27.4105,20.8823C27.9859,20.3776 28.2738,19.6767 28.2738,18.7795L28.2738,12.6393ZM25.2606,17.4337C24.8133,17.4337 24.4449,17.2715 24.1553,16.9471C23.8657,16.6226 23.7209,16.188 23.7209,15.6433C23.7209,15.0986 23.8657,14.6619 24.1553,14.3335C24.4449,14.0051 24.8133,13.8409 25.2606,13.8409C25.6711,13.8409 26.034,14.0091 26.3493,14.3456C26.6645,14.682 26.8221,15.1146 26.8221,15.6433C26.8221,16.172 26.6645,16.6026 26.3493,16.9351C26.034,17.2675 25.6711,17.4337 25.2606,17.4337Z"
      android:strokeWidth="1"
      android:fillColor="#FFFFFE"
      android:fillType="nonZero"
      android:strokeColor="#00000000"/>
  <path
      android:pathData="M22.4923,23.6066C21.614,23.6066 20.9249,23.9115 20.425,24.5214C20.0011,23.9115 19.3801,23.6066 18.5623,23.6066C17.858,23.6066 17.2939,23.8148 16.8698,24.2313L16.8698,23.774L15.3705,23.774L15.3705,29.3408L16.8698,29.3408L16.8698,26.6633C16.8698,26.0907 16.9835,25.663 17.2106,25.3804C17.4378,25.0978 17.7558,24.9565 18.1647,24.9565C18.4828,24.9565 18.7478,25.0661 18.9598,25.2856C19.1719,25.505 19.2779,25.7969 19.2779,26.1613L19.2779,29.3408L20.7772,29.3408L20.7772,26.6633C20.7772,26.0981 20.8946,25.6723 21.1293,25.386C21.3641,25.0996 21.6859,24.9565 22.0948,24.9565C22.4128,24.9565 22.6778,25.0661 22.8899,25.2856C23.1019,25.505 23.2079,25.7969 23.2079,26.1613L23.2079,29.3408L24.6959,29.3408L24.6959,25.8601C24.6959,25.1982 24.4801,24.6571 24.0485,24.2369C23.6168,23.8167 23.0981,23.6066 22.4923,23.6066ZM31.4996,26.4514C31.4692,25.6184 31.1758,24.936 30.6193,24.4042C30.0627,23.8724 29.3982,23.6066 28.6258,23.6066C27.8004,23.6066 27.1057,23.8836 26.5416,24.4377C25.9774,24.9918 25.6953,25.7002 25.6953,26.5629C25.6953,27.4257 25.9755,28.1322 26.5359,28.6826C27.0962,29.233 27.7853,29.5082 28.6031,29.5082C29.2392,29.5082 29.7957,29.3631 30.2729,29.0731C30.7499,28.783 31.1096,28.3554 31.3519,27.7901L30.0116,27.5112C29.7163,28.0021 29.2467,28.2475 28.6031,28.2475C28.2548,28.2475 27.95,28.1304 27.6887,27.8961C27.4275,27.6618 27.259,27.3476 27.1833,26.9534L31.4996,26.9534L31.4996,26.4514ZM28.6258,24.8895C28.9439,24.8895 29.224,24.9751 29.4664,25.1462C29.7087,25.3172 29.8752,25.5738 29.9661,25.9159L27.2287,25.9159C27.312,25.5887 27.4881,25.3358 27.7569,25.1573C28.0257,24.9788 28.3153,24.8895 28.6258,24.8895Z"
      android:strokeWidth="1"
      android:fillColor="#FFFFFE"
      android:fillType="nonZero"
      android:strokeColor="#00000000"/>
  <path
      android:pathData="M10.2092,29.5082C10.9219,29.5082 11.4996,28.921 11.4996,28.1967C11.4996,27.4724 10.9219,26.8852 10.2092,26.8852C9.4966,26.8852 8.9189,27.4724 8.9189,28.1967C8.9189,28.921 9.4966,29.5082 10.2092,29.5082"
      android:strokeWidth="1"
      android:fillColor="#D92265"
      android:fillType="nonZero"
      android:strokeColor="#00000000"/>
</vector>

Create a Loading Screen Layout

Next, add the loading screen layout. Before the SDK loads, for approximately one full second the SDK loading screen will appear on either a black or white background (depending on whether the phone is in dark or light mode).
To do this, add a new file called view_pngme_helper.xml on android/app/src/main/res/layout

πŸ“˜

Information

Create a layout folder if needed, some Flutter projects may not have this by default.

android/app/src/main/res/layout
view_pngme_helper.xml

markup
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@color/background"
    android:orientation="horizontal"
    android:layout_gravity="center_vertical"
    android:gravity="center|center_vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/pngme_logo"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:src="@drawable/ic_pngme_logo"
        />

</LinearLayout>

Call the SDK and Pass Params

Now add some logic by opening Android Studio. Create a new Kotlin class with File -> New -> Kottlin File/Class name it as PngmeSDKHelper.kt click OK.

πŸ“˜

Information

The PngmeSDKHelper class will be in charge of receive parameters and open the PngmeSDK library

Copy and paste the code below into the PngmeSDKHelper.kt you have just created.

kotlin
package com.yourpackage_name

// copy from here ->
// here you should change for your app name
import com.example.my_app.MainActivity.Companion.RESULT_SDK_ON_CLOSE_DIALOG
import com.example.my_app.MainActivity.Companion.RESULT_SDK_ON_COMPLETE_OK
import com.example.my_app.MainActivity.Companion.RESULT_SDK_ON_ERROR

import com.pngme.sdk.library.PngmeSdk
import com.pngme.sdk.library.data.UserInfo

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class PngmeSDKHelper : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.view_pngme_helper)
        initSDK()
    }

    private fun initSDK() {
        PngmeSdk.init(this)

        val user = UserInfo()
        user.companyName = intent.getStringExtra("companyName")
        user.userFirstName = intent.getStringExtra("userFirstName")
        user.userLastName = intent.getStringExtra("userLastName")
        user.userPhone = intent.getStringExtra("userPhone")
        user.userEmail = intent.getStringExtra("userEmail")

        // optional
        val externalId = intent.getStringExtra("externalId");
        if (externalId != null) {
            user.externalId = externalId;
        }

        PngmeSdk.registerUser(user)
        PngmeSdk.start(onComplete = :: onComplete, onError = :: onError, onCloseDialog = :: closeDialog )
    }

    private fun closeDialog() {
        setResult(RESULT_SDK_ON_CLOSE_DIALOG)
        this.finish()
    }

    private fun onComplete() {
        setResult(RESULT_SDK_ON_COMPLETE_OK)
        this.finish()
    }

    private fun onError(errorMessage: String) {
        setResult(RESULT_SDK_ON_ERROR)
        //Optional, Use it to log errors, if needed
        // DO NOT FINISH FROM HERE, IT WILL CRASH IF YOU DO IT
    }

}

After creating PngmeSDKHelper.kt you must edit your AndroidManifest.xml to add this activity.

markup
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yourpackage_name">

    
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="my_app"
        ...>
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            ...>
            ........More boilerplate code
        </activity>

        <!-- Add code from here -->
         <activity android:name="com.example.my_app.PngmeSDKHelper" android:theme="@style/Theme.AppCompat.NoActionBar" />
        <!-- To here --> 
    
        ...
    </application>
</manifest>

Main Activity

Navigate to your Android MainActivity.kt and add some magical code to call the already created PngmeSDKHelper.kt

To expose a method to Flutter, utilise the Flutter channels: https://flutter.dev/docs/development/platform-integration/platform-channels

Edit your main activity to look like the code below:

MainActivity.kt
package com.yourpackage_name

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine


// imports that you need to add
import androidx.annotation.NonNull
import io.flutter.plugin.common.MethodChannel
import android.content.Intent
import com.pngme.sdk.library.PngmeSdk
import com.pngme.sdk.library.common.Environment
import com.pngme.sdk.library.data.KYCDataInfo
import com.pngme.sdk.library.data.LoanInfo
import com.pngme.sdk.library.data.UserInfo
// end of imports

class MainActivity: FlutterActivity() {
  private val CHANNEL = "com.flutter.pngme/sdk"
  private var openSDKResult: MethodChannel.Result? = null


  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
  
// < -------- Copy and Paste code from here -------- >
    super.onActivityResult(requestCode, resultCode, data)
    if (requestCode == REQUEST_START_SMS_SDK) {

        when (resultCode) {
          RESULT_SDK_ON_COMPLETE_OK -> openSDKResult?.success(SUCCESS)
          RESULT_SDK_ON_CLOSE_DIALOG -> openSDKResult?.success(SDK_WAS_DISMISSED)
          RESULT_SDK_ON_ERROR -> openSDKResult?.error(E_ON_SDK, E_ON_SDK, E_ON_SDK)
        }
      }
  }

  override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
      call, result ->
      if (call.method == "OpenSDK") {

//      Set environment variable
        val clientKey : String? = call.argument("clientKey")
        if (clientKey != null && clientKey.isNotBlank()) {
          PngmeSdk.setEnvironment(Environment.Prod, clientKey, context )
        }

        if (PngmeSdk.needInitOfSDK(this)) {
//          Get params from Flutter call
          val companyName : String? = call.argument("companyName")
          val userFirstName : String? = call.argument("userFirstName")
          val userLastName : String? = call.argument("userLastName")
          val userPhone : String? = call.argument("userPhone")
          val userEmail : String? = call.argument("userEmail")
          val externalId : String? = call.argument("externalId")

//          Add params to send to PngmeSDKHelper
          val intent = Intent(context, PngmeSDKHelper::class.java)
          intent.putExtra("companyName", companyName)
          intent.putExtra("userFirstName", userFirstName)
          intent.putExtra("userLastName", userLastName)
          intent.putExtra("userPhone", userPhone)
          intent.putExtra("userEmail", userEmail)
          intent.putExtra("externalId", externalId)
          openSDKResult = result
//          Launch the activity
          activity.startActivityForResult(intent, REQUEST_START_SMS_SDK)
        } else {
          result.success(SUCCESS_ALREADY_SENDING_SMS)
        }
//    Optional methods
      } else if (call.method == "updateUser") {
        val user = UserInfo()
        user.companyName = call.argument("companyName")
        user.userFirstName = call.argument("userFirstName")
        user.userLastName = call.argument("userLastName")
        user.userPhone = call.argument("userPhone")
        user.userEmail = call.argument("userEmail")
        user.externalId = call.argument("externalId")
        result.success(PngmeSdk.updateUser(user, context))
      } else if (call.method == "hasPermission") {
        result.success(PngmeSdk.hasPermission(context))
      }
//    End of optional methods
      else {
        result.notImplemented()
      }
    }
  }

  companion object {

    const val REQUEST_START_SMS_SDK = 11
    private const val E_ACTIVITY_DOES_NOT_EXIST = "E_ACTIVITY_DOES_NOT_EXIST"
    private const val E_ON_SDK = "E_ON_SDK"
    private const val SUCCESS_ALREADY_SENDING_SMS = "SUCCESS_PNGME_IS_ALREADY_SENDING_SMS"
    private const val SUCCESS = "SUCCESS"
    private const val SDK_WAS_DISMISSED = "SDK_WAS_DISMISSED"

    const val RESULT_SDK_ON_COMPLETE_OK = 3
    const val RESULT_SDK_ON_CLOSE_DIALOG = 4
    const val RESULT_SDK_ON_ERROR = 5

  }
// < -------- Copy and Paste code to here -------- >
}

In the above code, a channel name is defined by where you 'listen' and wait for calls. Then a method is defined and named OpenSDK.

When OpenSDK method is called from Flutter, check if a session was initiated by calling needInitOfSDK.

PngmeSdk.needInitOfSDK(this)

This function returns a boolean, which is used to check if the SDK should be opened. Remember that the Pngme SDK asks for user permissions once and then (so long as permissions continue to be granted) sends SMS silently in the background. There is no need to open the Pngme SDK again unless a user has disabled SMS permissions.

If PngmeSdk.needInitOfSDK(this) returns true, pass the required params from the Flutter call to PngmeSDKHelper.kt

Finally, the activity is launched with the following method:

context.startActivity(intent)

Calling a Method from Flutter

We have created a "com.flutter.pngme/sdk" channel and defined "OpenSDK" function in MainActivity.kt, now let's call it from Flutter.

Within the Flutter file, add the following import:

import 'package:flutter/services.dart';

Now you are able to define the channel:

static const platform = const MethodChannel('com.flutter.pngme/sdk');

void openSDK() async {
    String value;
    try {
      value = await platform.invokeMethod("OpenSDK", <String, dynamic>{
        'companyName': 'Your company name',
        'clientKey': 'Your Client key',
        // Data from clients
        'userFirstName': 'Name',
        'userLastName': 'LastName',
        'userPhone': '878792132',
        'userEmail': '[email protected]',
      });
      print(value);
    } catch (e) {
      print(e);
    }
  }

πŸ“˜

Information

Note: Parameter, channel and method names are defined as Magic strings for documentation purposes. Another option is to move it to a file with constant variables.

Run your app to see the Pngme permission screen when the OpenSDK method is called.

Parameter Descriptions

ParamRequiredTypeDescription
companyNameYesStringUsed to show your company name on components
clientKeyYesStringOn this param you should pass your client key provided by Pngme team. For security reasons avoid to hardcode this key on your code, we highly recommend to use it from your .env file
phoneNumberYesStringcountry code + phone number string. Ej: for Ghana (country code +233) phone number 03X XXX XXXX you should pass '23303X XXX XXXX' Warning: Pngme assumes that this data is verified by your app. If email or phoneNumber are not verified please let support team know.
firstNameYesStringUser first name
lastNameYesStringUser last name
emailYesStringUser Email
externalIdYesStringYou can pass your uuid in this field, this can be useful to identify your users last when obtaining processed data from our servers.
isKycVerifiedYesBooleanOn this param you can let pngme know if your user was KYC verified or not

Did this page help you?