Scrivi dati

Questa guida è compatibile con la versione 1.1.0-alpha12 di Connessione Salute.

Questa guida illustra la procedura di scrittura o aggiornamento dei dati in Connessione Salute.

Gestire i valori zero

Alcuni tipi di dati come passi, distanza o calorie potrebbero avere un valore di 0. Scrivi solo valori pari a zero quando riflettono una vera inattività mentre l'utente indossava il dispositivo. Non scrivere valori pari a zero se il dispositivo non è stato indossato, se mancano dati o se la batteria è scarica. In questi casi, ometti il record per evitare dati fuorvianti.

Configurare la struttura dei dati

Prima di scrivere i dati, dobbiamo configurare i record. Per più di 50 tipi di dati, ognuno con le rispettive strutture. Per ulteriori dettagli sui tipi di dati disponibili, consulta le Informazioni di riferimento su Jetpack.

Record di base

Il tipo di dati Passi in Connessione Salute acquisisce il numero di passi effettuati da un utente tra una lettura e l'altra. Il conteggio dei passi rappresenta una misurazione comune su piattaforme di salute, fitness e benessere.

Il seguente esempio mostra come impostare i dati del conteggio dei passi:

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 120,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    )
)

Record con unità di misura

Connessione Salute può memorizzare i valori insieme alle relative unità di misura per fornire accuratezza. Un esempio è il tipo di dati Nutrizione, che è vasto e completo. Include un'ampia varietà di campi facoltativi per i nutrienti, che vanno dai carboidrati totali alle vitamine. Ogni punto dati rappresenta i nutrienti che sono stati potenzialmente consumati come parte di un pasto o di un alimento.

In questo tipo di dati, tutti i nutrienti sono rappresentati in unità di Mass, mentre energy è rappresentato in unità di Energy.

L'esempio seguente mostra come impostare i dati nutrizionali per un utente che ha mangiato una banana:

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(1))

val banana = NutritionRecord(
    name = "banana",
    energy = 105.0.kilocalories,
    dietaryFiber = 3.1.grams,
    potassium = 0.422.grams,
    totalCarbohydrate = 27.0.grams,
    totalFat = 0.4.grams,
    saturatedFat = 0.1.grams,
    sodium = 0.001.grams,
    sugar = 14.0.grams,
    vitaminB6 = 0.0005.grams,
    vitaminC = 0.0103.grams,
    startTime = startTime,
    endTime = endTime,
    startZoneOffset = ZoneOffset.UTC,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        device = Device(type = Device.TYPE_PHONE)
    )
)

Record con dati di serie

Connessione Salute può memorizzare un elenco di dati delle serie. Un esempio è il tipo di dati Battito cardiaco, che acquisisce una serie di campioni di battiti rilevati tra le letture.

In questo tipo di dati, il parametro samples è rappresentato da un elenco di campioni di battito cardiaco. Ogni campione contiene un valore beatsPerMinute e un valore time.

Il seguente esempio mostra come impostare i dati delle serie di battito cardiaco:

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(5))

val heartRateRecord = HeartRateRecord(
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    // records 10 arbitrary data, to replace with actual data
    samples = List(10) { index ->
        HeartRateRecord.Sample(
            time = startTime + Duration.ofSeconds(index.toLong()),
            beatsPerMinute = 100 + index.toLong(),
        )
    },
    metadata = Metadata.autoRecorded(
        device = Device(type = Device.TYPE_WATCH)
    ))

Richiedere le autorizzazioni all'utente

Dopo aver creato un'istanza client, l'app deve richiedere le autorizzazioni all'utente. Gli utenti devono poter concedere o negare le autorizzazioni in qualsiasi momento.

A questo scopo, crea un insieme di autorizzazioni per i tipi di dati richiesti. Assicurati che le autorizzazioni nel set siano dichiarate prima nel file manifest di Android.

// Create a set of permissions for required data types
val PERMISSIONS =
    setOf(
  HealthPermission.getReadPermission(HeartRateRecord::class),
  HealthPermission.getWritePermission(HeartRateRecord::class),
  HealthPermission.getReadPermission(StepsRecord::class),
  HealthPermission.getWritePermission(StepsRecord::class)
)

Utilizza getGrantedPermissions per verificare se alla tua app sono già state concesse le autorizzazioni richieste. In caso contrario, utilizza createRequestPermissionResultContract per richiedere queste autorizzazioni. Viene visualizzata la schermata delle autorizzazioni di Connessione Salute.

// Create the permissions launcher
val requestPermissionActivityContract = PermissionController.createRequestPermissionResultContract()

val requestPermissions = registerForActivityResult(requestPermissionActivityContract) { granted ->
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions successfully granted
  } else {
    // Lack of required permissions
  }
}

suspend fun checkPermissionsAndRun(healthConnectClient: HealthConnectClient) {
  val granted = healthConnectClient.permissionController.getGrantedPermissions()
  if (granted.containsAll(PERMISSIONS)) {
    // Permissions already granted; proceed with inserting or reading data
  } else {
    requestPermissions.launch(PERMISSIONS)
  }
}

Poiché gli utenti possono concedere o revocare le autorizzazioni in qualsiasi momento, la tua app deve controllare periodicamente le autorizzazioni concesse e gestire gli scenari in cui l'autorizzazione viene persa.

Scrittura di dati

Uno dei flussi di lavoro comuni in Connessione Salute è la scrittura dei dati. Per aggiungere record, utilizza insertRecords.

Il seguente esempio mostra come scrivere i conteggi dei passi di inserimento dei dati:

suspend fun insertSteps(healthConnectClient: HealthConnectClient) {
    val endTime = Instant.now()
    val startTime = endTime.minus(Duration.ofMinutes(5))
    try {
        val stepsRecord = StepsRecord(
            count = 120,
            startTime = startTime,
            endTime = endTime,
            startZoneOffset = ZoneOffset.UTC,
            endZoneOffset = ZoneOffset.UTC,
            metadata = Metadata.autoRecorded(
                device = Device(type = Device.TYPE_WATCH)
            )
        )
        healthConnectClient.insertRecords(listOf(stepsRecord))
    } catch (e: Exception) {
        // Run error handling here
    }
}

Aggiorna dati

Se devi modificare uno o più record, soprattutto quando devi sincronizzare l'archivio dati dell'app con i dati di Connessione Salute, puoi aggiornare i tuoi dati. Esistono due modi per aggiornare i dati esistenti, a seconda dell'identificatore utilizzato per trovare i record.

Metadati

Vale la pena esaminare prima la classe Metadata, poiché è necessaria quando si aggiornano i dati. Al momento della creazione, ogni Record in Connessione Salute ha un campo metadata. Le seguenti proprietà sono pertinenti per la sincronizzazione:

Proprietà Descrizione
id Ogni Record in Health Connect ha un valore id univoco.
Connessione Salute compila automaticamente questo campo quando inserisci un nuovo record.
lastModifiedTime Ogni Record tiene traccia anche dell'ultima modifica del record.
Connessione Salute compila automaticamente questo campo.
clientRecordId Ogni Record può avere un ID univoco associato che funge da riferimento nel datastore dell'app.
La tua app fornisce questo valore.
clientRecordVersion Se un record contiene clientRecordId, clientRecordVersion può essere utilizzato per consentire la sincronizzazione dei dati con la versione nel datastore dell'app.
La tua app fornisce questo valore.

Aggiornamento dopo la lettura per intervallo di tempo

Per aggiornare i dati, prepara prima i record necessari. Apporta le modifiche necessarie ai record. Poi chiama il numero updateRecords per apportare le modifiche.

Il seguente esempio mostra come aggiornare i dati. A questo scopo, i valori di offset del fuso orario di ogni record vengono modificati in PST.

suspend fun updateSteps(
    healthConnectClient: HealthConnectClient,
    prevRecordStartTime: Instant,
    prevRecordEndTime: Instant
) {
    try {
        val request = healthConnectClient.readRecords(
            ReadRecordsRequest(
                recordType = StepsRecord::class, timeRangeFilter = TimeRangeFilter.between(
                    prevRecordStartTime, prevRecordEndTime
                )
            )
        )

        val newStepsRecords = arrayListOf<StepsRecord>()
        for (record in request.records) {
            // Adjusted both offset values to reflect changes
            val sr = StepsRecord(
                count = record.count,
                startTime = record.startTime,
                startZoneOffset = record.startTime.atZone(ZoneId.of("PST")).offset,
                endTime = record.endTime,
                endZoneOffset = record.endTime.atZone(ZoneId.of("PST")).offset,
                metadata = record.metadata
            )
            newStepsRecords.add(sr)
        }

        healthConnectClient.updateRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

Upsert tramite ID record cliente

Se utilizzi i valori facoltativi ID record cliente e Versione record cliente, ti consigliamo di utilizzare insertRecords anziché updateRecords.

La funzione insertRecords è in grado di eseguire l'upsert dei dati. Se i dati esistono in Connessione Salute in base al set specificato di ID record client, vengono sovrascritti. In caso contrario, viene scritto come nuovi dati. Questo scenario è utile ogni volta che devi sincronizzare i dati dal datastore dell'app a Connessione Salute.

Il seguente esempio mostra come eseguire un'operazione di upsert sui dati estratti dall'archivio dati dell'app:

suspend fun pullStepsFromDatastore() : ArrayList<StepsRecord> {
    val appStepsRecords = arrayListOf<StepsRecord>()
    // Pull data from app datastore
    // ...
    // Make changes to data if necessary
    // ...
    // Store data in appStepsRecords
    // ...
    var sr = StepsRecord(
        metadata = Metadata.autoRecorded(
            clientRecordId = "Your client record ID",
            device = Device(type = Device.TYPE_WATCH)
        ),
        // Assign more parameters for this record
    )
    appStepsRecords.add(sr)
    // ...
    return appStepsRecords
}

suspend fun upsertSteps(
    healthConnectClient: HealthConnectClient,
    newStepsRecords: ArrayList<StepsRecord>
) {
    try {
        healthConnectClient.insertRecords(newStepsRecords)
    } catch (e: Exception) {
        // Run error handling here
    }
}

Dopodiché, puoi chiamare queste funzioni nel thread principale.

upsertSteps(healthConnectClient, pullStepsFromDatastore())

Controllo del valore nella versione del record cliente

Se la procedura di upsert dei dati include la versione del record cliente, Connessione Salute esegue controlli di confronto nei valori clientRecordVersion. Se la versione dei dati inseriti è superiore a quella dei dati esistenti, viene eseguito l'upsert. In caso contrario, la procedura ignora la modifica e il valore rimane invariato.

Per includere il controllo delle versioni nei dati, devi fornire Metadata.clientRecordVersion con un valore Long in base alla logica di controllo delle versioni.

val endTime = Instant.now()
val startTime = endTime.minus(Duration.ofMinutes(15))

val stepsRecord = StepsRecord(
    count = 100L,
    startTime = startTime,
    startZoneOffset = ZoneOffset.UTC,
    endTime = endTime,
    endZoneOffset = ZoneOffset.UTC,
    metadata = Metadata.manualEntry(
        clientRecordId = "Your supplied record ID",
        clientRecordVersion = 0L, // Your supplied record version
        device = Device(type = Device.TYPE_WATCH)
    )
)

Gli upsert non incrementano automaticamente version ogni volta che vengono apportate modifiche, impedendo qualsiasi istanza imprevista di sovrascrittura dei dati. In questo caso, devi fornire manualmente un valore più alto.

Best practice per la scrittura dei dati

Le app devono scrivere in Connessione Salute solo dati di propria origine.

Se i dati della tua app sono stati importati da un'altra app, la responsabilità di scrivere i propri dati in Connessione Salute ricade sull'altra app.

È anche consigliabile implementare una logica che gestisca le eccezioni di scrittura, ad esempio i dati al di fuori dei limiti o un errore del sistema interno. Puoi applicare le tue strategie di backoff e ripetizione a un meccanismo di pianificazione dei job. Se la scrittura in Connessione Salute non va a buon fine, assicurati che la tua app possa superare questo punto di esportazione. Non dimenticare di registrare e segnalare gli errori per facilitare la diagnosi.

Quando monitori i dati, puoi seguire alcuni suggerimenti a seconda del modo in cui la tua app scrive i dati.

Gestione del fuso orario

Quando scrivi record basati sul tempo, evita di impostare offset su zoneOffset.UTC per impostazione predefinita, perché ciò può comportare timestamp imprecisi quando gli utenti si trovano in altre zone. Calcola invece l'offset in base alla posizione effettiva del dispositivo. Puoi recuperare il fuso orario del dispositivo utilizzando ZoneId.systemDefault().

val endTime = Instant.now()
val startTime = endTime.minus(java.time.Duration.ofDays(1))
val stepsRecords = mutableListOf<StepsRecord>()
var sampleTime = startTime
val minutesBetweenSamples = 15L
while (sampleTime < endTime) {
    // Get the default ZoneId then convert it to an offset
    val zoneOffset = ZoneOffset.systemDefault().rules.getOffset(sampleTime)
    stepsRecords += StepsRecord(
        startTime = sampleTime.minus(java.time.Duration.ofMinutes(minutesBetweenSamples)),
        startZoneOffset = zoneOffset,
        endTime = sampleTime,
        endZoneOffset = zoneOffset,
        count = Random.nextLong(1, 100),
        metadata = Metadata.unknownRecordingMethod(),
    )
    sampleTime = sampleTime.plus(java.time.Duration.ofMinutes(minutesBetweenSamples))
}
healthConnectClient.insertRecords(
    stepsRecords
)

Per ulteriori dettagli, consulta la documentazione relativa a ZoneId.

Monitoraggio passivo

Sono incluse le app che eseguono il monitoraggio passivo di fitness o salute, ad esempio la registrazione continua in background di passi o frequenza cardiaca.

La tua app deve scrivere periodicamente i dati in Connessione Salute nei seguenti modi:

  • A ogni sincronizzazione, vengono scritti solo i dati nuovi e quelli aggiornati modificati dall'ultima sincronizzazione.
  • Dividi le richieste in blocchi di massimo 1000 record per richiesta di scrittura.
  • Utilizza WorkManager per pianificare attività periodiche in background, con un periodo di tempo di almeno 15 minuti.
  • Limita l'esecuzione delle attività solo quando il dispositivo è inattivo e la batteria non è in esaurimento.
val constraints = Constraints.Builder()
    .requiresBatteryNotLow()
    .requiresDeviceIdle(true)
    .build()

val writeDataWork = PeriodicWorkRequestBuilder<WriteDataToHealthConnectWorker>(
        15,
        TimeUnit.MINUTES,
        5,
        TimeUnit.MINUTES
    )
    .setConstraints(constraints)
    .build()

Monitoraggio attivo

Sono incluse le app che eseguono il monitoraggio basato su eventi come allenamento e sonno o l'inserimento manuale di dati da parte dell'utente, ad esempio l'alimentazione. Questi record vengono creati quando l'app è in primo piano o in rari casi in cui viene utilizzata più volte al giorno.

Assicurati che la tua app non mantenga Connessione Salute in esecuzione per l'intera durata dell'evento.

I dati devono essere scritti in Connessione Salute in uno dei due modi seguenti:

  • Sincronizza i dati in Connessione Salute al termine dell'evento. Ad esempio, sincronizza i dati quando l'utente termina una sessione di allenamento monitorata.
  • Pianifica un'attività una tantum utilizzando WorkManager per sincronizzare i dati in un secondo momento.

Best practice per la granularità e la frequenza delle scritture

Quando scrivi dati in Connessione Salute, utilizza una risoluzione appropriata. L'utilizzo della risoluzione appropriata consente di ridurre il carico di archiviazione, mantenendo comunque dati coerenti e accurati. La risoluzione dei dati comprende due aspetti:

  1. Frequenza di scrittura: la frequenza con cui la tua applicazione invia nuovi dati a Connessione Salute. Ad esempio, scrivi nuovi dati ogni 15 minuti.
  2. Granularità dei dati scritti: la frequenza con cui sono stati campionati i dati push. Ad esempio, scrivi campioni di frequenza cardiaca ogni 5 secondi. Non tutti i tipi di dati richiedono la stessa frequenza di campionamento. Non è molto utile aggiornare i dati del conteggio dei passi ogni secondo, rispetto a una cadenza meno frequente, ad esempio ogni 60 secondi. Tuttavia, frequenze di campionamento più elevate possono fornire agli utenti una visione più dettagliata e granulare dei propri dati relativi a salute e attività fisica. Le frequenze di campionamento devono trovare un equilibrio tra dettagli e prestazioni.

Scrivere i dati monitorati durante il giorno

Per i dati raccolti in modo continuativo, come i passi, la tua applicazione deve scrivere in Connessione Salute almeno ogni 15 minuti durante il giorno.

Tipo di dati

Unità

Previsto

Esempio

Passi

passi

Ogni minuto

23:14 - 23:15 - 5 passi

23:16 - 23:17 - 22 passi

23:17 - 23:18 - 8 passi

StepsCadence

passi/min

Ogni minuto

23:14 - 23:15 - 5 spm

23:16 - 23:17 - 22 spm

23:17 - 23:18 - 8 spm

Spinte in sedia a rotelle

spinte

Ogni minuto

23:14 - 23:15 - 5 notifiche push

23:16 - 23:17 - 22 spinte

23:17 - 23:18 - 8 push

ActiveCaloriesBurned

Calorie

Ogni 15 minuti

23:15 - 23:30 - 2 calorie

23:30 - 23:45 - 25 calorie

23:45 - 00:00 - 5 calorie

TotalCaloriesBurned

Calorie

Ogni 15 minuti

23:15 - 23:30 - 16 calorie

23:30 - 23:45 - 16 calorie

23:45 - 00:00 - 16 calorie

Distanza

km/min

Ogni minuto

23:14-23:15 - 0,008 km

23:16 - 23:16 - 0,021 km

23:17 - 23:18 - 0,012 km

ElevationGained

m

Ogni minuto

20:36 - 20:37 - 3,048 m

20:39 - 20:40 - 3048 m

23:23 - 23:24 - 9,144 m

PianiSaliti

piani

Ogni minuto

23:14 - 23:15 - 5 piani

23:16 - 23:16 - 22 piani

23:17 - 23:18 - 8 piani

HeartRate

bpm

4 volte al minuto

6:11:15 - 55 bpm

6:11:30 - 56 bpm

6:11:45 - 56 bpm

6:12:00 - 55 bpm

HeartRateVariabilityRmssd

ms

Ogni minuto

6:11 - 23 ms

RespiratoryRate

respiri/minuto

Ogni minuto

23:14 - 23:15 - 60 respiri al minuto

23:16 - 23:16 - 62 respiri/minuto

23:17 - 23:18 - 64 respiri al minuto

OxygenSaturation

%

Ogni ora

6:11 - 95,208%

Scrivere sessioni

I dati devono essere scritti in Connessione Salute al termine dell'allenamento o della sessione di sonno.

Come minimo, la tua applicazione deve seguire le indicazioni riportate nella colonna previsto della tabella seguente. Ove possibile, segui le indicazioni migliori.

Sessioni di allenamento e sonno

Il seguente esempio mostra come scrivere i dati durante un allenamento:

Tipo di dati

Unità

Previsto

Cordiali saluti,

Esempio

Passi

passi

Ogni minuto

Ogni secondo

23:14-23:15 - 5 passi

23:16 - 23:17 - 22 passi

23:17 - 23:18 - 8 passi

StepsCadence

passi/min

Ogni minuto

Ogni secondo

23:14-23:15 - 35 spm

23:16 - 23:17 - 37 spm

23:17 - 23:18 - 40 spm

Spinte in sedia a rotelle

spinte

Ogni minuto

Ogni secondo

23:14-23:15 - 5 notifiche push

23:16 - 23:17 - 22 spinte

23:17 - 23:18 - 8 push

CyclingPedalingCadence

rpm / respiri al minuto

Ogni minuto

Ogni secondo

23:14-23:15 - 65 rpm

23:16 - 23:17 - 70 rpm

23:17 - 23:18 - 68 rpm

Potenza

watt

Ogni minuto

Ogni secondo

23:14-23:15 - 250 watt

23:16 - 23:17 - 255 watt

23:17 - 23:18 - 245 watt

Velocità

km/min

Ogni minuto

Ogni secondo

23:14-23:15 - 0,3 km/min

23:16 - 23:17 - 0,4 km/min

23:17 - 23:18 -0,4 km/min

Distanza

km/m

Ogni minuto

Ogni secondo

23:14-23:15 - 0,008 km

23:16 - 23:16 - 0,021 km

23:17 - 23:18 - 0,012 km

ActiveCaloriesBurned

Calorie

Ogni minuto

Ogni secondo

23:14-23:15 - 20 calorie

23:16 - 23:17 - 20 calorie

23:17 - 23:18 - 25 calorie

TotalCaloriesBurned

Calorie

Ogni minuto

Ogni secondo

23:14-23:15 - 36 calorie

23:16 - 23:17 - 36 calorie

23:17 - 23:18 - 41 calorie

ElevationGained

m

Ogni minuto

Ogni secondo

20:36 - 20:37 - 3,048 m

20:39 - 20:40 - 3048 m

23:23 - 23:24 - 9,144 m

ExerciseRoutes

lat/lng/alt

Ogni 3-5 secondi

Ogni secondo

HeartRate

bpm

4 volte al minuto

Ogni secondo

23:14-23:15 - 150 bpm

Il seguente esempio mostra come scrivere i dati durante o dopo una sessione di sonno:

Tipo di dati

Unità

Campioni previsti

Esempio

Fasi del sonno

fase

Periodo di tempo granulare per fase del sonno

23:46 - 23:50 - sveglio

23:50 - 23:56 - sonno leggero

23:56 - 00:16 - sonno profondo

RestingHeartRate

bpm

Unico valore giornaliero (previsto al mattino presto)

6:11 - 60 bpm

OxygenSaturation

%

Unico valore giornaliero (previsto al mattino presto)

6:11 - 95,208%

Manifestazioni multisportive

Questo approccio utilizza tipi e strutture di dati esistenti e verifica la compatibilità con le implementazioni e i lettori di dati di Connessione Salute attuali. Questo è un approccio comune adottato dalle piattaforme per il fitness.

Il seguente esempio mostra come scrivere i dati per un triathlon:

val swimStartTime = Instant.parse("2024-08-22T08:00:00Z")
val swimEndTime = Instant.parse("2024-08-22T08:30:00Z")
val bikeStartTime = Instant.parse("2024-08-22T08:40:00Z")
val bikeEndTime = Instant.parse("2024-08-22T09:40:00Z")
val runStartTime = Instant.parse("2024-08-22T09:50:00Z")
val runEndTime = Instant.parse("2024-08-22T10:20:00Z")

val swimSession = ExerciseSessionRecord(
    startTime = swimStartTime,
    endTime = swimEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_SWIMMING_OPEN_WATER,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

val bikeSession = ExerciseSessionRecord(
    startTime = bikeStartTime,
    endTime = bikeEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_BIKING,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

val runSession = ExerciseSessionRecord(
    startTime = runStartTime,
    endTime = runEndTime,
    exerciseType = ExerciseSessionRecord.EXERCISE_TYPE_RUNNING,
    metadata = Metadata.autoRecorded(
      device = Device(type = Device.TYPE_WATCH)
    )
)

healthConnectClient.insertRecords(listOf(swimSession, bikeSession, runSession))