Flutter Integration Guide

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

Introduction

🚧

Notice

This documentation is for v1.0.34 of the Pngme SDK. Please check back for the v2.x release (ETA mid-Fed 2022).

Install dependencies

Update Gradle

In Flutter, go to /android/build.gradle.
Add this line:

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

In the same file, 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/build.gradle.

Add the following lines to dependencies:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    // Add from here
    implementation ('com.github.pngme:android-sdk:v1.0.34')
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation("androidx.multidex:multidex:2.0.1")
    // to here
}

Add the following line to android:

android {
    defaultConfig {
        multiDexEnabled = true  
    }
}

Build the Flutter Wrapper

Create a Loading Screen

Add a new file android/app/src/main/res/drawable/ic_pngme_logo.xml, with the following code:

<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. Add a new file o do this, add a new file android/app/src/main/res/layout/view_pngme_helper.xml.

πŸ“˜

Notice

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@color/pngme_sdk_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>

Create the wrapper

Create a new Kotlin file named PngmeSDKHelper.kt in your project root. This is usually at the root of your applications named path, e.g. android/app/src/main/kotlin/com/example/my_app. The PngmeSDKHelper.kt file will be in the same directory as your MainActivity.kt.

🚧

Caution

Change com.example.my_app to your app's correct path structure

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

package com.example.my_app

// 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
    }

}

Add wrapper to manifest

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

🚧

Caution

As above, change com.example.my_app to your app's correct path structure

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

    
    <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, we use Flutter channels: https://flutter.dev/docs/development/platform-integration/platform-channels

Edit your main activity to look like the code below:

🚧

Caution

As above, change com.example.my_app to your app's correct path structure

package com.example.my_app

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 a client will where you 'listen' and wait for calls. Then a method is defined and named OpenSDK.

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.

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 = 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