Flutter access sdk in depth explanation
Flutter access sdk in depth explanation
This article will guide you step by step on how you can integrate the spintly access sdk into your app. Spintly do provide a sample app and sample app source code on how the app can integrate with the spintly access sdk, if you havent got the app please contact Spintly for the same, as the sample app will help you understand things faster. For this SDK integation to happen you need to either do the type2 or type3 integration.
Once the client is done integrating the sdk in thier app, the app needs to be sent to spintly for review, this review is needed so that the sdk is properly used in the client app in accordance to spintly standard, Once spintly approves the app, the client can go ahead and release the app on playstore and appstore.
Please note you cannot integrate spintly access sdk standalone, it will always go hand in hand with the api
1 Changes required in the client app to support Spintly SDK
There are certain steps we need to follow at different stages of the app flow. Following are the locations where sdk-related changes need to be added.
1. During app initialization
2. After login
3. Mandatory checks
4. Getting a list of doors
5. Starting Bluetooth scan
6. Opening doors
7. On notification
8. Logout
2 SDK Details
Spintly integration requires the following SDKs to be used by the client app:
- OAuth SDK: This SDK is used for getting a Spintly access token in exchange for the user's client jwt token.
- Access control SDK: This SDK contains the actual access control functionality
3 High-Level Overview
Spintly uses Auth 2.0 Token Exchange, for more information please visit this site:
https://datatracker.ietf.org/doc/html/rfc8693
Here in the diagram you can see the JWT token is given to the OAuth SDK and in return, the OAuth SDK returns the access token. This access token is then passed to the Spintly access control SDK.
4 During App Initialization: OAuth SDK
Before using the SDK, the app developer needs to specify the clientId, providerId and environment. The clientId and providerId will be provided by the Spintly Support person to the app developer. The environment will be specified by the developer based on whether the developer is building the app for production or a sandbox environment. Different clientId and providerId will be provided for the production and sandbox environment.
import 'package:oauthsdk/spintly_oauth_manager.dart';
final String _clientId = "clientIdString";
final String _provider = "providerString";
final String _environment = production;
var _oauthManager;
_oauthManager = SpintlyOauthManager(_clientId, _provider, _environment);
Before using the SDK, the developer needs to specify the clientId , provider and environment. The clientId and provider id will be provided by the Spintly Support person to the developer. The environment will be specified by the developer based on whether the developer is building the app for production or a sandbox environment. Different clientId and providers will be provided for the production and sandbox environment.
5 During App Initialization: Access Control SDK
Just like the OAuth SDK on the previous page, the environment needs to be set for the access control SDK after its instance is initialized.
import 'package:smartaccesssdk/environment_manager.dart';
final String _environment = 'dev';
final _envManager = EnvironmentManager();
//set environment
_envManager.setEnvironment(_environment);
//get environment
await _envManager.getEnvironment();
6 After login: OAuth SDK
The user's client jwt token which was obtained after the user logged in to the app, needs to be passed to the OAuth SDK. The OAuth SDK in return will give back a Spintly access token.
Note: Here the user's client JWT token contains a unique Id that identifies a particular user, this unique Id must be added to Spintly using the Spintly backend APIs.
import 'package:oauthsdk/authorization_callback.dart';
import 'package:oauthsdk/authentication_details.dart';
var callback = AuthorizationCallback(
onSuccess: (session) {
print("onSuccess authorizationCallback");
String? accessToken = session.accessToken.token;
//To be passed to other Spintly services that make use of this token
},
getAuthenticationDetails: (continuation) {
// this is called when user is logged out or all tokens have been expired and
// need login parameters again from client.
// For Token Exchange grant type authorization:
print("getAuthenticationDetails");
continuation.authenticationDetails = AuthenticationDetails.createWithTokenExchangeGrantType(clientToken);
continuation.continueTask();
// If clientToken was not available when this function was called,
// hold on to the continuation reference and once clientToken is acquired,
// repeat the above steps.
// Or call getSession again and repeat the process
},
onFailure: (exception) {
print('getAccessToken onFailure $exception');
}
);
//to create a new session. It will generate new token on every call
_oauthManager.createSession(callback);
//to get an existing session if it exists.
//It will return existing token if still valid otherwise create new token
_oauthManager.getOrCreateSession(callback);
7 After login: Access Control SDK
The Spintly access token that is generated by the OAuth SDK is then passed to the Spintly access control SDK. After this step succeeds, functionalities of the access SDK can be used.
import 'package:smartaccesssdk/credential_manager.dart';
//Use access token to login to SDK and use Spintly services
await _credManager.logIn(accessToken);
print('signInUser onSuccess');
The Spintly access token that is generated by the OAuth SDK is then passed to the Spintly access control SDK. After this step the access control functionality of SDK can be used.
Note: The Spintly access token will have an expiry. Client is responsible to update Spintly access token on the event that it is expired. An event listener can be attached in the following way. This event is sent whenever an api call fails due to access token expiry.
import 'package:smartaccesssdk/credential_manager.dart';
var accessTokenExpiredListener;
//To add subscription
accessTokenExpiredListener ??= _credManager.accesTokenExpired().listen((event) {
print("Access token expired");
//Update the token now or at later time
});
//Remove the subscription
await accessTokenExpiredListener.cancel();
//Once new token is available
await _credManager.updateAccessToken(accessToken);
8 Getting list of access points
This snippet of the code explains how to get all the devices to which a particular user has permission to.
import 'package:smartaccesssdk/permission_manager.dart';
import 'package:smartaccesssdk/access_point.dart';
final _permissionManager = PermissionManager();
var accessPointsUpdatedListener;
List<AccessPoint>? accessPointList;
// This is a list of Access Points the user has access to.
accessPointList = await _permissionManager.getAccessPoints();
// To get update of when this list changes, attach an event listener in the following way
//To add subscription
accessPointsUpdatedListener ??= _permissionManager.accessPointsUpdated().listen((event) {
print("Access points updated");
//call _permissionManager.getAccessPoints again and refresh your view (if present)
//Update access point list with received access points
_permissionManager.getAccessPoints().then((value)
{
accessPointList=value;
//Refresh screen to re-populate access point list
}
);
});
//Remove subscription when component is unmounted to avoid memory leaks
await accessPointsUpdatedListener.cancel();
9 Starting the Bluetooth Scan
Once the user is logged in or once the user has opened the app and got the app in foreground, a check has to be made to use bluetooth based functionality i.e.
- For all Android versions, the bluetooth of the phone needs to be turned on for access to work via BLE.
- For Android version less than equal to 11: Check if bluetooth and location permissions are granted and prompt the user to turn on bluetooth and location. Here Nearby devices permission is not required.
- For Android version greater than equal to 12: Check if Nearby devices permission is granted and prompt the user to turn on bluetooth. Here the location permission and the tuning on of location is not required for access to work.
For android 6.0 and above till android 11.0, BLUETOOTH permission is required for bluetooth communication to work and the BLUETOOTH_ADMIN permission to scan for BLE devices and to be able to turn on the phone’s bluetooth.
Also, in these versions (android 6.0 and above till android 11.0), ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission is required and location (network mode) is to be turned on.
Along with that, for android 10.0 and above till android 11.0, the background location ACCESS_BACKGROUND_LOCATION permission is necessary for proximity/tap to access to work in the background.
For android 12.0 and above, the Nearby devices permission is required while the location permission or location turn on is not required. The Nearby devices permission in UI translates in code to BLUETOOTH_SCAN, BLUETOOTH_ADVERTISE and BLUETOOTH_CONNECT permissions, all of which are required. For the BLUETOOTH_SCAN permission a flag needs to assert that the scan is not for deriving physical location. See below how to mention it:
https://developer.android.com/develop/connectivity/bluetooth/bt-permissions#assert-never-for-location
This is a mandate required by android for scanning close by BLE devices. See more about requesting location permissions from the customer at runtime below:
https://developer.android.com/develop/sensors-and-location/location/permissions#request-location-access-runtime
Since BLUETOOTH, BLUETOOTH_ADMIN and the location related permissions are required only upto android 11.0, the maxSdkVersion needs to be set to 30 (android 11) for these permissions. See below how to use it:
https://developer.android.com/develop/connectivity/bluetooth/bt-permissions
Note: These permissions need to be requested from the user and granted along with turn on of bluetooth (and location in respective versions) before the next step of starting bluetooth scan of the access SDK. Since granting of these permissions requires user interactions it is left to the client app to implement it. However, for better understanding, below are examples of the dialogs
that should be presented to the customer to grant access in the respective scenarios. The client app can rely only on the system generated pop ups to get permissions from the user and to turn on the bluetooth and location. However, in-app pop ups with custom messages give the user more context on why the permissions and service turn on are required for access to work.
Bluetooth turn on in app prompt:

Bluetooth turn on system dialog:

Bluetooth Nearby Devices in app permission prompt:

Nearby Devices system permission dialog:

Location access permission in app prompt:

Location access permission system prompt:

Background location permission shown by app (for android 10 and 11):

Background location permission system settings (allow all the time should be selected below):

Location turn on in app prompt:

Location turn on system dialog:

The below snippet of code explains how to start the bluetooth scan.
Note: Without this step, the customer won't be able to access the doors to which they have access.
import 'package:smartaccesssdk/access_manager.dart';
final _accessManager = AccessManager();
await _accessManager.startBleScan();
Note: Along with starting of the scan each of these checks should be done each time the app comes in the foreground (not just when the app is opened) to account for the fact that the user might have turned off one of the required services or might have removed one of the permissions.
10 On Notifications
This snippet of the code explains the function that needs to be called whenever the access point permissions are updated in the backend (from the client backend to Spintly backend). When such permission changes happen, it’s the job of the client backend to alert the client app that permissions have changed. When the app receives the notification, it needs to call the poll data function. This will cause the SDK to receive the new permissions from the Spintly backend.
import 'package:smartaccesssdk/cloud_sync_manager.dart';
final _cloudSyncManager = CloudSyncManager();
//Check for change in access point permissions/access point list using SDK cloud sync manager
await _cloudSyncManager.pollData();
print("polled access points");
11 Opening the Doors
Spintly provides 3 options to open the doors:
Click to Access: This works well for use cases of listing all the doors in the app UI such that the customer can see them, and click on a specific door to unlock it
Tap to Access: Here user just needs to tap the phone on the unit and the door will open
Proximity Access: Here the phone needs to be within the proximity of the device
The tap to access and proximity access can be enabled or disabled in the Spintly backend.
Click to Access
If he wants to list the doors and wants to open the door by click to access, then he needs to use the following snippet.
import 'package:smartaccesssdk/access_manager.dart';
import 'package:smartaccesssdk/unlocked.dart';
import 'package:smartaccesssdk/access_point.dart';
final _accessManager = AccessManager();
//Unlock the tapped access point using SDK access manager
//accessPoint id indicates the id of access point which is same as what was passed to the function
Unlocked unlocked = await _accessManager.bleUnlockAccessPoint(accessPoint.id!);
print("unlocked: ${unlocked.accessPointId}");
//Unlock the closest access point using SDK access manager
//accessPoint id indicates the id of access point which is same as what was passed to the function
Unlocked unlocked = await _accessManager.bleUnlockClosestAccessPoint();
print("unlocked: ${unlocked.accessPointId}");
// Only one unlock at a time is supported as of this moment so please avoid calling this function again without the previous one returning success/error.
12 Mobile NFC access
The Spintly access SDK supports NFC based access for mobile phones which have NFC hardware. For NFC access to work, it is the responsibility of the client app to show the user prompts to turn on the mobile’s NFC if the mobile has NFC hardware. If these requirements are fulfilled then NFC based tap to access will work if access SDK login is done. However, NFC based access only supports tap to access and will not support other modes via pure NFC.
Tap to access can then be used only via NFC and turning off bluetooth. However, it is advised to suggest the users to use both NFC and bluetooth for seamless access.
For better understanding, below are the dialogs that should be presented to the customer to grant access to phone NFC.
NFC turn on in app prompt:

NFC system settings:

Note: Each time the app comes into the foreground, it is the responsibility of the client app to show a prompt to the user if the NFC is not turned on which has to be independent of and parallel to BLE pop ups.
15 On logout
When the user logs out of the app, it is also important that the app first stops the BLE scan and then logs out from both the SDK i.e. the OAuth SDK and Access Control SDK. The following snippet of code does the job.
//Stop bluetooth scanning before log out
await _accessManager.stopBleScan();
//Log out from the SDK credential manager
await _credManager.logOut(); //Logout existing credential if present
//Clears the OAuth session of SDk
await _oauthManager.clearSession();