Migrating to Version 2.0+

Overview

To simplify your application logic and integration with the Knot Android SDK, Version 2.0 includes a number of breaking changes that may affect the way your integration works or behaves.

The new version includes a number of significant improvements:

  1. Enhanced speed and stability in loading merchant flows.
  2. More streamlined initialization of the SDK.
  3. Simplified event handling, more informative event messaging, and uniform naming conventions for easier debugging.
  4. Improved maintainability and foundations for new feature compatibility.

Breaking Changes

Configuring and opening the Knot SDK has changed significantly in iOS Version 1.0 and requires some refactoring in order to initialize the SDK with a session. Errors are now encapsulated in a KnotError object which provides an enumerated value to debug with.

Session Initialization

Changes

  • CardOnFileSwitcher.getInstance() and Configuration(environment, clientId, sessionId) are replaced with a more flexible KnotConfiguration.
  • The product type is now defined by Product enum values .card_switcher | .transaction_link.
  • The new interface allows configuration of additional properties such as useCategoriesuseSearch, and merchantIds.
  • The open action now requires a KnotConfiguration and an optional KnotEventDelegate which is now Knot.open(context, knotConfiguration, knotEventDelegate).

Before

CardOnFileSwitcher cardOnFileSwitcher = CardOnFileSwitcher.getInstance();
Configuration switcherConfig = new Configuration(environment, clientId, sessionId);

cardOnFileSwitcher.setMerchantIds(new int[]{44});
cardOnFileSwitcher.setUseCategories(true);
cardOnFileSwitcher.setUseSearch(true);

Options options = new Options();
String[] domainUrls = {"https://domain1.com", "https://domain2.com", ....};
options.setDomainUrls(domainUrls);

cardOnFileSwitcher.init(context, switcherConfig, options, onSessionEventListener);

cardOnFileSwitcher.openCardOnFileSwitcher("Onboarding"); 
val cardOnFileSwitcher = CardOnFileSwitcher.getInstance()
val switcherConfig = Configuration(environment, clientId, sessionId)

cardOnFileSwitcher.setMerchantIds(intArrayOf(44))
cardOnFileSwitcher.setUseCategories(true)
cardOnFileSwitcher.setUseSearch(true)

val options = Options()
val domainUrls = arrayOf("https://domain1.com", "https://domain2.com")
options.setDomainUrls(domainUrls)

cardOnFileSwitcher.init(context, switcherConfig, options, onSessionEventListener)

cardOnFileSwitcher.openCardOnFileSwitcher("Onboarding")

After

KnotConfiguration config = new KnotConfiguration(
    "session_12345",                      // sessionId
    "client_67890",                       // clientId
    Environment.production,               // environment
    Knot.Product.card_switcher,           // product
    new int[]{101, 102, 103},             // merchantIds
    true,                                 // useCategories
    true,                                 // useSearch
    new String[]{"https://example.com"},  // domainUrls
    "onboarding"                          // entryPoint
);

KnotEventDelegate knotEventDelegate = new KnotEventDelegate() {
    @Override
    public void onSuccess(String merchant) {
        // Handle successful operation
    }

    @Override
    public void onError(KnotError knotError) {
        // Handle error operation using knotError
    }

    @Override
    public void onExit() {
        // Handle SDK exit event
    }

    @Override
    public void onEvent(KnotEvent knotEvent) {
        // Extract and process event details from KnotEvent
    }
};

Knot.open(context, config, knotEventDelegate);
val config = KnotConfiguration(
    "session_12345",                      // sessionId
    "client_67890",                       // clientId
    Environment.PRODUCTION,               // environment
    Knot.Product.card_switcher,           // product
    intArrayOf(101, 102, 103),            // merchantIds
    true,                                 // useCategories
    true,                                 // useSearch
    arrayOf("https://example.com"),       // domainUrls
    "onboarding"                          // entryPoint
)

KnotEventDelegate knotEventDelegate = new KnotEventDelegate() {
    @Override
    public void onSuccess(String merchant) {
        // Handle successful operation
    }

    @Override
    public void onError(KnotError knotError) {
        // Handle error operation using knotError
    }

    @Override
    public void onExit() {
        // Handle SDK exit event
    }

    @Override
    public void onEvent(KnotEvent knotEvent) {
        // Extract and process event details from KnotEvent
    }
};

Knot.open(context, config, knotEventDelegate)

Event Handling

Changes

  • Event handling is now managed through KnotEventDelegate instead of closures.
  • Events like onSuccessonError, and onExit are now explicit methods inside a delegate.
  • The onEvent method introduces the KnotEvent object to better handle Knot emitted events.
  • The KnotError type provides improved error descriptions.
  • The sendCard parameter is deprecated and its functionality incorporated into the metaData dictionary within KnotEvent when KnotEvent.event equals AUTHENTICATED.

Before

cardOnFileSwitcher.setOnSessionEventListener(new OnSessionEventListener() {
    @Override
    public void onSuccess(String merchant) {
        Log.d("onSuccess", merchant);
    }

    @Override
    public void onError(String errorCode, String errorMessage) {
        Log.d("onError", errorCode + " " + errorMessage);
    }

    @Override
    public void onExit() {
        Log.d("onExit", "exit");
    }

    @Override
    public void onEvent(String eventName, String merchantName, String taskId) {
        Log.d("onEvent", eventName + " " + merchantName + " " + taskId);
    }
});
cardOnFileSwitcher.setOnSessionEventListener(object : OnSessionEventListener {
    override fun onSuccess(merchant: String) {
        Log.d("onSuccess", merchant)
    }

    override fun onError(errorCode: String, errorMessage: String) {
        Log.d("onError", "$errorCode $errorMessage")
    }

    override fun onExit() {
        Log.d("onExit", "exit")
    }

    override fun onEvent(eventName: String, merchantName: String, taskId: String) {
        Log.d("onEvent", "$eventName $merchantName $taskId")
    }
})

After

KnotEventDelegate knotEventDelegate = new KnotEventDelegate() {
    @Override
    public void onSuccess(String merchant) {
        // Handle successful operation
    }

    @Override
    public void onError(KnotError knotError) {
        // Handle error operation using knotError
    }

    @Override
    public void onExit() {
        // Handle SDK exit event
    }

    @Override
    public void onEvent(KnotEvent knotEvent) {
        // Extract and process event details from KnotEvent
    }
};
val eventDelegate = object : KnotEventDelegate {
    override fun onSuccess(merchant: String) {
        // Handle successful operation
    }

    override fun onError(knotError: KnotError) {
        // Handle error operation using knotError
    }

    override fun onExit() {
        // Handle SDK exit event
    }

    override fun onEvent(knotEvent: KnotEvent) {
        // Extract and process event details from KnotEvent
    }
}

sendCard

🚧

Note

Most apps do not use the explicit sendCard method, as it is rarely applicable to the integration with the Knot SDK.

The sendCard parameter has been deprecated and its functionality is incorporated into the metaData object within KnotEvent when the KnotEvent.event equals AUTHENTICATED. This change enhances flexibility by allowing additional contextual data to be included in events without requiring separate parameters. Previously, sendCard was accessed as a standalone value, but now developers can retrieve it from the metaData object in the event callback. This approach ensures better extensibility and consistency across different event types. To access the sendCard value, simply extract it from the event’s metaData object.

@Override
public void onEvent(KnotEvent knotEvent) {
    // Extract the metadata map from the KnotEvent
    Map<String, Object> metaData = knotEvent.getMetaData();
    
    // Check if the metadata contains a sendCard flag 
    if (knotEvent.getEvent().equalsIgnoreCase("AUTHENTICATED") && metaData != null && metaData.containsKey("sendCard")) {
        Boolean sendCard = (Boolean) metaData.get("sendCard");
        Log.d("KnotEvent", "sendCard: " + sendCard);
    } 
}
override fun onEvent(knotEvent: KnotEvent) {
    val metaData = knotEvent.metaData
    if (knotEvent.event.equals("AUTHENTICATED", ignoreCase = true) && metaData != null && metaData.containsKey("sendCard")) {
        val sendCard = metaData["sendCard"] as? Boolean
        Log.d("KnotEvent", "sendCard: $sendCard")
    }
}

Event Names

The SDK now maps raw event names to standardized event names for easier handling.

Event Name Prior to 2.02.0 Event Name
refresh session requestREFRESH_SESSION_REQUEST
merchant clickedMERCHANT_CLICKED
login startedLOGIN_STARTED
authenticatedAUTHENTICATED
otp requiredOTP_REQUIRED
security questions requiredSECURITY_QUESTIONS_REQUIRED
approval requiredAPPROVAL_REQUIRED

Error Handling

Error handling has been improved with more structured and meaningful error messages.

Changes

  • Errors are now encapsulated in the KnotError enum.
  • Each error has a human-readable description (errorDescription) and a unique error code (errorCode).
  • Improved clarity and consistency across error messages.

Before

@Override
public void onError(String errorCode, String errorMessage) {
  Log.d("onError", errorCode + " " + errorMessage);
}
override fun onError(error: String?, errorMessage: String?) {
  Log.d("onError", "$error $errorMessage")
}

After

public void onError(KnotError error) {
    switch (error) {
        case INVALID_SESSION:
            Log.e("KnotError", "Error: INVALID_SESSION - " + error.getErrorDescription());
            break;
        case EXPIRED_SESSION:
            Log.e("KnotError", "Error: EXPIRED_SESSION - " + error.getErrorDescription());
            break;
        case INVALID_CLIENT_ID:
            Log.e("KnotError", "Error: INVALID_CLIENT_ID - " + error.getErrorDescription());
            break;
        case INTERNAL_ERROR:
            Log.e("KnotError", "Error: INTERNAL_ERROR - " + error.getErrorDescription());
            break;
    }
}
fun onError(error: KnotError) {
    when (error) {
        KnotError.INVALID_SESSION -> Log.e("KnotError", "Error: INVALID_SESSION - ${error.errorDescription}")
        KnotError.EXPIRED_SESSION -> Log.e("KnotError", "Error: EXPIRED_SESSION - ${error.errorDescription}")
        KnotError.INVALID_CLIENT_ID -> Log.e("KnotError", "Error: INVALID_CLIENT_ID - ${error.errorDescription}")
        KnotError.INTERNAL_ERROR -> Log.e("KnotError", "Error: INTERNAL_ERROR - ${error.errorDescription}")
    }
}

Error Types

The Knot SDK provides predefined error cases for you to handle based on your own needs.

Error CaseDescription
INVALID_SESSIONThe session is invalid.
EXPIRED_SESSIONThe session has expired.
INVALID_CLIENT_IDThe client ID is invalid.
INTERNAL_ERRORAn internal error occurred.

Closing the SDK

🚧

Note

Most apps do not use the explicit close method, as it is infrequently applicable to the integration with the Knot SDK.

Changes

  • Closing the SDK is now statically accessed via Knot.close() as opposed to being bound to the session object.

Before

cardOnFileSwitcher.closeCardOnFileSwitcher();
cardOnFileSwitcher.closeCardOnFileSwitcher()

After

Knot.close();
Knot.close()