Skip to main content

WarrantyLife SDK Usage Guide

This guide provides an overview of how to integrate and use the WarrantyLife SDK in your application.

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 ProcessLifecycleOwner observer that calls trackAppLaunch() every time the app comes to the foreground

Important: The lifecycle observer is registered after apiClient is fully constructed, so trackAppLaunch always 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:

  1. Sends an APP_ENTERED_FOREGROUND sensor event to the server
  2. If logged in → calls getItemByDeviceID() then getAllWarranties() to refresh coverage and offer state; then checks hasUserChangedDevice() and fires setDeviceChangedCallback if true
  3. If not logged in → calls getItemByDeviceIdPublic() to check coverage without auth
  4. 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

FieldTypeDescription
actionVoucherActionNext action the app must take — see table below
registrationTypeRegistrationTypeForm variant for the registration screen — see §Auth
originalDeviceVoucherDevice?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
ownerUser?Account that owns the plan. Email is the source for LOCKED_EMAIL_REGISTRATION
requiresReceiptBoolean?Whether a receipt image must be captured before IMEI entry
verifiedCustomerPurchaseDateString?Pre-filled purchase date if supplied by the retailer. null means the user must enter it
isDeviceTransferableBoolean?Whether the plan can be transferred to a different device
rewardAmountMaxInt?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.

ActionisErrorMeaningRecommended handling
NORMAL_REGISTRATIONfalseDefault — no special conditionsProceed to next step
DEVICE_TRANSFERABLE_POPUPfalseCoverage bought for a different device; plan is transferableShow confirm dialog — user confirms they are on the original device → proceed; declines → stay
DEVICE_NON_TRANSFERABLE_POPUPfalseCoverage bought for a different device; plan is not transferableShow info dialog — user cannot proceed
USER_NOT_LOGGED_IN_CLAIMEDfalsePlan already registered; user is not logged inShow info dialog → navigate to Login
DIFFERENT_EMAIL_CLAIMEDfalsePlan claimed under a different accountShow info dialog — user cannot proceed
ACTIVE_WARRANTY_CONFLICTtrueActive warranties on this device under another accountShow error message — contact support
WARRANTY_NOT_TRANSFERABLEtrueSelected plan is non-transferableShow error message
START_WARRANTY_TRANSFERfalsePlan transfer should be initiatedNavigate 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 from voucher.voucherItem?.model and voucher.voucherItem?.serialNumber. See the VoucherItem reference 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.

TypePassword fieldEmail fieldDescription
NORMAL_REGISTRATIONRequiredUser-enteredStandard account creation
PASSWORDLESS_REGISTRATIONHiddenUser-enteredAccount created without a password
LOCKED_EMAIL_REGISTRATIONRequiredPre-filled, read-onlyEmail 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

StatePayloadWhen it fires
SubmittedWorker has been enqueued and upload is about to begin — use this to navigate away or show a loader
Progresspercentage: IntUpload is in progress; emitted repeatedly as each file is sent
SuccessAll data uploaded successfully; proceed to the next registration step
ValidationErrorerror: WLSDKErrorA required piece of data is missing before any network call is made
UploadErrormessage: String, code: IntThe server returned an error response to an API call during upload

ValidationError vs UploadError: ValidationError is a local pre-flight check — the SDK detects missing data and fails fast without hitting the network. UploadError means the request reached the server and the server rejected it (e.g. HTTP 4xx/5xx); code contains the HTTP status code.

WLSDKErrorValidationError cases

Each WLSDKError value tells you exactly what is missing and what the user must do before retrying:

ErrorMeaningRecommended action
MISSING_IMEINo IMEI has been setShow error on the IMEI field; user must re-enter
MISSING_RECEIPT_IMAGEReceipt image path is not setNavigate back to the receipt capture screen
MISSING_SENSOR_TEST_DATASensor test was not completedNavigate to the sensor test screen
MISSING_AUTO_SYSTEM_TEST_DATAAuto system test was not completedNavigate to the auto-system test screen
MISSING_FRONT_GLASS_IMAGEFront glass photo is missingReset front glass test status; navigate to front glass test
MISSING_BACK_GLASS_IMAGEBack glass photo is missingReset back glass test status; navigate to back glass test
MISSING_OPEN_FACE_TEST_IMAGEOpen face photo is missingReset 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:

FieldTypeDescription
itemIdsList<String>Item ID from registerDevice
voucherCodeStringValidated voucher code
firstName / lastNameStringUser name
companyString?Optional company
streetAddress, city, zipStringAddress fields
stateStringProvince.id from getAppConfig() — not the display name or abbreviation
countryStringCountry.id from getAppConfig() — not the display name or abbreviation
phoneStringContact number
dateOfPurchaseString?"yyyy-MM-dd" format
acceptedMarketingBooleanMarketing consent
isDeviceInsuredBoolean?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)

CheckError returned
User not logged inOptInError.NOT_LOGGED_IN
No active coverageOptInError.NO_ACTIVE_COVERAGE
Notification permission deniedOptInError.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
MethodReturnsDescription
hasSsr()BooleanWhether the SSR (Safe Driving Reward) offer is available for the plan
hasSsd()BooleanWhether the SSD (Safe Driving Discount) offer is available for the plan
isTipOffered()BooleanWhether the Trade-In Protection offer is available for the plan
getRewardAmountMax()IntMaximum reward amount available (show Smart Saver screen when > 0)
getRewardAmount()IntCurrent earned reward amount
getPlanCurrency()StringCurrency code for reward amounts (e.g. "$")
getTradeInScore()FloatCurrent 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 via getItemByDeviceID(). Display it alongside the Trade-In Protection offer to show users their device's current estimated trade-in value. A score of 0f indicates 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:

  1. User is logged in
  2. Has active coverage
  3. At least one of SSR, SSD, or Trade-In is enabled
  4. 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

FunctionParametersReturns
login(email, password)String, StringApiResponse<LoginResponse>
registerNewUser(email, password, firstName, lastName, phone, street, city, state, country, zip, company)String...ApiResponse<LoginResponse>
recoverLostPassword(email)StringApiResponse<ResponseBody>
emailRegisteredCheck(email)StringApiResponse<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.country must be set to Country.id and WLCreateWarrantyModel.state must be set to Province.idnot the display name or abbreviation. Passing a name or abbreviation will cause the server to reject the warranty creation request.

Voucher

FunctionParametersReturns
getVoucherByVoucherCode(code, isPlanTransfer)String, Boolean?ApiResponse<Voucher>

Device / Item

FunctionParametersReturns
getItemByDeviceID()ApiResponse<ItemByDeviceIdResponse>
getItemByDeviceIdPublic()ApiResponse<DeviceDetail>
registerDevice(context)ContextFlow<RegisterDeviceState>

Warranty

FunctionParametersReturns
createWarranty(context, model)Context, WLCreateWarrantyModelApiResponse<Warranty>
updateWarranty(context, warrantyId, model)Context, String, WLCreateWarrantyModelApiResponse<Warranty>
getAllWarranties()ApiResponse<List<Warranty>>

Notifications

FunctionParametersReturns
updateFCMToken(itemId, token)String, StringApiResponse<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.

FunctionSignatureDescription
setMockDeviceId()() → UnitReplaces 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) → UnitRestores 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

  1. 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.
  2. Re-launch the app (or trigger trackAppLaunch). The SDK calls getItemByDeviceID() with the mock ID, finds no warranty, and the app enters its unregistered state.
  3. Complete the registration / purchase flow you want to test.
  4. When finished, call appDataManager.resetDeviceId(context) to restore the real device ID.
  5. 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 with BuildConfig.DEBUG or 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

TypeIDUsed byDescription
DROP_DETECTION_SERVICE1001DropDetectionServicePersistent foreground notification while drop-detection is running
DROP_DETECTED1002DropDetectionServiceShown when a drop impact is detected
BACKGROUND_LOCATION1003BgLocationWorkerShown while background location is being fetched
DATA_SYNC1004UploadDropDataWorkerShown while drop data is syncing to the server
HEARTBEAT1005HeartbeatWorkerShown during periodic heartbeat work
RE_LOGIN1006SDK auth checkShown when re-authentication is required; tap navigates to login
REWARDS_REACTIVATION1007SDKShown 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.

ParameterTypeDescription
typeWLNotificationTypeThe notification to update
titleString?Notification title. null keeps the existing value
descriptionString?Notification body text. null keeps the existing value
intentActionString?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
intentDataString?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_LOGIN and REWARDS_REACTIVATION use Intent.ACTION_VIEW with 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.

HEARTBEAT is omitted from the example above because the SDK manages it internally. You may still customise it via WLNotificationType.HEARTBEAT if your app requires it.