Files
gh-asleep-ai-sleeptrack-ski…/skills/sleeptrack-android/SKILL.md
2025-11-29 17:58:23 +08:00

552 lines
17 KiB
Markdown

---
name: sleeptrack-android
description: 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`:
```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`:
```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
```kotlin
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
```kotlin
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
```kotlin
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:
```kotlin
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
```kotlin
@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
```kotlin
@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
1. **RECORD_AUDIO**: Microphone access for sleep sound recording
2. **POST_NOTIFICATIONS**: Android 13+ notification permission
3. **Battery Optimization**: Prevent app from being killed during tracking
### Permission Request Flow
```kotlin
// Request permissions sequentially
when {
!hasMicrophonePermission() -> requestMicrophone()
!hasNotificationPermission() -> requestNotification()
!isBatteryOptimizationIgnored() -> requestBatteryOptimization()
else -> allPermissionsGranted()
}
```
### Best Practices
1. **Request in sequence**: Request one permission at a time for better UX
2. **Show rationale**: Explain why each permission is needed before requesting
3. **Handle denial**: Provide fallback or guide users to settings
4. **Check on resume**: Re-check permissions when app resumes
```kotlin
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
```kotlin
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
```kotlin
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
```kotlin
@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
```kotlin
@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:
```kotlin
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:
```kotlin
// 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
```kotlin
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
```kotlin
@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:
```kotlin
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 implementations
- `references/ui_implementation_guide.md`: Complete ViewBinding and Jetpack Compose UI examples
- `references/testing_guide.md`: Comprehensive unit, integration, and UI testing guides
- `references/android_architecture_patterns.md`: Complete architecture examples from the official sample app
- `references/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:
1. **Test thoroughly**: Test on different Android versions (especially 13+ for notifications)
2. **Handle edge cases**: Low battery, airplane mode, app updates during tracking
3. **Fetch reports**: Use REST API or backend integration to retrieve sleep reports
4. **Build UI**: Create compelling visualizations of sleep data
5. **Analytics**: Track user engagement and sleep patterns
For backend report fetching and webhook integration, use the `sleeptrack-be` skill.