17 KiB
name, description
| name | description |
|---|---|
| sleeptrack-android | This skill helps Android developers integrate the Asleep SDK for sleep tracking functionality. Use this skill when developers ask about Android implementation, MVVM architecture patterns, permission handling, Jetpack Compose UI, Kotlin coroutines integration, or Android-specific sleep tracking features. This skill provides working code examples from the official sample app. |
Sleeptrack Android
Overview
This skill provides comprehensive guidance for integrating the Asleep sleep tracking SDK into Android applications. It covers Android-specific implementation details including MVVM architecture, permission handling, state management, UI patterns, and lifecycle management.
Use this skill when developers need to:
- Set up Asleep SDK in Android projects
- Implement sleep tracking with proper Android architecture
- Handle Android-specific permissions (microphone, notifications, battery optimization)
- Manage tracking lifecycle with state machines
- Build UI with ViewBinding or Jetpack Compose
- Handle errors and edge cases in Android environment
- Implement foreground services for background tracking
Prerequisites: Review the sleeptrack-foundation skill first for core concepts including session lifecycle, error codes, and API fundamentals.
Quick Start
1. Add Dependencies
Add to your app-level build.gradle:
dependencies {
// Core dependencies
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
// Required for Asleep SDK
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.google.code.gson:gson:2.10'
implementation 'ai.asleep:asleepsdk:3.1.4'
// Optional: Hilt for DI
implementation "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-compiler:2.48"
}
Minimum Requirements:
- minSdk: 24 (Android 7.0)
- targetSdk: 34
- Java: 17
- Kotlin: 1.9.24+
See references/gradle_setup.md for complete Gradle configuration.
2. Configure Permissions
Add to AndroidManifest.xml:
<manifest>
<!-- Essential permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<!-- Battery optimization -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Notifications (Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>
3. Initialize SDK
Asleep.initAsleepConfig(
context = applicationContext,
apiKey = "your_api_key_here",
userId = "unique_user_id",
baseUrl = "https://api.asleep.ai",
callbackUrl = null, // Optional webhook URL
service = "YourAppName",
asleepConfigListener = object : Asleep.AsleepConfigListener {
override fun onSuccess(userId: String?, asleepConfig: AsleepConfig?) {
// SDK initialized successfully
}
override fun onFail(errorCode: Int, detail: String) {
Log.e("Asleep", "Init failed: $errorCode - $detail")
}
}
)
4. Start Sleep Tracking
Asleep.beginSleepTracking(
asleepConfig = asleepConfig,
asleepTrackingListener = object : Asleep.AsleepTrackingListener {
override fun onStart(sessionId: String) {
// Tracking started successfully
}
override fun onPerform(sequence: Int) {
// Called every ~30 seconds (1 sequence)
}
override fun onFinish(sessionId: String?) {
// Tracking completed
}
override fun onFail(errorCode: Int, detail: String) {
// Handle tracking errors
}
},
notificationTitle = "Sleep Tracking Active",
notificationText = "Tap to return to app",
notificationIcon = R.drawable.ic_notification,
notificationClass = MainActivity::class.java
)
5. Stop Tracking
Asleep.endSleepTracking()
Android Architecture Pattern (MVVM + Hilt)
The recommended architecture follows Android best practices with MVVM pattern, Hilt dependency injection, and proper state management.
State Management
Define tracking states using sealed classes:
sealed class AsleepState {
data object STATE_IDLE: AsleepState()
data object STATE_INITIALIZING : AsleepState()
data object STATE_INITIALIZED : AsleepState()
data object STATE_TRACKING_STARTING : AsleepState()
data object STATE_TRACKING_STARTED : AsleepState()
data object STATE_TRACKING_STOPPING : AsleepState()
data class STATE_ERROR(val errorCode: AsleepError) : AsleepState()
}
data class AsleepError(val code: Int, val message: String)
Basic ViewModel Pattern
@HiltViewModel
class SleepTrackingViewModel @Inject constructor(
@ApplicationContext private val app: Application
) : ViewModel() {
private val _trackingState = MutableStateFlow<AsleepState>(AsleepState.STATE_IDLE)
val trackingState: StateFlow<AsleepState> = _trackingState
private var config: AsleepConfig? = null
fun initializeSDK(userId: String) {
Asleep.initAsleepConfig(
context = app,
apiKey = BuildConfig.ASLEEP_API_KEY,
userId = userId,
baseUrl = "https://api.asleep.ai",
callbackUrl = null,
service = "MyApp",
asleepConfigListener = object : Asleep.AsleepConfigListener {
override fun onSuccess(userId: String?, asleepConfig: AsleepConfig?) {
config = asleepConfig
_trackingState.value = AsleepState.STATE_INITIALIZED
}
override fun onFail(errorCode: Int, detail: String) {
_trackingState.value = AsleepState.STATE_ERROR(AsleepError(errorCode, detail))
}
}
)
}
fun startTracking() {
config?.let {
Asleep.beginSleepTracking(
asleepConfig = it,
asleepTrackingListener = trackingListener,
notificationTitle = "Sleep Tracking",
notificationText = "Active",
notificationIcon = R.drawable.ic_notification,
notificationClass = MainActivity::class.java
)
}
}
fun stopTracking() {
Asleep.endSleepTracking()
}
}
For complete production-ready ViewModel with real-time data, error handling, and lifecycle management, see references/complete_viewmodel_implementation.md.
Basic Activity Pattern
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: SleepTrackingViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Setup button
binding.btnTrack.setOnClickListener {
when (viewModel.trackingState.value) {
AsleepState.STATE_INITIALIZED -> viewModel.startTracking()
AsleepState.STATE_TRACKING_STARTED -> viewModel.stopTracking()
else -> {}
}
}
// Observe state
lifecycleScope.launch {
viewModel.trackingState.collect { state ->
updateUI(state)
}
}
}
}
For complete Activity implementation with permission handling, see references/complete_viewmodel_implementation.md.
Permission Handling
Android requires multiple runtime permissions for sleep tracking:
Required Permissions
- RECORD_AUDIO: Microphone access for sleep sound recording
- POST_NOTIFICATIONS: Android 13+ notification permission
- Battery Optimization: Prevent app from being killed during tracking
Permission Request Flow
// Request permissions sequentially
when {
!hasMicrophonePermission() -> requestMicrophone()
!hasNotificationPermission() -> requestNotification()
!isBatteryOptimizationIgnored() -> requestBatteryOptimization()
else -> allPermissionsGranted()
}
Best Practices
- Request in sequence: Request one permission at a time for better UX
- Show rationale: Explain why each permission is needed before requesting
- Handle denial: Provide fallback or guide users to settings
- Check on resume: Re-check permissions when app resumes
override fun onResume() {
super.onResume()
if (!hasRequiredPermissions() && Asleep.isSleepTrackingAlive(applicationContext)) {
handlePermissionLoss()
}
}
For complete PermissionManager implementation, see references/complete_viewmodel_implementation.md.
Error Handling
Distinguish between critical errors and warnings:
Error Classification
fun isWarning(errorCode: Int): Boolean {
return errorCode in setOf(
AsleepErrorCode.ERR_AUDIO_SILENCED,
AsleepErrorCode.ERR_UPLOAD_FAILED
)
}
fun handleError(error: AsleepError) {
if (isWarning(error.code)) {
// Show warning, continue tracking
Toast.makeText(context, getUserFriendlyMessage(error), Toast.LENGTH_SHORT).show()
} else {
// Critical error - stop tracking
stopTracking()
showErrorDialog(getUserFriendlyMessage(error))
}
}
UI Patterns
ViewBinding Example
class TrackingFragment : Fragment() {
private var _binding: FragmentTrackingBinding? = null
private val binding get() = _binding!!
private val viewModel: SleepTrackingViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.trackingState.collect { state ->
when (state) {
AsleepState.STATE_TRACKING_STARTED -> {
binding.btnTrack.text = "Stop Tracking"
}
AsleepState.STATE_INITIALIZED -> {
binding.btnTrack.text = "Start Tracking"
}
else -> {}
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Jetpack Compose Example
@Composable
fun SleepTrackingScreen(
viewModel: SleepTrackingViewModel = hiltViewModel()
) {
val trackingState by viewModel.trackingState.collectAsState()
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
when (trackingState) {
AsleepState.STATE_TRACKING_STARTED -> {
Button(onClick = { viewModel.stopTracking() }) {
Text("Stop Tracking")
}
}
AsleepState.STATE_INITIALIZED -> {
Button(onClick = { viewModel.startTracking() }) {
Text("Start Sleep Tracking")
}
}
is AsleepState.STATE_ERROR -> {
ErrorDisplay(error = (trackingState as AsleepState.STATE_ERROR).errorCode)
}
else -> {
CircularProgressIndicator()
}
}
}
}
For complete UI implementations with Material3 components, see references/ui_implementation_guide.md.
Lifecycle Management
Handle App Lifecycle Events
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
// Reconnect to existing tracking session
if (Asleep.isSleepTrackingAlive(applicationContext)) {
viewModel.reconnectToTracking()
}
}
override fun onStop() {
super.onStop()
// Tracking continues in background via foreground service
// No action needed
}
override fun onDestroy() {
super.onDestroy()
// Do NOT call endSleepTracking() here
// Service continues in background
}
}
Foreground Service
The Asleep SDK automatically manages a foreground service during tracking. The notification keeps the service alive:
Asleep.beginSleepTracking(
asleepConfig = config,
asleepTrackingListener = listener,
notificationTitle = "Sleep Tracking Active",
notificationText = "Tracking your sleep patterns",
notificationIcon = R.drawable.ic_sleep,
notificationClass = MainActivity::class.java // Tapping notification opens this
)
The service will:
- Keep the app alive during sleep tracking
- Show persistent notification
- Maintain microphone access
- Continue even if user swipes away the app
Real-Time Sleep Data
Access preliminary sleep data during tracking:
// In ViewModel
override fun onPerform(sequence: Int) {
// Check after sequence 10, then every 10 sequences
if (sequence > 10 && sequence % 10 == 0) {
getCurrentSleepData()
}
}
private fun getCurrentSleepData() {
Asleep.getCurrentSleepData(
asleepSleepDataListener = object : Asleep.AsleepSleepDataListener {
override fun onSleepDataReceived(session: Session) {
val currentSleepStage = session.sleepStages?.lastOrNull()
val currentSnoringStage = session.snoringStages?.lastOrNull()
Log.d("Sleep", "Current stage: $currentSleepStage")
}
override fun onFail(errorCode: Int, detail: String) {
Log.e("Sleep", "Failed to get current data: $errorCode")
}
}
)
}
Note: Real-time data is preliminary and may differ from final report after processing.
Testing
Unit Testing
class SleepTrackingViewModelTest {
@get:Rule val mainDispatcherRule = MainDispatcherRule()
@Test
fun `startTracking should fail if not initialized`() = runTest {
viewModel.startTracking()
assertNotEquals(AsleepState.STATE_TRACKING_STARTED, viewModel.trackingState.value)
}
}
Integration Testing
@Test
fun trackingFlow_complete() {
onView(withId(R.id.btn_start_stop)).perform(click())
onView(withId(R.id.tracking_indicator)).check(matches(isDisplayed()))
}
For complete testing guide with Compose UI tests and test utilities, see references/testing_guide.md.
Common Issues & Solutions
Tracking stops unexpectedly
Causes: Battery optimization, notification dismissed, permission revoked, microphone conflict
Solution: Check battery optimization and permissions on resume:
override fun onResume() {
super.onResume()
if (!hasRequiredPermissions() && Asleep.isSleepTrackingAlive(applicationContext)) {
handlePermissionLoss()
}
}
No real-time data available
Cause: Checking before sequence 10
Solution: Only call getCurrentSleepData() after sequence 10
ERR_UPLOAD_FORBIDDEN error
Cause: Same user_id tracking on multiple devices
Solution: Use unique user IDs per device or check for active sessions before starting
Resources
This skill includes detailed reference documentation:
references/complete_viewmodel_implementation.md: Complete ViewModel, Activity, and PermissionManager implementationsreferences/ui_implementation_guide.md: Complete ViewBinding and Jetpack Compose UI examplesreferences/testing_guide.md: Comprehensive unit, integration, and UI testing guidesreferences/android_architecture_patterns.md: Complete architecture examples from the official sample appreferences/gradle_setup.md: Comprehensive Gradle configuration including dependencies and ProGuard rules
Official Documentation
- Android Getting Started: https://docs-en.asleep.ai/docs/android-get-started.md
- AsleepConfig Reference: https://docs-en.asleep.ai/docs/android-asleep-config.md
- SleepTrackingManager: https://docs-en.asleep.ai/docs/android-sleep-tracking-manager.md
- Error Codes: https://docs-en.asleep.ai/docs/android-error-codes.md
Android Resources
- Android Permissions: https://developer.android.com/guide/topics/permissions/overview
- Foreground Services: https://developer.android.com/develop/background-work/services/foreground-services
- StateFlow Guide: https://developer.android.com/kotlin/flow/stateflow-and-sharedflow
- Hilt Documentation: https://developer.android.com/training/dependency-injection/hilt-android
Next Steps
After implementing Android sleep tracking:
- Test thoroughly: Test on different Android versions (especially 13+ for notifications)
- Handle edge cases: Low battery, airplane mode, app updates during tracking
- Fetch reports: Use REST API or backend integration to retrieve sleep reports
- Build UI: Create compelling visualizations of sleep data
- Analytics: Track user engagement and sleep patterns
For backend report fetching and webhook integration, use the sleeptrack-be skill.