MiTensión uses Room Persistence Library for local data storage. The database stores blood pressure measurements organized by time periods (morning, afternoon, night).
The Medicion entity represents a single blood pressure measurement:
@Entitydata class Medicion( @PrimaryKey(autoGenerate = true) val id: Int = 0, val sistolica: Int, val diastolica: Int, val timestamp: Long = System.currentTimeMillis())
CREATE TABLE Medicion ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, sistolica INTEGER NOT NULL, diastolica INTEGER NOT NULL, timestamp INTEGER NOT NULL)
Room automatically converts Kotlin data types to appropriate SQLite types.
The ResumenDiario data class represents daily averages for each time period:
data class ResumenDiario( val dia: Int, val mediaSistolicaManana: Double?, val mediaDiastolicaManana: Double?, val mediaSistolicaTarde: Double?, val mediaDiastolicaTarde: Double?, val mediaSistolicaNoche: Double?, val mediaDiastolicaNoche: Double?)
The MedicionDao interface defines all database operations:
@Daointerface MedicionDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertar(medicion: Medicion) @Query("SELECT * FROM Medicion WHERE timestamp >= :inicioDelDia AND timestamp < :finDelDia ORDER BY timestamp DESC") fun obtenerMedicionesPorDia(inicioDelDia: Long, finDelDia: Long): Flow<List<Medicion>> @Query("SELECT COUNT(id) FROM Medicion WHERE timestamp >= :inicio AND timestamp < :fin") suspend fun contarMedicionesEnRango(inicio: Long, fin: Long): Int @Query("""...""") fun obtenerResumenMensual(inicioDelMes: Long, finDelMes: Long): Flow<List<ResumenDiario>>}
@Query("SELECT * FROM Medicion WHERE timestamp >= :inicioDelDia AND timestamp < :finDelDia ORDER BY timestamp DESC")fun obtenerMedicionesPorDia(inicioDelDia: Long, finDelDia: Long): Flow<List<Medicion>>
@Query("SELECT COUNT(id) FROM Medicion WHERE timestamp >= :inicio AND timestamp < :fin")suspend fun contarMedicionesEnRango(inicio: Long, fin: Long): Int
Use Case: Determine which measurement number (1, 2, or 3) to display in the UI.Example:
val count = medicionDao.contarMedicionesEnRango(inicio, fin)val numeroMedicion = count + 1 // If 0 measurements, show "Medición 1"
This advanced query uses a Common Table Expression (CTE) to calculate daily averages grouped by time period:
@Query(""" WITH MedicionesConPeriodo AS ( SELECT *, CASE WHEN (CAST(strftime('%H', timestamp / 1000, 'unixepoch') AS INTEGER) * 60 + CAST(strftime('%M', timestamp / 1000, 'unixepoch') AS INTEGER)) BETWEEN 1 AND 750 THEN 'MAÑANA' WHEN (CAST(strftime('%H', timestamp / 1000, 'unixepoch') AS INTEGER) * 60 + CAST(strftime('%M', timestamp / 1000, 'unixepoch') AS INTEGER)) BETWEEN 751 AND 1140 THEN 'TARDE' ELSE 'NOCHE' END AS periodo FROM Medicion WHERE timestamp >= :inicioDelMes AND timestamp < :finDelMes ) SELECT CAST(strftime('%d', timestamp / 1000, 'unixepoch') AS INTEGER) AS dia, AVG(CASE WHEN periodo = 'MAÑANA' THEN sistolica ELSE NULL END) as mediaSistolicaManana, AVG(CASE WHEN periodo = 'MAÑANA' THEN diastolica ELSE NULL END) as mediaDiastolicaManana, AVG(CASE WHEN periodo = 'TARDE' THEN sistolica ELSE NULL END) as mediaSistolicaTarde, AVG(CASE WHEN periodo = 'TARDE' THEN diastolica ELSE NULL END) as mediaDiastolicaTarde, AVG(CASE WHEN periodo = 'NOCHE' THEN sistolica ELSE NULL END) as mediaSistolicaNoche, AVG(CASE WHEN periodo = 'NOCHE' THEN diastolica ELSE NULL END) as mediaDiastolicaNoche FROM MedicionesConPeriodo GROUP BY dia""")fun obtenerResumenMensual(inicioDelMes: Long, finDelMes: Long): Flow<List<ResumenDiario>>
Query Breakdown:
1
CTE: Classify Time Period
The CTE adds a computed periodo column to each measurement:
Converts timestamp to hour and minute
Calculates total minutes since midnight
Classifies as MAÑANA (1-750), TARDE (751-1140), or NOCHE
2
Calculate Daily Averages
Groups measurements by day and calculates averages:
Uses CASE expressions to filter by period
AVG() function computes mean values
Returns NULL if no measurements for a period
3
Return Flow
Room wraps results in Flow<List<ResumenDiario>> for reactive UI updates
SQLite’s strftime() function requires dividing timestamp by 1000 to convert milliseconds to seconds.
// Use specific columns@Query("SELECT id, sistolica FROM Medicion WHERE ...")// Use LIMIT for large datasets@Query("SELECT * FROM Medicion ORDER BY timestamp DESC LIMIT 100")// Use Flow for automatic updatesfun getMediciones(): Flow<List<Medicion>>
// Don't use SELECT * if you only need specific columns@Query("SELECT * FROM Medicion") // Inefficient if table has many columns// Don't return all records at once@Query("SELECT * FROM Medicion") // Could load thousands of records// Don't use LiveData if you need Flowfun getMediciones(): LiveData<List<Medicion>> // Flow is preferred