(iOS) Slate+ Expand Samples with Riiiver
This is a sample app with which you can learn how to integrate the Riiiver SDK into your companion app for Slate+.
With this app you'll find:
- Expansion of UX with Slate+ by utilizing the Riiiver SDK.
- How the Riiiver functions, piece(s) and iiidea(s), work.
- User Interface provided by our SDK, e.g. login screen, iiidea list screen.
How does this sample demonstrate?
- (1) Scan BLE devices nearby.
(2) Connect to Slate+ evaluation board(EVB).
- Discover the service and characteristics provided as
Riiiver_Demo
by QCLI. - If it doesn't find the service and characteristics, it doesn't move to forward pages.
- Discover the service and characteristics provided as
(3) Login to Riiiver.
- (4) Complete iiidea settings.
- (5) Assign the Riiiver functions that'll be triggered by pushing the button on the EVB.
- Push the button and find the result that appears on the display of the EVB.
Screenshots
Out of scope for this Sample App
- This app is not a template. Integrate our SDK to your app as you like, e.g. you can choose a suitable architecture for your app.
- This app does not implement data storage that keeps the app state even after killing and relaunching the app.
- This app does not implement a complete paring of BLE connection. The Riiiver functions only work when the connection is alive between Slate+ and your mobile phone.
Prepare for development using Riiiver SDK
Preparing for development
What this section describes:
- Sign up as a developer
- [resource file]
erconfiguration.json
.
To develop with Riiiver SDK, sign up as a developer and register information about your service and company. Please visit our developer site and sign up by following the instructions.
The required information is as follows:
- Your company information.
- Your application information.
- Your device information.
Riiiver SDK reads vendor and app information by referring to the erconfiguration.json
file. The VendorId
and AppId
should be the IDs that Riiiver has given you when you registered your vendor and app information.
The prepared erconfiguration.json
file must be located in the root hierarchy of the bundle. In this project, the file is placed in the directory Infra/RiiiverSDK/SupportingFiles
, but Xcode's Run Script copies the file to the root of the bundle when the build is executed.
Install the Riiiver SDK
What this section describes:
- How to import the
RiiiverSDK.framework
into your project
In this sample project, we use CocoaPods to install Riiiver SDK. We have provided a Podfile in the root directory of the project. Please install Riiiver SDK by yourself with the below command.
$ pod install
After the installation is completed, you will find the RIiiver SDK and external libraries used in this project under the Pods/
directory, and a {project_name}.xcworkspace
file in the root directory. When building the application, please open and build the .xcworkspace file.
- Note
- The `Pods/` directory and Xcode workspace are not subject to git management.
Using Riiiver SDK features
Initialize the Riiiver SDK
What this section describes:
- [method]
initialize(deviceIdList:)
- [method]
addListener(listener:)
- [protocol]
ERSdkListener
Be sure to execute the initialize(deviceIdList:)
method before using any of the methods provided in the Riiiver SDK.
In this sample app, we have prepared the initialization method in the class RiiiverSDKClient
for calling methods of Riiiver SDK, and that method is called every time before executing a method of Riiiver SDK. The implementation is designed to return immediately if it has already been initialized, so it does not matter how many times it is called. The argument is passed the ID of the device supported by the application as an Array of String. If you have not registered your device information, please visit our developer site and register it first.
Also, since the ERSdkListener
protocol is provided to delegate processing to the app side when an event occurs in the Riiiver SDK, we use the addListener(listener:)
method to set the delegate after initialization is complete. This method is also implemented to do nothing if the delegate instance is the same, so it does not matter how many times it is called.
/* qcc5100.sample/Infra/RiiiverSDK/RiiiverSDKClient.swift */private func initialize() async throws -> Void { let error = await RiiiverSdkManager.initialize(deviceIdList: RiiiverConstants.deviceIds) if let error { print("## RiiiverSdkClient: initialized failed due to \(error.localizedDescription)") throw error } RiiiverSdkManager.addListener(delegate: self) // ← add delegate return }
The ERSdkListener
protocol provides the following delegate methods, so please implement the necessary processing.
Methods of ERSdkListener
startApplet(applet:error:)
Called when the execution of iiidea is started.
endApplet(applet:blockName:output:error:)
Called when the execution of iiidea has finished.
blockName
is the type of Piece (trigger, service, or action) that terminated. In case of abnormal termination, it will tell which Piece has been reached. Also,error
contains error information.onLogin(applets:)
Called when login to Riiiver is completed.
onLogout()
Called when users log out of Riiiver.
- Note
- - `Applet` is the name of iiidea when developing Riiiver. Please read "iiidea" instead of "applet" as it is used in the code and documentation.
- `Block` is the name of piece when developing Riiiver. Please read "piece" instead of "block" as it is used in the code and documentation.
Use the authentication features(login and out)
What this section describes:
- [method]
getLoginUrl()
- [method]
verifyCodeOf(uri:)
- [method]
logout()
An authentication page is provided to login to Riiiver. Please open the URL in the return value of the getLoginUrl()
method. When a user completes the login operation on the authentication page, a redirect will occur. The redirect URL is registered as a part of your application information via our developer site. If you have not registered, please visit our developer site and register the redirect URL. Please set it in info.plist
so that the app can be launched via a custom scheme.
When the app is launched via a custom scheme, a short-lived authorization code is given as a query parameter in the URL. The URL retrieved by the application(_:open:options:)
delegate method in the AppDelegate
or the onOpenURL(perform:)
method if you are using SwiftUI, is passed to the verifyCodeOf(uri:)
method to complete the login to Riiiver.
- Note
- - It is recommended to use `SFSafariViewController` to open the authentication page, as it will cause a redirect and pass values to the app with a custom scheme. If you use the `UIApplication.shared.open(url:)` method to open in an external browser, the user may have changed their default browser from Safari.
If you want to log out of Riiiver, execute the logout()
method at any time.
Display the iiidea list and the settings screen
What this section describes:
- [class]
ERUserWebPagesView
- [protocol]
ERUserWebPagesViewDelegate
We provide a web page that aggregates a iiidea store for users to search for and obtain iiidea, and a list view for users to set or delete already obtained iiidea. The ERUserWebPagesView
class, which extends the WKWebView
class, is provided as a class for displaying these web pages.
loadPage(type:)
method can be executed to display the page, in the viewDidLoad()
method when using UIViewController in your project, or in the makeUIView(context:)
method when using SwiftUI's UIViewRepresentable in your project.
Some iiidea require the use of device's permissions or OAuth authentication for third-party services at runtime, so when a user requests those permissions on a web page, the ERUserWebPagesViewDelegate
protocol is provided to delegate the handling of those events to the app side. The following delegate methods are provided for this delegate, so please implement the necessary processing.
Methods of ERUserWebPagesViewDelegate
.
didReceivePermissionRequest(permissionType:)
Called when the request button for a device's permission on a web page is tapped. The type of permission being requested is passed as an argument, so that you can present the user with the permission's usage authorization dialog.
didReceiveOpenUrlRequest(url:)
Called when a new URL needs to be opened, such as for OAuth authentication. Open the URL passed as argument.
didReceiveCloseRequest()
Called when the close button on the web page is tapped.
onError(error:)
Called when an error occurs on a web page.
Once the necessary settings for iiidea have been completed, iiidea can be activated. iiidea is activated and is ready to run.
Pre-install some iiidea(s)
What this section describes:
- [method]
forcedDownloadApplet(appletIds:)
If you want to make pre-installed some features when the user installs your application, you can use the forcedDownloadApplet(appletIds:)
method to make the iiidea of the ID specified in the argument already obtained.
Get the available iiideas list
What this section describes:
- [method]
getMyApplets(deviceIdList:)
- [class]
ERApplet
This sample app is implemented so that each of the two physical buttons on the Slate+ evaluation board can execute a different iiidea when pressed. For this purpose, the app provides a UI that allows the user to freely select the iiidea to be executed. At this time, the method getMyApplets(deviceIdList:)
is used to retrieve the list of iiidea already obtained by the user. By passing a device ID as an argument in the form of an Array of String, a list of available iiideas on that device can be obtained. The return value is an Array of ERApplet
.
The ERApplet
class provides properties containing iiidea metadata, as well as methods related to activation deactivation and iiidea execution. When you want to find out if a iiidea is ready to run (activated state), you can call getActivateStatus()
method.
Implement the Piece(s) and execute iiidea
What this section describes:
- [protocol]
ERBlockExecutor
- [protocol]
ERBlockDelegate
iiidea is a small application that combines 3 Pieces. We call Trigger Piece for the first one, Service Piece for the second one, and Action Piece for the third one, respectively.
All Pieces implemented in the app or SDK must inherit the ERBlockExecutor
protocol. The following methods are defined in this protocol, so please implement the necessary processing.
Methods of ERBlockExecutor
enable(applet:)
Called when an
applet
using this Piece is activated by the user. Return true if the piece can be activated, or false if it cannot be activated.Although there is no example implementation in this sample app, the geofence trigger implemented in the SDK, for example, checks whether permission to use location information has been obtained and returns false if permission has not been obtained.
execute(applet:blockName:preferenceJson:inputJson:handler:)
Called when an
applet
that uses this Piece is executed and in the order of execution of this Piece. In other words, this method is called when the previous Piece is completed.
When the processing of the Piece is completed, thecompleted(outputJson:error:)
method of theERBlockDelegate
passed as thehandler
is executed. In case of abnormal termination, call thecompleted(outputJson:error:)
method with the argumenterror
.
Note that this method is called when it turns activated in the case of the first Piece of iiidea, the trigger Piece. It is necessary to make sure that the execution of iiidea starts when the desired event actually occurs. Regarding theQCC5100TriggerPieceCore
, which fires when a button on the Slate+ EVB is pressed by the user, theQCC5100TriggerPieceCore
instance holds the iiidea id and ahandler
for it as a key-value pair in the dictionary type. And it implements thedidTrigger(appletId:input:)
method to notify events. At the timing when theperipheral(_:didUpdateValueFor:error:)
method ofCBPeripheralDelegate
is called (i.e., when the app is notified of the button press event on the EVB), thedidTrigger( appletId:input:)
method is called to start iiidea execution.disable(applet:)
Called when an
applet
that uses this Piece is deactivated. Implement a process such as releasing resources that needed to be held while waiting for execution.In
QCC5100TriggerPieceCore
, theERBlockDelegate
corresponding to iiideaId is discarded. Also, the geofence trigger implemented in the SDK stops monitoring location information.
Run iiidea from outside the app using Firebase
What this section describes:.
- [method]
setNotificationToken(_:)
- [method]
executeTaskForRemoteNotification(_:)
Preparation
The Firebase Cloud Messaging API is used to achieve this. The following files are required to use the function.
GoogleService-info.plist
APNs authentication key
For information on how to prepare the file, please refer to the following document
Generating a provisioning profile
Enable Background Modes (iOS only)
The prepared Google-Service-info.plist
file must be placed in the root hierarchy of the bundle. In this project, the file is placed in the directory Infra/Firebase/GoogleService
, but Xcode's Run Script copies the file to the root of the bundle at build execution.
FirebaseMessaging Setup
In order to use the FirebaseMessaging APIs, some implementation is required in the AppDelegate.
In the application(_:didFinishLauchingWithOptions:)
method of the AppDelegate, you must perform these actions: (1) the FCM token issue request, (2) FCM delegate configuration, (3) push notification permission request, and (4) APNs device token issue request in the application(_:didFinishLauchingWithOptions:)
method of AppDelegate.
// (1) Request to issue FCM token and set delegates FirebaseApp.configure() // (2) Set messaging delegate Messaging.messaging().delegate = self // Register for remote notifications. // (3) This shows a permission dialog on first run, to show the dialog at // a more appropriate time move this registration accordingly. 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) application.registerUserNotificationSettings(settings) } // (4) Request to issue APNs device token application.registerForRemoteNotifications()
Implement the application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
method, which will be called when the APNs device token is issued. The FCM token is issued when the device token for the APNs is registered with the Firebase Messaging SDK.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { debugPrint("[APNs]デバイストークン受信 [\(deviceToken.map{ String(format: "%.2hhx", $0) }.joined())]") Messaging.messaging().apnsToken = deviceToken }
Inherit the MessagingDelegate
protocol and implement the messaging(_:didReceipRegistrationToken:)
method, which will be called when the FCM token is issued, and set the FCM token to the Riiiver SDK. Execute the setNotificationToken(_:)
method.
extension AppDelegate: MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { if let fcmToken { print("fcmToken = \(String(describing: fcmToken))") self.fcmToken = fcmToken Task(priority: .medium) { do { try await RiiiverSDKClient.sharedInstance.setNotificationToken(fcmToken) } catch (let error) { print("## MessagingDelegate setNotificationToken failed due to \(error.localizedDescription)") } } } else { debugPrint("fcm token is nil.") return } } }
Implement the application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
method, which is called when a data message is received using Firebase (cf. message type). The received push notification is passed to the Riiiver SDK to delegate subsequent processing. Use the ExecuteTaskForRemoteNotification(_:)
method.
For Riiiver-based data messages, the userInfo
contains the classification information of the push notification with the key "notification_id"
. If your project already uses Firebase, you can branch the process depending on the presence or absence of this key. In other words, determine if this key is included and execute the executeTaskForRemoteNotification(_:)
method only if it is included.