WarrantyLife SDK Usage Guide
This guide provides an overview of how to integrate and use the WarrantyLife SDK in your application.
- Android
SDK Initialisation
WarrantyLifeSDK.build() performs the following on first call:
- Initialises the Retrofit API client with your
clientCode - Sets up Room databases (tests, warranties, drops, notifications)
- Initialises WorkManager with the SDK's custom
WLWorkerFactory - Registers a
ProcessLifecycleOwnerobserver that callstrackAppLaunch()every time the app comes to the foreground
Important: The lifecycle observer is registered after
apiClientis fully constructed, sotrackAppLaunchalways has a valid client available.
Lifecycle Callbacks
Register all SDK callbacks in Activity.onCreate() before setContent.
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private var notificationPermissionRequestPending = false
private var navigateToRegistration: (() -> Unit)? = null
// Handles the system POST_NOTIFICATIONS permission dialog result
private val requestNotificationPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
notificationPermissionRequestPending = false
if (isGranted) {
WarrantyLifeSDK.onNotificationPermissionGranted(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. Notification permission callbacks — one per Smart Saver opt-in type
val requestPermission: () -> Unit = { requestNotificationPermissionIfNeeded() }
WarrantyLifeSDK.setSSRNotificationPermissionRequestCallback(requestPermission)
WarrantyLifeSDK.setSSDNotificationPermissionRequestCallback(requestPermission)
WarrantyLifeSDK.setTradeInNotificationPermissionRequestCallback(requestPermission)
// 2. Device change callback — fired when the SDK detects a new device
WarrantyLifeSDK.setDeviceChangedCallback {
runOnUiThread { navigateToRegistration?.invoke() }
}
// 3. Refresh device category (safe to call before build() completes)
WarrantyLifeSDK.refreshDeviceCategory(this)
setContent {
AppNavigation(onRegisterNavigationReady = { navigateToRegistration = it })
}
}
private fun requestNotificationPermissionIfNeeded() {
if (notificationPermissionRequestPending) return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED
) {
notificationPermissionRequestPending = true
requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
}
trackAppLaunch behaviour
Every time the app enters the foreground trackAppLaunch runs automatically. It:
- Sends an
APP_ENTERED_FOREGROUNDsensor event to the server - If logged in → calls
getItemByDeviceID()thengetAllWarranties()to refresh coverage and offer state; then checkshasUserChangedDevice()and firessetDeviceChangedCallbackif true - If not logged in → calls
getItemByDeviceIdPublic()to check coverage without auth - After API calls complete, evaluates whether to start the drop-detection service (see §9)
Typical Registration Flow
The full flow is an 8-step nested navigation graph. Each step is a Composable backed by a @HiltViewModel.
voucher_code → required_tests → auth → [receipt] → imei → [date_of_purchase] → [smart_saver] → create_warranty
Steps in [] are conditional and may be skipped depending on plan configuration.
Receipt capture is placed before IMEI entry so that the image file is already written to the cache when registerDevice runs. ReceiptViewModel.saveAndUpload handles the case where itemId is not yet available — it saves the file locally and skips the network upload; the file is then picked up automatically during registerDevice.
Step 1 — Voucher Code (getVoucherByVoucherCode)
@HiltViewModel
class WarrantyRegistrationViewModel @Inject constructor(
private val apiRepository: ApiRepository,
private val appDataManager: AppDataManager,
) : ViewModel() {
// Pre-fills the field if a voucher code was previously saved
val savedVoucherCode: String = appDataManager.getCurrentVoucherCode()
fun getVoucherByVoucherCode(voucherCode: String) {
viewModelScope.launch {
val response = apiRepository.getVoucherByVoucherCode(
voucherCode = voucherCode,
isPlanTransfer = null
)
// handle ApiSuccessResponse / ApiErrorResponse
}
}
}
Response: Voucher — contains plan details, test requirements, Smart Saver offer flags, registration deadline, and original device details when the plan is already claimed.
Voucher fields reference
| Field | Type | Description |
|---|---|---|
action | VoucherAction | Next action the app must take — see table below |
registrationType | RegistrationType | Form variant for the registration screen — see §Auth |
originalDevice | VoucherDevice? | Details of the device the plan was originally registered to. Present when the plan is claimed (isClaimed == true). Used to format the DEVICE_TRANSFERABLE_POPUP message |
owner | User? | Account that owns the plan. Email is the source for LOCKED_EMAIL_REGISTRATION |
requiresReceipt | Boolean? | Whether a receipt image must be captured before IMEI entry |
verifiedCustomerPurchaseDate | String? | Pre-filled purchase date if supplied by the retailer. null means the user must enter it |
isDeviceTransferable | Boolean? | Whether the plan can be transferred to a different device |
rewardAmountMax | Int? | Maximum Smart Saver reward available. > 0 means the Smart Saver screen should be shown |
VoucherDevice fields (original device)
Voucher.originalDevice is populated by the SDK when the plan is already claimed (isClaimed == true). It is a lightweight model containing only the fields needed for UI display:
data class VoucherDevice(
val model: String?, // e.g. "iPhone 13 Pro"
val manufacturerName: String?, // e.g. "Apple"
val serialNumber: String?, // IMEI / serial number
)
// Format DEVICE_TRANSFERABLE_POPUP message with original device details
val device = voucher.originalDevice
val model = device?.model ?: "your device"
val imei = device?.serialNumber ?: "N/A"
val message = action.description.format(model, imei, model)
// → "Looks like this coverage was bought for an iPhone 13 Pro (IMEI: 352800112345678).
// Are you using that iPhone 13 Pro right now? ..."
VoucherAction — routing after voucher validation
The Voucher.action field tells the app what to do next. Actions with isError = true should be shown as error messages; all others require specific UI handling.
| Action | isError | Meaning | Recommended handling |
|---|---|---|---|
NORMAL_REGISTRATION | false | Default — no special conditions | Proceed to next step |
DEVICE_TRANSFERABLE_POPUP | false | Coverage bought for a different device; plan is transferable | Show confirm dialog — user confirms they are on the original device → proceed; declines → stay |
DEVICE_NON_TRANSFERABLE_POPUP | false | Coverage bought for a different device; plan is not transferable | Show info dialog — user cannot proceed |
USER_NOT_LOGGED_IN_CLAIMED | false | Plan already registered; user is not logged in | Show info dialog → navigate to Login |
DIFFERENT_EMAIL_CLAIMED | false | Plan claimed under a different account | Show info dialog — user cannot proceed |
ACTIVE_WARRANTY_CONFLICT | true | Active warranties on this device under another account | Show error message — contact support |
WARRANTY_NOT_TRANSFERABLE | true | Selected plan is non-transferable | Show error message |
START_WARRANTY_TRANSFER | false | Plan transfer should be initiated | Navigate to plan transfer flow |
val voucher = response.data ?: return
val action = voucher.action
val registrationType = voucher.registrationType
if (action.isError) {
showError(action.description)
return
}
when (action) {
VoucherAction.NORMAL_REGISTRATION -> navigateToNextStep()
VoucherAction.START_WARRANTY_TRANSFER -> navigateToTransferFlow()
VoucherAction.DEVICE_TRANSFERABLE_POPUP -> showConfirmDialog(action.description) {
onConfirm = { navigateToNextStep() }
}
VoucherAction.USER_NOT_LOGGED_IN_CLAIMED -> showInfoDialog(action.description) {
onConfirm = { navigateToLogin() }
}
// DEVICE_NON_TRANSFERABLE_POPUP, DIFFERENT_EMAIL_CLAIMED
else -> showInfoDialog(action.description)
}
For
DEVICE_TRANSFERABLE_POPUP, the model and IMEI come fromvoucher.voucherItem?.modelandvoucher.voucherItem?.serialNumber. See theVoucherItemreference above for the full formatting example.
Step 2 — Required Tests
Tests required by the plan are read from the local Room database via AppDataManager:
val hasAutoTest = appDataManager.isAutoTestRequired()
val hasSensor = appDataManager.isSensorTestRequired()
val hasFrontGlass = appDataManager.isFrontCameraTestRequired()
val hasBackGlass = appDataManager.isBackCameraTestRequired()
val hasOpenFace = appDataManager.isOpenFaceTestRequired()
Each returns Boolean (suspend). true means DiagnosticRequirement.TEST_REQUIRED.
Step 3 — Auth (login / registerNewUser)
The screen is skipped automatically if appDataManager.isUserLoggedIn() is already true.
// Login
val response = apiRepository.login(email, password)
// Register new user
val response = apiRepository.registerNewUser(
email, password, firstName, lastName,
phone, streetAddress, city, state, country, zip, company
)
Both return ApiResponse<LoginResponse>. On success, user token and identity are persisted automatically.
RegistrationType — registration form variant
Voucher.registrationType controls which fields the registration form presents. Read it before rendering the register screen.
| Type | Password field | Email field | Description |
|---|---|---|---|
NORMAL_REGISTRATION | Required | User-entered | Standard account creation |
PASSWORDLESS_REGISTRATION | Hidden | User-entered | Account created without a password |
LOCKED_EMAIL_REGISTRATION | Required | Pre-filled, read-only | Email is provided by the plan; user sets a password |
when (voucher.registrationType) {
RegistrationType.NORMAL_REGISTRATION -> {
// show email + password + profile fields
}
RegistrationType.PASSWORDLESS_REGISTRATION -> {
// show email + profile fields; pass "" for password
}
RegistrationType.LOCKED_EMAIL_REGISTRATION -> {
// pre-fill email from voucher.owner?.email, make it read-only
// show password + profile fields
}
}
Step 4 — Receipt Image (conditional)
Shown immediately after auth when appDataManager.isReceiptRequired() is true. Capturing the image here ensures it is present in the cache before registerDevice runs.
appDataManager.setReceiptImagePath(filePath) // absolute path to JPEG
If the user skips this screen the file path is left empty; registerDevice will still succeed but the receipt upload step will be a no-op.
Step 5 — IMEI + Register Device (registerDevice)
appDataManager.setIMEI(imei)
apiRepository.registerDevice(context).collect { state ->
when (state) {
is RegisterDeviceState.Submitted -> navigateToNextScreen()
is RegisterDeviceState.Progress -> showProgress(state.percentage)
is RegisterDeviceState.Success -> { /* upload complete */ }
is RegisterDeviceState.ValidationError -> handleValidationError(state.error)
is RegisterDeviceState.UploadError -> showError(state.message)
}
}
registerDevice is a Flow that streams upload progress. It uploads sensor test data, glass test images, and the receipt image (if captured in step 4) as a multi-step background job.
RegisterDeviceState reference
| State | Payload | When it fires |
|---|---|---|
Submitted | — | Worker has been enqueued and upload is about to begin — use this to navigate away or show a loader |
Progress | percentage: Int | Upload is in progress; emitted repeatedly as each file is sent |
Success | — | All data uploaded successfully; proceed to the next registration step |
ValidationError | error: WLSDKError | A required piece of data is missing before any network call is made |
UploadError | message: String, code: Int | The server returned an error response to an API call during upload |
ValidationErrorvsUploadError:ValidationErroris a local pre-flight check — the SDK detects missing data and fails fast without hitting the network.UploadErrormeans the request reached the server and the server rejected it (e.g. HTTP 4xx/5xx);codecontains the HTTP status code.
WLSDKError — ValidationError cases
Each WLSDKError value tells you exactly what is missing and what the user must do before retrying:
| Error | Meaning | Recommended action |
|---|---|---|
MISSING_IMEI | No IMEI has been set | Show error on the IMEI field; user must re-enter |
MISSING_RECEIPT_IMAGE | Receipt image path is not set | Navigate back to the receipt capture screen |
MISSING_SENSOR_TEST_DATA | Sensor test was not completed | Navigate to the sensor test screen |
MISSING_AUTO_SYSTEM_TEST_DATA | Auto system test was not completed | Navigate to the auto-system test screen |
MISSING_FRONT_GLASS_IMAGE | Front glass photo is missing | Reset front glass test status; navigate to front glass test |
MISSING_BACK_GLASS_IMAGE | Back glass photo is missing | Reset back glass test status; navigate to back glass test |
MISSING_OPEN_FACE_TEST_IMAGE | Open face photo is missing | Reset open face test status; navigate to open face test |
is RegisterDeviceState.ValidationError -> {
when (state.error) {
WLSDKError.MISSING_IMEI -> {
// show error on IMEI input field
}
WLSDKError.MISSING_RECEIPT_IMAGE -> {
// navigate back to receipt capture screen
}
WLSDKError.MISSING_SENSOR_TEST_DATA -> {
// navigate to sensor test
}
WLSDKError.MISSING_AUTO_SYSTEM_TEST_DATA -> {
// navigate to auto-system test
}
WLSDKError.MISSING_FRONT_GLASS_IMAGE -> {
// navigate to front glass test
}
WLSDKError.MISSING_BACK_GLASS_IMAGE -> {
// navigate to back glass test
}
WLSDKError.MISSING_OPEN_FACE_TEST_IMAGE -> {
// navigate to open face test
}
}
}
Step 6 — Date of Purchase (conditional)
Shown when appDataManager.isPurchaseDateRequired() is true.
appDataManager.setReceiptDate(date) // "yyyy-MM-dd"
Step 7 — Smart Saver (conditional)
Shown when appDataManager.hasSsr() || appDataManager.hasSsd() || appDataManager.isTipOffered().
See §8 for full details.
Step 8 — Create / Update Warranty
// New registration
val response = apiRepository.createWarranty(context, model)
// Plan transfer (device change)
val response = apiRepository.updateWarranty(context, encodedWarrantyId, model)
WLCreateWarrantyModel fields:
| Field | Type | Description |
|---|---|---|
itemIds | List<String> | Item ID from registerDevice |
voucherCode | String | Validated voucher code |
firstName / lastName | String | User name |
company | String? | Optional company |
streetAddress, city, zip | String | Address fields |
state | String | Province.id from getAppConfig() — not the display name or abbreviation |
country | String | Country.id from getAppConfig() — not the display name or abbreviation |
phone | String | Contact number |
dateOfPurchase | String? | "yyyy-MM-dd" format |
acceptedMarketing | Boolean | Marketing consent |
isDeviceInsured | Boolean? | Whether device is separately insured |
On success, coverage state, opt-in flags, and heartbeat schedule are persisted automatically. The drop-detection service is started if all conditions are met (see §9).
Try Before You Buy Registration Flow
A trial flow lets the user run a diagnostic and activate coverage on a provisional basis before committing.
startTrial() → required tests → notification permission → completeTrial()
Step 1 — Start the trial
apiRepository.startTrial()
Call this when the user enters the trial flow. It signals the backend to open a provisional coverage window for the device.
Step 2 — Run required tests
Run the same diagnostic tests as the standard registration flow (see §11). The test suite is unchanged; the distinction is that results are submitted as part of a trial rather than a full warranty.
Step 3 — Request notification permission (Android)
The drop-detection service requires the POST_NOTIFICATIONS permission on Android 13+. Request it at this point in the flow, before completing the trial, so the service can start immediately on completeTrial.
// Check and request at the appropriate point in your UI
WarrantyLifeSDK.setSSRNotificationPermissionRequestCallback {
// launch your permission request UI here
}
Step 4 — Complete the trial
apiRepository.completeTrial()
Call this after the user has finished the required tests and granted (or declined) notification permission. On success the trial period is active and the drop-detection service will start if all conditions are met.
Querying trial state
val isActive: Boolean = appDataManager.isTrialActive()
Returns true while a trial period is open for the current device.
Trade-In Registration Flow
The trade-in flow captures a device condition score that is used to calculate a trade-in value.
startTradeInRegistration() → required tests → notification permission → completeTradeInRegistration()
Step 1 — Start trade-in registration
apiRepository.startTradeInRegistration()
Step 2 — Run required tests
Run the diagnostic tests applicable to trade-in (see §11). The SDK determines which tests are required based on the active plan configuration.
Step 3 — Request notification permission (Android)
Same as the standard and trial flows — request POST_NOTIFICATIONS before completing registration so the service can start immediately.
Step 4 — Complete trade-in registration
apiRepository.completeTradeInRegistration()
Querying the trade-in score
val score: String = appDataManager.getTradeInScore().let { raw ->
if (raw <= 0f) "Pending%" else "${raw.toInt()}%"
}
Returns "Pending%" when the score has not yet been calculated by the backend, otherwise returns the score as a percentage string (e.g. "82%").
Smart Saver Opt-Ins
setSSR, setSSD, and setTradeIn each accept (context, enabled) and return OptInResult.
when (val result = appDataManager.setSSR(context, enabled)) {
is OptInResult.Success -> { /* saved */ }
is OptInResult.Error -> showError(result.reason.message)
}
Validation (when enabled = true)
| Check | Error returned |
|---|---|
| User not logged in | OptInError.NOT_LOGGED_IN |
| No active coverage | OptInError.NO_ACTIVE_COVERAGE |
| Notification permission denied | OptInError.NOTIFICATION_PERMISSION_DENIED |
Exception: If a registration or plan transfer is in progress (isRegistrationInProgress() or isDeviceTransferInProgress()), all three checks are skipped and the opt-in is saved immediately.
When enabled = true and all checks pass, the drop-detection service is started automatically if it is not already running.
Reading current values
val ssrOn = appDataManager.getSSR()
val ssdOn = appDataManager.getSSD()
val tipOn = appDataManager.getTradeIn()
Checking offer availability
val hasSsr = appDataManager.hasSsr()
val hasSsd = appDataManager.hasSsd()
val hasTip = appDataManager.isTipOffered()
val maxReward = appDataManager.getRewardAmountMax()
val reward = appDataManager.getRewardAmount()
val currency = appDataManager.getPlanCurrency()
val tradeScore = appDataManager.getTradeInScore() // Float — current device trade-in score
| Method | Returns | Description |
|---|---|---|
hasSsr() | Boolean | Whether the SSR (Safe Driving Reward) offer is available for the plan |
hasSsd() | Boolean | Whether the SSD (Safe Driving Discount) offer is available for the plan |
isTipOffered() | Boolean | Whether the Trade-In Protection offer is available for the plan |
getRewardAmountMax() | Int | Maximum reward amount available (show Smart Saver screen when > 0) |
getRewardAmount() | Int | Current earned reward amount |
getPlanCurrency() | String | Currency code for reward amounts (e.g. "$") |
getTradeInScore() | Float | Current device trade-in score. Updated automatically on every trackAppLaunch. Returns 0f if no score has been received yet |
getTradeInScore()is refreshed from the server on every foreground launch viagetItemByDeviceID(). Display it alongside the Trade-In Protection offer to show users their device's current estimated trade-in value. A score of0findicates the value has not yet been retrieved — show a loading or unavailable state rather than displaying zero.
Drop-Detection Service
The DropDetectionService is a foreground service that monitors for drops using the accelerometer. It is started by the SDK automatically — you do not call it directly.
When the service starts
The SDK evaluates the following conditions and starts the service if all are met:
- User is logged in
- Has active coverage
- At least one of SSR, SSD, or Trade-In is enabled
- Notification permission is granted (or device is pre-API 33)
This evaluation runs on every trackAppLaunch, setSSR/setSSD/setTradeIn success, and createWarranty/updateWarranty success.
Handling missing notification permission
If condition 4 fails, the SDK fires per-opt-in callbacks instead of starting the service:
// Register in Activity.onCreate(), before setContent
WarrantyLifeSDK.setSSRNotificationPermissionRequestCallback {
// show UI or launch system permission dialog
}
WarrantyLifeSDK.setSSDNotificationPermissionRequestCallback {
// same or different handling for SSD
}
WarrantyLifeSDK.setTradeInNotificationPermissionRequestCallback {
// same or different handling for Trade-In
}
Only callbacks for currently-enabled opt-ins are invoked. After the user grants the permission:
WarrantyLifeSDK.onNotificationPermissionGranted(context)
// SDK re-evaluates conditions and starts the service if all are met
Device Change Detection
On every foreground launch (when logged in), the SDK compares the current device ID against the stored one. If they differ, setDeviceChangedCallback is invoked on a background thread.
WarrantyLifeSDK.setDeviceChangedCallback {
runOnUiThread { navigateToRegistration?.invoke() }
}
Direct the user to the registration flow with isPlanTransfer = true to perform a plan transfer via updateWarranty.
Diagnostic Tests
Diagnostic tests are managed by sdk-diagnostic. Use SampleSensorViewModel (or your own equivalent) to drive them.
viewModel.initializeTestManager(activity)
// Auto system test
viewModel.startAutoSystemTest()
val result: AutoTestResult? by viewModel.autoTestResults.collectAsState()
// Sensor test
viewModel.startSensorTest(context)
val result: SensorTestResult? by viewModel.sensorTestResults.collectAsState()
// Glass tests
viewModel.startFrontGlassTest()
viewModel.startBackGlassTest()
viewModel.startOpenFaceTest()
val result: GlassTestResult? by viewModel.glassTestResults.collectAsState()
Result sealed classes
sealed class AutoTestResult {
data class Result(val isPassed: Boolean) : AutoTestResult()
data class Failure(val error: WLDiagnosticError) : AutoTestResult()
}
sealed class SensorTestResult {
object Started : SensorTestResult()
data class Result(val isPassed: Boolean) : SensorTestResult()
data class Failure(val error: WLDiagnosticError) : SensorTestResult()
}
sealed class GlassTestResult {
data class Result(val isPassed: Boolean) : GlassTestResult()
data class Failure(val error: WLDiagnosticError) : GlassTestResult()
}
API Reference
All API functions are suspend and return ApiResponse<T>:
sealed class ApiResponse<T>
class ApiSuccessResponse<T>(val data: T?) : ApiResponse<T>()
class ApiErrorResponse<T>(val errorMessage: String?, val error: String, val errorCode: Int?) : ApiResponse<T>()
Auth
| Function | Parameters | Returns |
|---|---|---|
login(email, password) | String, String | ApiResponse<LoginResponse> |
registerNewUser(email, password, firstName, lastName, phone, street, city, state, country, zip, company) | String... | ApiResponse<LoginResponse> |
recoverLostPassword(email) | String | ApiResponse<ResponseBody> |
emailRegisteredCheck(email) | String | ApiResponse<ResponseBody> |
getAppConfig() | — | ApiResponse<AppConfig> |
recoverLostPassword triggers a password-reset email to the supplied address. A success response indicates the email was dispatched; the response body carries no structured data. Show a confirmation message regardless of the outcome to avoid disclosing whether the address is registered.
when (val response = apiRepository.recoverLostPassword(email)) {
is ApiSuccessResponse -> showConfirmation("Check your email for a reset link.")
is ApiErrorResponse -> showError(response.errorMessage)
}
emailRegisteredCheck checks whether an email address already has an account before the user reaches the registration screen. Use it to branch between login and sign-up flows without exposing this information through a login error.
when (val response = apiRepository.emailRegisteredCheck(email)) {
is ApiSuccessResponse -> navigateToLogin(email) // address is registered
is ApiErrorResponse -> navigateToRegister(email) // address is not registered
}
getAppConfig returns the list of supported countries and their provinces/states. Call it once on startup (or before showing the registration form) and cache the result locally. The id values from Country and Province are the values that must be passed to WLCreateWarrantyModel.
when (val response = apiRepository.getAppConfig()) {
is ApiSuccessResponse -> {
val countries = response.data?.countries.orEmpty()
// populate country picker with countries
// when user selects a country, populate state picker with country.provinces
}
is ApiErrorResponse -> showError(response.errorMessage)
}
AppConfig model
class AppConfig {
val countries: List<Country>? // all supported countries
}
class Country {
val id: String? // pass as `country` in WLCreateWarrantyModel
val name: String // display name
val abbreviation: String? // e.g. "US", "CA"
val provinces: List<Province>? // states / provinces for this country
val postalCodeRegex: String? // full postal code validation pattern
val partialPostalCodeRegex: String? // partial / in-progress validation pattern
val postalCodeFormatDescription: String?// hint text for the postal code field
val minPhoneNumberDigits: Int? // minimum phone number length
val maxPhoneNumberDigits: Int? // maximum phone number length
}
class Province {
val id: String? // pass as `state` in WLCreateWarrantyModel
val name: String // display name, e.g. "Ontario"
val abbreviation: String? // short code, e.g. "ON"
val realid: Int? // internal server ID — not required for warranty creation
}
Important:
WLCreateWarrantyModel.countrymust be set toCountry.idandWLCreateWarrantyModel.statemust be set toProvince.id— not the display name or abbreviation. Passing a name or abbreviation will cause the server to reject the warranty creation request.
Voucher
| Function | Parameters | Returns |
|---|---|---|
getVoucherByVoucherCode(code, isPlanTransfer) | String, Boolean? | ApiResponse<Voucher> |
Device / Item
| Function | Parameters | Returns |
|---|---|---|
getItemByDeviceID() | — | ApiResponse<ItemByDeviceIdResponse> |
getItemByDeviceIdPublic() | — | ApiResponse<DeviceDetail> |
registerDevice(context) | Context | Flow<RegisterDeviceState> |
Warranty
| Function | Parameters | Returns |
|---|---|---|
createWarranty(context, model) | Context, WLCreateWarrantyModel | ApiResponse<Warranty> |
updateWarranty(context, warrantyId, model) | Context, String, WLCreateWarrantyModel | ApiResponse<Warranty> |
getAllWarranties() | — | ApiResponse<List<Warranty>> |
Notifications
| Function | Parameters | Returns |
|---|---|---|
updateFCMToken(itemId, token) | String, String | ApiResponse<UpdateItemResponse> |
Update the FCM token whenever Firebase delivers a new one:
WarrantyLifeSDK.updateFCMToken(context, fcmToken)
AppDataManager Reference
Obtain via WarrantyLifeSDK.getAppDataManager(context) or inject with Hilt.
Auth state
fun isUserLoggedIn(): Boolean
fun hasActiveCoverage(): Boolean
fun logout()
Voucher / registration
fun getCurrentVoucherCode(): String
fun isReceiptRequired(): Boolean
fun isPurchaseDateRequired(): Boolean
fun isDeviceTransferInProgress(): Boolean
fun getEncodedWarrantyID(): String?
Device
fun hasUserChangedDevice(context: Context): Boolean
fun setIMEI(imei: String)
fun setReceiptDate(date: String)
fun setReceiptImagePath(path: String?)
Smart Saver opt-ins
fun setSSR(context: Context, enabled: Boolean): OptInResult
fun setSSD(context: Context, enabled: Boolean): OptInResult
fun setTradeIn(context: Context, enabled: Boolean): OptInResult
fun getSSR(): Boolean
fun getSSD(): Boolean
fun getTradeIn(): Boolean
Offer availability
fun hasSsr(): Boolean // SSR offer available for the plan
fun hasSsd(): Boolean // SSD offer available for the plan
fun isTipOffered(): Boolean // Trade-In Protection offer available
fun getRewardAmountMax(): Int // Maximum reward amount (show Smart Saver screen when > 0)
fun getRewardAmount(): Int // Current earned reward amount
fun getPlanCurrency(): String // Currency code for reward display (e.g. "$")
fun getTradeInScore(): Float // Current device trade-in score; 0f if not yet received
Device ID testing helpers
Every warranty/plan is tied to the device ID of the physical handset that was registered. These two functions let you simulate a fresh, unregistered device during testing without wiping real data or using a second physical device.
| Function | Signature | Description |
|---|---|---|
setMockDeviceId() | () → Unit | Replaces the stored device ID with a randomly generated mock ID ("Mock_<uuid>"), logs the user out, and clears all session and voucher state. The SDK will no longer find a warranty for this ID, so the app behaves as if it has no coverage |
resetDeviceId(context) | (Context) → Unit | Restores the stored device ID to the real hardware device ID, undoing setMockDeviceId() |
// Simulate a device with no warranties (e.g. to test the registration flow)
appDataManager.setMockDeviceId()
// Then re-launch the app (or call trackAppLaunch) so the SDK re-evaluates coverage
// Restore the real device ID when done testing
appDataManager.resetDeviceId(context)
// Then re-launch the app so the SDK picks up the real coverage again
Typical testing workflow
- Call
appDataManager.setMockDeviceId()— the stored device ID is replaced with"Mock_<uuid>", and the user is logged out with all session and voucher state cleared. - Re-launch the app (or trigger
trackAppLaunch). The SDK callsgetItemByDeviceID()with the mock ID, finds no warranty, and the app enters its unregistered state. - Complete the registration / purchase flow you want to test.
- When finished, call
appDataManager.resetDeviceId(context)to restore the real device ID. - Re-launch the app so the SDK re-loads the real coverage.
Do not ship
setMockDeviceId()behind a user-facing toggle. It is intended for debug/QA builds only. Guard calls withBuildConfig.DEBUGor a dedicated QA flag.
Diagnostic requirements
suspend fun isAutoTestRequired(): Boolean
suspend fun isSensorTestRequired(): Boolean
suspend fun isFrontCameraTestRequired(): Boolean
suspend fun isBackCameraTestRequired(): Boolean
suspend fun isOpenFaceTestRequired(): Boolean
ProGuard / R8
# SDK models and repositories
-keep class com.warrantylife.sdk.model.** { *; }
-keep class com.warrantylife.sdk.repository.** { *; }
-keep interface com.warrantylife.sdk.api.ApiService { *; }
# Hilt entry points
-keep @dagger.hilt.InstallIn class * { *; }
-keep @dagger.hilt.EntryPoint interface * { *; }
# WorkManager workers (required for WLWorkerFactory reflection)
-keep class com.warrantylife.sdk.worker.** { *; }
-keep class com.warrantylife.sdk.dropdetection.worker.** { *; }
# Third-party
-keep class io.sentry.** { *; }
-keep class retrofit2.** { *; }
-keep class com.google.gson.** { *; }
-keepattributes *Annotation*
Notification Customisation
The SDK manages seven notifications used by its foreground services and background workers. Call WarrantyLifeSDK.updateNotification() to override the title, description, or deep-link intent for any of them. This must be called after WarrantyLifeSDK.build() — a call made before build() is ignored and an error is logged.
WLNotificationType reference
| Type | ID | Used by | Description |
|---|---|---|---|
DROP_DETECTION_SERVICE | 1001 | DropDetectionService | Persistent foreground notification while drop-detection is running |
DROP_DETECTED | 1002 | DropDetectionService | Shown when a drop impact is detected |
BACKGROUND_LOCATION | 1003 | BgLocationWorker | Shown while background location is being fetched |
DATA_SYNC | 1004 | UploadDropDataWorker | Shown while drop data is syncing to the server |
HEARTBEAT | 1005 | HeartbeatWorker | Shown during periodic heartbeat work |
RE_LOGIN | 1006 | SDK auth check | Shown when re-authentication is required; tap navigates to login |
REWARDS_REACTIVATION | 1007 | SDK | Shown when Smart Saver rewards need to be reactivated |
updateNotification signature
WarrantyLifeSDK.updateNotification(
type: WLNotificationType,
title: String? = null,
description: String? = null,
intentAction: String? = null,
intentData: String? = null
)
All parameters except type are optional — pass only the fields you want to override; unset fields preserve the existing stored value.
| Parameter | Type | Description |
|---|---|---|
type | WLNotificationType | The notification to update |
title | String? | Notification title. null keeps the existing value |
description | String? | Notification body text. null keeps the existing value |
intentAction | String? | Intent action string — use your app's component action (e.g. "com.yourapp.ACTION_MAIN") to bring the app to the foreground, or Intent.ACTION_VIEW for a deep link |
intentData | String? | URI for the intent. Required when intentAction is Intent.ACTION_VIEW. Use your app's deep-link scheme (e.g. "myapp://screen-navigation/login") |
Example — updating all notification types from a ViewModel
fun updateNotificationSettings(context: Context) {
// Persistent foreground notification while drop-detection service is active
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.DROP_DETECTION_SERVICE,
title = context.getString(R.string.app_name),
description = context.getString(R.string.your_smartsaver_rewards_are_active),
intentAction = "com.yourapp.ACTION_MAIN"
)
// Shown when a drop impact is detected
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.DROP_DETECTED,
title = context.getString(R.string.app_name),
description = context.getString(R.string.drop_impact_detected),
intentAction = "com.yourapp.ACTION_MAIN"
)
// Shown while background location is being fetched
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.BACKGROUND_LOCATION,
title = context.getString(R.string.app_name),
description = context.getString(R.string.fetching_location),
intentAction = "com.yourapp.ACTION_MAIN"
)
// Shown while drop data is syncing to the server
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.DATA_SYNC,
title = context.getString(R.string.app_name),
description = context.getString(R.string.drop_data_sync_notification),
intentAction = "com.yourapp.ACTION_MAIN"
)
// RE_LOGIN uses a deep link to navigate directly to the login screen
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.RE_LOGIN,
title = context.getString(R.string.app_name),
description = context.getString(R.string.re_login),
intentAction = Intent.ACTION_VIEW,
intentData = "myapp://screen-navigation/login"
)
// REWARDS_REACTIVATION deep-links to the Smart Saver reactivation screen
WarrantyLifeSDK.updateNotification(
type = WLNotificationType.REWARDS_REACTIVATION,
title = context.getString(R.string.app_name),
description = context.getString(
R.string.click_to_re_activate_your_smartsaver_bonus_of_up_to_s,
appDataManager.getPlanCurrency().getRewardMaxStaticAmount()
),
intentAction = Intent.ACTION_VIEW,
intentData = "myapp://screen-navigation/NOTIFICATION_START_SERVICE/true"
)
}
Call updateNotificationSettings once after WarrantyLifeSDK.build() returns — for example, in MainActivity.onCreate() or in a ViewModel that is initialised at startup.
RE_LOGINandREWARDS_REACTIVATIONuseIntent.ACTION_VIEWwith a deep-link URI so the notification tap navigates the user directly to the correct screen. All other types use a component action string to bring the app to the foreground without changing the current screen.
HEARTBEATis omitted from the example above because the SDK manages it internally. You may still customise it viaWLNotificationType.HEARTBEATif your app requires it.