Skip to content

Commit ee9e18b

Browse files
authored
Merge pull request #714 from seamapi/mobile-v2-updates
Update Mobile SDK Documentation for Android v2 and iOS v3
2 parents 2cc56db + e17bc42 commit ee9e18b

File tree

3 files changed

+400
-408
lines changed

3 files changed

+400
-408
lines changed
Lines changed: 59 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,98 @@
11
# Handling System Permissions
22

3-
This guide demonstrates how to request necessary system permissions, such as Bluetooth or Location Access. The SDK enables your application to identify and request the necessary permissions for specific features. Additionally, it details how to display warnings or banners within your application when required permissions are not enabled.
3+
The mobile SDK surfaces missing or required system permissions (Bluetooth, internet connectivity, and so on) as `CredentialError.userInteractionRequired(action)` entries in each credential’s `errors` array. After activation, observe these errors and handle the specified actions. Errors update automatically as requirements change.
44

5-
## Retrieving and Requesting Required Permissions at Start Up
6-
7-
Upon launch, the application needs to request the necessary permissions from the user. To retrieve the obtain the list of required permissions, use the `listRequiredAndroidPermissions` or `listRequiredIosPermissions` methods, and specify the features the application will be using.
5+
## Monitoring Permission Errors
86

97
{% tabs %}
8+
109
{% tab title="Android Kotlin" %}
1110
```kotlin
12-
val requiredPermissions = seam.phone.native.listRequiredAndroidPermissions(
13-
enableUnlockWithTap = true
14-
)
15-
16-
if (requiredPermissions.isNotEmpty()) {
17-
// Request required permissions from the user.
11+
coroutineScope.launch {
12+
SeamSDK.getInstance().credentials.collect { credentialsList ->
13+
val errors = credentialsList.flatMap { it.errors }
14+
errors.forEach { error ->
15+
when (error) {
16+
is SeamCredentialError.Expired -> { /* handle credential expiration error */}
17+
is SeamCredentialError.Loading -> { /* handle not loaded yet */ }
18+
is SeamCredentialError.Unknown -> { /* handle unknown error */}
19+
is SeamCredentialError.UserInteractionRequired -> {
20+
handleUserInteractionRequired(error.interaction)
21+
}
22+
}
23+
}
24+
}
1825
}
1926
```
2027
{% endtab %}
2128

2229
{% tab title="iOS Swift" %}
23-
```swift
24-
let requiredPermissions = seam.phone.native.listRequiredIosPermissions( // Coming soon!
25-
enableUnlockWithTap: true
26-
)
2730

28-
if (!requiredPermissions.isEmpty) {
29-
// Request required permissions from the user.
31+
Use Combine to watch the published credentials array and handle permission-related errors:
32+
33+
34+
```swift
35+
import SeamSDK
36+
import Combine
37+
38+
func startMonitoringPermissionErrors() {
39+
permissionCancellable = Seam.shared.$credentials
40+
.map { credentials in
41+
credentials.flatMap { credential in
42+
credential.errors.compactMap { error in
43+
guard case .userInteractionRequired(let action) = error else { return nil }
44+
return action
45+
}
46+
}
47+
}
48+
.receive(on: RunLoop.main)
49+
.sink { actions in
50+
actions.forEach { handlePermissionAction($0) }
51+
}
3052
}
3153
```
3254
{% endtab %}
3355
{% endtabs %}
3456

35-
Once you've acquired the list of required permissions, please refer to relevant Android and iOS documentation for guidance on adding system permissions. 
36-
37-
<figure><img src="../../../.gitbook/assets/image (6).png" alt=""><figcaption><p>Requesting Required System Permissions for your App</p></figcaption></figure>
3857

39-
***
58+
The mobile SDK automatically clears resolved permission errors once the required permission is granted, reflecting the updated credential state.
4059

41-
## Perform a System Check and Display Warnings
60+
## Handling Permission Actions
4261

43-
An app user may choose to deny the application's permission requests. Before launching any features, your application must perform a system check. If the required permissions are not enabled, the app must inform the user, and instruct the user to enable them.
62+
Implement your handler for each action:
4463

4564
{% tabs %}
65+
4666
{% tab title="Android Kotlin" %}
4767
```kotlin
48-
fun handleEvent(
49-
event: SeamEvent
50-
) {
51-
// Check whether the phone state has changed.
52-
// Note that these events are located under the phone namespace.
53-
if (event is SeamEvent.Phone) {
54-
val phone = seam.phone.get().nativeMetadata
55-
56-
if (
57-
// The desired state has not been met.
58-
!phone.canUnlockWithTap
59-
) {
60-
if (phone.errors.any { it is SeamError.Phone.Native.MissingRequiredAndroidPermissions }) {
61-
// Need to update the required permissions.
62-
val requiredPermissions = seam.phone.native.listRequiredAndroidPermissions(
63-
enableUnlockWithTap = true
64-
)
65-
66-
// Request the requiredPermissions or prompt the user to do so.
67-
}
68+
fun handleUserInteractionRequired(interaction: SeamRequiredUserInteraction) {
69+
when (interaction) {
70+
is SeamRequiredUserInteraction.CompleteOtpAuthorization -> { /* handle OTP authorization */ }
71+
is SeamRequiredUserInteraction.EnableBluetooth -> { /* handle Bluetooth error */ }
72+
is SeamRequiredUserInteraction.EnableInternet -> { /* handle Internet connection error*/ }
73+
is SeamRequiredUserInteraction.GrantPermissions -> { /* handle permissions error*/ }
6874
}
69-
}
7075
}
7176
```
7277
{% endtab %}
7378

7479
{% tab title="iOS Swift" %}
7580
```swift
76-
func eventDelegate(
77-
event: SeamEvent
78-
) {
79-
// Check whether the phone state has changed.
80-
// Note that these events are located under the phone namespace.
81-
switch (event) {
82-
case .phone:
83-
let phone = seam.phone.get().nativeMetadata // Coming soon!
84-
85-
// The desired state has not been met.
86-
if(!phone.canUnlockWithTap) {
87-
if (phone.errors.contains(where: $0 == {.phone(.native(.missingRequiredIosPermissions)))}) {
88-
// Need to update the required permissions.
89-
let requiredPermissions = seam.phone.native.listRequiredIosPermissions( // Coming soon!
90-
enableUnlockWithTap: true
91-
)
92-
// Request the requiredPermissions or prompt the user to do so.
93-
}
94-
}
95-
break
81+
func handlePermissionAction(_ action: CredentialUserAction) {
82+
switch action {
83+
case .enableInternet:
84+
// Prompt the user to enable network connectivity.
85+
case .enableBluetooth:
86+
// Prompt the user to turn on Bluetooth.
87+
case .grantBluetoothPermission:
88+
// Prompt the user to grant Bluetooth permission in Settings.
9689
}
9790
}
9891
```
9992
{% endtab %}
10093
{% endtabs %}
10194

102-
<figure><img src="../../../.gitbook/assets/image (7).png" alt=""><figcaption><p>Inform the user to enable required system permissions.</p></figcaption></figure>
10395

96+
## See also
97+
98+
For a complete SwiftUI-based implementation of credential error handling for iOS, see `SeamUnlockCardView` in the SeamComponents library, which demonstrates observing credential errors and updating the UI accordingly.

docs/capability-guides/mobile-access/mobile-device-sdks/initializing-the-seam-mobile-sdk.md

Lines changed: 77 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,39 @@ dependencies {
3636
{% endtab %}
3737

3838
{% tab title="iOS Swift" %}
39-
To install the Seam iOS SDK, add the `SeamSdk.podspec` to the `target` block of your [Podfile](https://guides.cocoapods.org/using/using-cocoapods.html).
39+
You can install SeamSDK via CocoaPods or Swift Package Manager (SPM).
40+
SeamSDK supports per-lock-provider integration granularity. Include only the modules you need to keep your app footprint minimal.
4041

4142
{% code title="Podfile" %}
4243
```ruby
4344
use_frameworks!
44-
45-
platform :ios, '...'
45+
platform :ios, '15.0'
4646

4747
target 'YourApp' do
48-
// ...
49-
// Local pod install with file path to SeamSdk.podspec
50-
pod 'SeamSdk', :path => './SeamSdk.podspec'
48+
# Local pod install with file path to SeamSdk.podspec
49+
pod 'SeamSDK', :path => 'PATH_TO_SEAM_SDK/SeamSDK.podspec'
50+
# Optional subspecs for specific providers:
51+
pod 'SeamSDK/SeamDormakabaIntegration', :path => 'PATH_TO_SEAM_SDK/SeamSDK.podspec'
52+
pod 'SeamSDK/SeamLatchIntegration', :path => 'PATH_TO_SEAM_SDK/SeamSDK.podspec'
5153
end
5254
```
5355
{% endcode %}
56+
57+
{% tabs %}
58+
{% tab title="SPM" %}
59+
```swift
60+
dependencies: [
61+
.package(path: "PATH_TO_SEAM_SDK/SeamSDK")
62+
],
63+
targets: [
64+
.target(
65+
name: "YourApp",
66+
dependencies: ["SeamSDK", "SeamSaltoIntegration"]
67+
)
68+
]
69+
```
70+
{% endtab %}
71+
{% endtabs %}
5472
{% endtab %}
5573
{% endtabs %}
5674

@@ -68,19 +86,15 @@ See the [device or system integration guide](../../../device-and-system-integrat
6886

6987
### iOS Requirement
7088

71-
If you are developing an iOS app, add [`requestAutomaticPassPresentationSuppression()`](https://developer.apple.com/documentation/passkit/pkpasslibrary/requestautomaticpasspresentationsuppression\(responsehandler:\)) to your app to prevent the user's phone from displaying Apple Wallet while scanning for Bluetooth low energy (BLE) or similar locks. This method suppresses Apple Wallet while your mobile app is in the foreground.
72-
73-
{% hint style="info" %}
74-
The use of this method requires a special entitlement from Apple.
75-
{% endhint %}
89+
While not required, you can optionally request the `com.apple.developer.passkit.pass-presentation-suppression` entitlement from the Apple Developer portal. This entitlement prevents Apple Wallet from appearing when scanning for Bluetooth low energy (BLE) or similar locks, improving the unlock experience.
7690

7791
***
7892

7993
## 3. Configure a User Identity for your App User and Generate a Client Session Token
8094

8195
A [user identity](../managing-mobile-app-user-accounts-with-user-identities.md) enables the application to request a user's mobile access permissions and use the app to unlock doors.
8296

83-
First, use the Seam API to create a [user identity](../managing-mobile-app-user-accounts-with-user-identities.md#what-is-a-user-identity) that will correspond to the App User Account using your internal user ID or other identifying information.
97+
First, use the Seam API or Seam Console to create a [user identity](../managing-mobile-app-user-accounts-with-user-identities.md#what-is-a-user-identity) that will correspond to the App User Account using your internal user ID or other identifying information.
8498

8599
Then, using the user identity, create a [client session](../../../core-concepts/authentication/client-session-tokens/) and capture the resulting [client session token](../../../core-concepts/authentication/client-session-tokens/). This token will be used to authenticate the user on your application.
86100

@@ -183,17 +197,20 @@ $client_session = $seam->client_sessions->create(
183197
$token = $client_session->token;
184198
```
185199
{% endtab %}
200+
201+
186202
{% endtabs %}
187203

188204
***
189205

190206
## 4. Initialize the Mobile SDK with the Client Session Token
191207

192-
Use the client session token generated earlier to initialize the Seam Mobile SDK. This initializes the Mobile SDK for the app user, and retrieves the relevant provider-specific settings. This also launches a background process that will continually poll for any updates to the access permissions.
193208

194-
### Initialization and Handling Configuration Errors
209+
Use the client session token that you generated earlier to bootstrap the Seam SDK on the device. Under the hood, this action sets up credential synchronization and starts a background sync loop to keep permissions up to date.
210+
211+
### Initialization and Error Handling
195212

196-
The initialization process may fail if the Seam workspace or provider-specific settings are not configured properly. If the initialization process encounters any irrecoverable errors, this block will catch these exceptions, specifically those identified as `SeamError`. These errors point to development errors, and should be addressed by making sure that your Workspace and User Identity is configured properly.
213+
Perform initialization and activation within your app’s asynchronous context (for example, Swift’s `Task` or Kotlin coroutines) so that you can handle errors. The initialization call may fail due to configuration issues (such as an invalid token), and the activation call may fail due to network or runtime errors. Catch these errors and present a user-friendly message or fallback UI as appropriate.
197214

198215
{% tabs %}
199216
{% tab title="Android Kotlin" %}
@@ -223,94 +240,59 @@ try {
223240

224241
{% tab title="iOS Swift" %}
225242
```swift
226-
import Seam
227-
228-
// client session token for th user
229-
let clientSessionToken =
230-
231-
let seam = SeamClient(
232-
clientSessionToken: clientSessionToken,
233-
seamEventDelegate:
234-
)
235-
236-
try seam.phone.native.initialize(
237-
enableUnlockWithTap: true
238-
)
243+
import SeamSDK
244+
245+
// Initialize the Mobile SDK with the client session token (CST).
246+
Task {
247+
do {
248+
// Bootstrap the SDK with the CST from your login flow.
249+
try Seam.initialize(clientSessionToken: token)
250+
// Start credential sync and background polling.
251+
try await Seam.shared.activate()
252+
print("Seam SDK is now active.")
253+
} catch let error as SeamError {
254+
// Handle SDK-specific initialization errors (invalid token, etc).
255+
showAlert(title: "Initialization Failed", message: error.localizedDescription)
256+
}
257+
}
239258
```
240259
{% endtab %}
241260
{% endtabs %}
242261

243-
### Handling Provider Initialization Errors from the Background Process
244-
245-
Any failures during the background process will produce errors. These errors can be accessed from the error list, and events will also be emitted. To handle these operational errors, you may log the error in logs or displaying a message to the user.
246262

247-
{% tabs %}
248-
{% tab title="Android Kotlin" %}
249-
```kotlin
250-
fun (
251-
seam: Seam,
252-
event: SeamEvent
253-
) {
254-
// If the event is under the phone namespace, the phone state may have changed.
255-
if event is SeamEvent.Phone {
256-
val phone = seam.phone.get().nativeMetadata
257-
258-
// Check for the desired state of the phone for all app features to work.
259-
if (!phone.isInitialized || !phone.canUnlockWithTap) {
260-
// Check errors and display them to the user.
261-
phone.errors
262-
// For example:
263-
// SeamError.Phone.Native.MissingRequiredAndroidPermissions: Missing required permissions.
264-
// SeamError.Phone.Native.InternetConnectionRequired: No internet.
265-
// SeamError.AuthenticationError: Invalid client session token.
266-
// SeamError.Internal.Phone.Native.ProviderInitializationFailure: Invalid provider configuration.
267-
// SeamError.Internal.Phone.Native.ProviderInitializationFailure: Android version not compatible.
268-
269-
// Display error message and disable functionality/access,
270-
// until the user resolves the issue.
271-
} else {
272-
// Set the UI state to indicate that all app features are working.
273-
}
274-
}
275-
}
263+
### Credential Errors
276264

277-
val seam = SeamClient(
278-
seamEventHandler = eventHandler,
279-
// ...
280-
)
281-
```
282-
{% endtab %}
265+
Any errors that occur between activation and deactivation surface on individual credential objects through their `errors` property. Observe the `credentials` array to detect and handle these errors:
283266

284-
{% tab title="iOS Swift" %}
285267
```swift
286-
func (
287-
seam: Seam,
288-
event: SeamEvent
289-
) {
290-
// If the event is under the phone namespace, the phone state may have changed.
291-
switch(event) {
292-
case .phone:
293-
let phone = seam.phone.get().nativeMetadata // Coming soon!
294-
295-
// Check for the desired state of the phone for all app features to work.
296-
if (!phone.isInitialized || !phone.canUnlockWithTap) {
297-
// check errors and display them to the user.
298-
phone.errors
299-
// For example:
300-
// seam.phone.native.missing_required_ios_permissions: Missing required permissions.
301-
// seam.phone.native.internet_connection_required: No internet.
302-
// seam.authentication_error: Invalid client session token.
303-
// seam.internal.phone.native.provider_initialization_failure: Invalid provider configuration.
304-
} else {
305-
// Set the UI state to indicate that all app features are working.
268+
import SeamSDK
269+
import Combine
270+
271+
private var credentialErrorsCancellable: AnyCancellable?
272+
273+
func startMonitoringCredentialErrors() {
274+
credentialErrorsCancellable = Seam.shared.$credentials
275+
.sink { credentials in
276+
for credential in credentials where !credential.errors.isEmpty {
277+
print("Errors for \(credential.displayName):", credential.errors)
278+
}
306279
}
307-
}
308280
}
309-
310-
let seam = SeamClient(
311-
seamEventHandler = eventHandler,
312-
// ...
313-
)
314281
```
315-
{% endtab %}
316-
{% endtabs %}
282+
283+
#### Credential Error Types
284+
285+
- `awaitingLocalCredential`: The system is waiting for a local credential to become available.
286+
- `expired`: The credential has expired and is no longer valid.
287+
- `userInteractionRequired(action)`: User interaction is required to resolve the credential issue; check the action for specifics.
288+
- `contactSeamSupport`: Configuration error requiring developer attention.
289+
- `unsupportedDevice`: The current device is not supported.
290+
- `unknown`: An unclassified or unexpected credential error occurred.
291+
292+
#### Possible `userInteractionRequired` Actions
293+
294+
- `completeOtpAuthorization(otpUrl:)`: The user must complete OTP authorization via the provided URL.
295+
- `enableInternet`: The user must enable internet connectivity.
296+
- `enableBluetooth`: The user must enable Bluetooth on the device.
297+
- `grantBluetoothPermission`: The user must grant Bluetooth permission to the app.
298+
- `appRestartRequired`: The user must restart the app to resolve the issue.

0 commit comments

Comments
 (0)