Sincronizza dati

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

La maggior parte delle app che si integrano con Connessione Salute ha un proprio datastore che funge da fonte di verità. Connessione Salute offre modi per mantenere sincronizzata la tua app.

A seconda dell'architettura dell'app, il processo di sincronizzazione potrebbe comportare alcune o tutte le seguenti azioni:

  • Inserisci dati nuovi o aggiornati dall'archivio dati della tua app in Connessione Salute.
  • Estrai le modifiche ai dati da Connessione Salute nell'archivio dati della tua app.
  • Elimina i dati da Connessione Salute quando vengono eliminati nell'archivio dati dell'app.

In ogni caso, assicurati che il processo di sincronizzazione mantenga allineati sia Connessione Salute sia l'archivio dati della tua app.

Fornire dati a Connessione Salute

La prima parte del processo di sincronizzazione consiste nell'inserire i dati dal datastore dell'app nel datastore di Connessione Salute.

Preparare i dati

In genere, i record nel datastore della tua app hanno i seguenti dettagli:

  • Una chiave univoca, ad esempio un UUID.
  • Una versione o un timestamp.

Durante la sincronizzazione dei dati con Connessione Salute, identifica e invia solo i dati che sono stati inseriti, aggiornati o eliminati dall'ultima sincronizzazione.

Scrivere dati su Connessione Salute

Per inserire i dati in Connessione Salute:

  1. Ottieni un elenco di voci nuove, aggiornate o eliminate dall'archivio dati della tua app.
  2. Per ogni voce, crea un oggetto Record appropriato per quel tipo di dati. Ad esempio, crea un oggetto WeightRecord per i dati relativi al peso.
  3. Specifica un oggetto Metadata con ogni Record. Ciò include clientRecordId, un ID dell'archivio dati della tua app che puoi utilizzare per identificare in modo univoco il record. A questo scopo, puoi utilizzare la chiave univoca esistente. Se i tuoi dati sono versionati, fornisci anche un clientRecordVersion che sia in linea con il versionamento utilizzato nei dati. Se non è sottoposto al controllo delle versioni, puoi utilizzare il valore Long del timestamp corrente come alternativa.

    val recordVersion = 0L
    // Specify as needed
    // The clientRecordId is an ID that you choose for your record. This
    // is often the same ID you use in your app's datastore.
    val clientRecordId = "<your-record-id>"
    
    val record = WeightRecord(
        metadata = Metadata.activelyRecorded(
            clientRecordId = clientRecordId,
            clientRecordVersion = recordVersion,
            device = Device(type = Device.TYPE_SCALE)
        ),
        weight = Mass.kilograms(62.0),
        time = Instant.now(),
        zoneOffset = ZoneOffset.UTC,
    )
    healthConnectClient.insertRecords(listOf()(record))
    
    
  4. Inserisci o aggiorna i dati in Connessione Salute utilizzando insertRecords. L'inserimento/aggiornamento dei dati significa che tutti i dati esistenti in Connessione Salute vengono sovrascritti purché i valori di clientRecordId esistano nell'archivio dati di Connessione Salute e il valore di clientRecordVersion sia superiore a quello esistente. In caso contrario, i dati upsertati vengono scritti come nuovi dati.

    healthConnectClient.insertRecords(arrayListOf(record))
    

Per scoprire di più sulle considerazioni pratiche per l'inserimento dei dati, consulta le best practice per la scrittura dei dati.

Memorizzare gli ID di Connessione Salute

Se la tua app legge anche i dati da Connessione Salute, memorizza id per i record dopo averli inseriti. È necessario questo id per elaborare le eliminazioni quando recuperi le modifiche ai dati da Connessione Salute.

La funzione insertRecords restituisce un InsertRecordsResponse che contiene l'elenco dei valori id. Utilizza la risposta per ottenere gli ID record e archiviarli.

val response = healthConnectClient.insertRecords(arrayListOf(record))

for (recordId in response.recordIdsList) {
    // Store recordId to your app's datastore
}

Recuperare i dati da Connessione Salute

La seconda parte del processo di sincronizzazione consiste nell'estrarre eventuali modifiche ai dati da Connessione Salute all'archivio dati dell'app. Le modifiche ai dati possono includere aggiornamenti ed eliminazioni.

Ottenere un token Changes

Per ottenere un elenco delle modifiche da estrarre da Connessione Salute, la tua app deve tenere traccia dei token Changes. Puoi utilizzarli quando richiedi Modifiche per restituire sia un elenco di modifiche ai dati sia un nuovo token Modifiche da utilizzare la volta successiva.

Per ottenere un token Changes, chiama getChangesToken e fornisci i tipi di dati richiesti.

val changesToken = healthConnectClient.getChangesToken(
    ChangesTokenRequest(recordTypes = setOf(WeightRecord::class))
)

Controllare le modifiche ai dati

Ora che hai ottenuto un token Changes, utilizzalo per ottenere tutte le Changes. Ti consigliamo di creare un ciclo per esaminare tutte le modifiche in cui viene verificato se sono disponibili modifiche ai dati. Ecco i passaggi successivi:

  1. Chiama getChanges utilizzando il token per ottenere un elenco di modifiche.
  2. Controlla ogni modifica per verificare se il tipo di modifica è un UpsertionChange o un DeletionChange ed esegui le operazioni necessarie.
    • Per UpsertionChange, prendi in considerazione solo le modifiche che non provengono dall'app di chiamata per assicurarti di non reimportare i dati.
  3. Assegna il token Changes successivo come nuovo token.
  4. Ripeti i passaggi 1-3 finché non rimangono Modifiche.
  5. Memorizza il token successivo e riservalo per un importazione futura.
suspend fun processChanges(token: String): String {
    var nextChangesToken = token
    do {
        val response = healthConnectClient.getChanges(nextChangesToken)
        response.changes.forEach { change ->
            when (change) {
                is UpsertionChange ->
                    if (change.record.metadata.dataOrigin.packageName != context.packageName) {
                        processUpsertionChange(change)
                    }
                is DeletionChange -> processDeletionChange(change)
            }
        }
        nextChangesToken = response.nextChangesToken
    } while (response.hasMore)
    // Return and store the changes token for use next time.
    return nextChangesToken
}

Per scoprire le considerazioni pratiche per l'estrazione dei dati, consulta le best practice per la sincronizzazione dei dati.

Elaborare le modifiche ai dati

Rifletti le modifiche apportate al datastore dell'app. Per UpsertionChange, utilizza id e lastModifiedTime da metadata per upsert il record. Per DeletionChange, utilizza id fornito per eliminare il record. Per farlo devi aver memorizzato il record id come indicato in Memorizzare gli ID Connessione Salute.

Eliminare i dati da Connessione Salute

Quando un utente elimina i propri dati dalla tua app, assicurati che vengano rimossi anche da Connessione Salute. Per farlo, utilizza deleteRecords. Prende un tipo di record e un elenco di valori id e clientRecordId, il che rende conveniente l'eliminazione batch di più dati. È disponibile anche un deleteRecords alternativo che accetta un timeRangeFilter.

Sincronizzazione a bassa latenza dai dispositivi indossabili

Per sincronizzare i dati da un dispositivo fitness indossabile a Connessione Salute con bassa latenza, utilizza CompanionDeviceService. Questo approccio funziona per i dispositivi che supportano le notifiche o le indicazioni BLE GATT e Android 8.0 (livello API 26) o versioni successive. CompanionDeviceService consente alla tua app di ricevere dati da indossabili e scriverli in Connessione Salute, anche quando l'app non è già in esecuzione. Per maggiori dettagli sulle best practice BLE, consulta la sezione Panoramica di Bluetooth Low Energy.

Associare il dispositivo

Innanzitutto, la tua app deve guidare l'utente attraverso una procedura una tantum per associare il dispositivo indossabile alla tua app utilizzando CompanionDeviceManager. In questo modo, la tua app riceve le autorizzazioni necessarie per interagire con il dispositivo. Per ulteriori informazioni, vedi Accoppiamento del dispositivo complementare.

Dichiarare il servizio nel manifest

Dopodiché, dichiara CompanionDeviceService nel file manifest dell'app. Aggiungi quanto segue a AndroidManifest.xml:

<manifest ...>
   <application ...>
       <service
           android:name=".MyWearableService"
           android:exported="true"
           android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
           <intent-filter>
               <action android:name="android.companion.CompanionDeviceService" />
           </intent-filter>
       </service>
   </application>
</manifest>

Crea CompanionDeviceService

Infine, crea una classe che estenda CompanionDeviceService. Questo servizio gestisce la connessione al dispositivo indossabile e riceve i dati tramite i callback GATT BLE. Quando vengono ricevuti nuovi dati, questi vengono scritti immediatamente in Connessione Salute.

import android.companion.CompanionDeviceService
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import androidx.health.connect.client.permission.HealthPermission
import androidx.health.connect.client.HealthConnectClient
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.StepsRecord
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch

class MyWearableService : CompanionDeviceService() {

   // A coroutine scope for handling suspend functions like writing to Health Connect
   private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
   private var healthConnectClient: HealthConnectClient? = null
   private var bluetoothGatt: BluetoothGatt? = null

   // This is called by the system when your wearable connects
   override fun onDeviceAppeared(address: String) {
       super.onDeviceAppeared(address)
       healthConnectClient = HealthConnectClient.getOrCreate(this)

       serviceScope.launch {
           // Check which permissions have been granted before subscribing to data from the wearable.
           // A service cannot request permissions, so your app must have already requested
           // and been granted them from an Activity.
           val granted = healthConnectClient?.permissionController?.getGrantedPermissions()

           // ... set up your GATT connection here ...

           // Once connected, subscribe to notifications for the data types you have
           // permission to write.
           if (granted?.contains(HealthPermission.getWritePermission(HeartRateRecord::class)) == true) {
               // subscribeToHeartRate(bluetoothGatt)
           }
       }
   }

   // The core of your low-latency pipeline is the BLE callback
   private val gattCallback = object : BluetoothGattCallback() {
       override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
           super.onCharacteristicChanged(gatt, characteristic, value)

           // 1. Instantly receive the data
           val rawData = value

           // 2. Parse the data from the wearable
           val healthData = parseWearableData(rawData) // Your custom parsing logic

           // 3. Immediately process it. For simplicity, this example writes
           //    directly to Health Connect. A real-world app might write to its
           //    own datastore first and then sync with Health Connect.
           serviceScope.launch {
               writeToHealthConnect(healthData)
           }
       }
   }

   private suspend fun writeToHealthConnect(healthData: HealthData) {
       val records = prepareHealthConnectRecords(healthData) // Convert to Health Connect records
       try {
           healthConnectClient?.insertRecords(records)
       } catch (e: Exception) {
           // Handle exceptions
       }
   }

   // This is called by the system when your wearable disconnects
   override fun onDeviceDisappeared(address: String) {
       super.onDeviceDisappeared(address)
       // Clean up your GATT connection and other resources
       bluetoothGatt?.close()
   }
}

Best practice per la sincronizzazione dei dati

I seguenti fattori influiscono sul processo di sincronizzazione.

Scadenza del token

Poiché un token Changes inutilizzato scade entro 30 giorni, devi utilizzare una strategia di sincronizzazione che eviti la perdita di informazioni in questo caso. La tua strategia potrebbe includere i seguenti approcci:

  • Cerca nel datastore dell'app il record utilizzato più di recente che contiene anche un id da Connessione Salute.
  • Richiedi record da Connessione Salute che iniziano con un timestamp specifico, quindi inseriscili o aggiornali nel datastore della tua app.
  • Richiedi un token di modifica per riservarlo per la volta successiva in cui sarà necessario.

Strategie di gestione delle modifiche consigliate

Se la tua app riceve token Changes non validi o scaduti, ti consigliamo le seguenti strategie di gestione a seconda della sua applicazione nella tua logica:

  • Leggi ed elimina i duplicati di tutti i dati. Questa è la strategia ideale.
    • Memorizzare il timestamp dell'ultima volta che ha letto i dati da Connessione Salute.
    • Alla scadenza del token, rileggi tutti i dati dal timestamp più recente o degli ultimi 30 giorni. Poi, deduplica i dati rispetto a quelli letti in precedenza utilizzando gli identificatori.
    • Idealmente, implementa gli ID client, in quanto sono necessari per gli aggiornamenti dei dati.
  • Leggi solo i dati dall'ultimo timestamp di lettura. Ciò comporta alcune discrepanze nei dati intorno al momento della scadenza del token Changes, ma il periodo di tempo è più breve e può richiedere da poche ore a un paio di giorni.
    • Memorizzare il timestamp dell'ultima volta che ha letto i dati da Connessione Salute.
    • Alla scadenza del token, leggi tutti i dati a partire da questo timestamp.
  • Elimina e leggi i dati degli ultimi 30 giorni. Questo è più in linea con quanto accade nella prima integrazione.
    • Elimina tutti i dati letti dall'app da Connessione Salute negli ultimi 30 giorni.
    • Una volta eliminati, leggi di nuovo tutti questi dati.
  • Leggi i dati degli ultimi 30 giorni senza deduplicazione. Questa è la strategia meno ideale e comporta la visualizzazione di dati duplicati per gli utenti.
    • Elimina tutti i dati letti dall'app da Connessione Salute negli ultimi 30 giorni.
    • Consenti voci duplicate.

Token di modifica del tipo di dati

Se la tua app utilizza più di un tipo di dati in modo indipendente, utilizza token di modifiche separati per ogni tipo di dati. Utilizza un elenco di più tipi di dati con l'API Changes Sync solo se questi tipi di dati vengono utilizzati insieme o non vengono utilizzati affatto.

Letture in primo piano

Le app possono leggere i dati da Connessione Salute solo quando sono in primo piano. Durante la sincronizzazione dei dati da Connessione Salute, l'accesso a Connessione Salute potrebbe interrompersi in qualsiasi momento. Ad esempio, la tua app deve gestire le interruzioni a metà di una sincronizzazione durante la lettura di una grande quantità di dati da Connessione Salute e continuare la volta successiva che l'app viene aperta.

Letture in background

Puoi richiedere che la tua applicazione venga eseguita in background e legga i dati da Connessione Salute. Se richiedi l'autorizzazione Background Read, l'utente può concedere alla tua app l'accesso per leggere i dati in background.

Importa i tempi

Poiché la tua app non può ricevere notifiche relative a nuovi dati, controlla la presenza di nuovi dati in due punti:

  • Ogni volta che la tua app diventa attiva in primo piano. In questo caso, utilizza gli eventi del ciclo di vita.
  • Periodicamente, mentre l'app rimane in primo piano. Invia una notifica agli utenti quando sono disponibili nuovi dati, consentendo loro di aggiornare lo schermo per riflettere le<0xx0D> modifiche.