I'm trying to save data when user starts a specific activity and when he ends it and I want to calculate how many hours the user spent this day/week/month
I'm having a problem to extract this information.
This is my table:
#Entity(tableName = "daily_activity_time_table")
data class TimeTrack(
#PrimaryKey(autoGenerate = true)
var activityId: Long = 0L,
#ColumnInfo(name = "start_time")
val startTimeMilli: Date = Date(System.currentTimeMillis()), //i didnt find a better way to save date and time
#ColumnInfo(name = "end_time")
var endTimeMilli: Date = startTimeMilli,
#ColumnInfo(name = "quality_rating")
var sleepQuality: Int = -1
)
and I need a query to get the sum of today hours for example, I tried doing something like this:
#Query("SELECT SUM(end_time-start_time) from daily_activity_time_table where start_time = date('now') ")
fun getTodayTime(): Long
I'm able to print to the screen the date and time with this convertor:
#SuppressLint("SimpleDateFormat")
fun convertLongToDateString(systemTime: Long): String {
return SimpleDateFormat("EEEE MMM-dd-yyyy' Time: 'HH:mm")
.format(systemTime).toString()
}
but cant find a way to print all the hours that the user used the activity today
I have made a small and very simplified project showing how to extract data from Room by a range of dates (represented as milliseconds since epoch)
https://github.com/or-dvir/RoomDate
Please note that this is NOT the proper way to set up Room database, and that this method has its limits (for example it does not take time zones into account).
Personally, I would just keep Room for storing and retreiving persistent data, and do the whole calculating things part in a different place (ie. your Activity or ViewModel or repository)
Something like
#Entity
class TimeTrack(
#PrimaryKey
val id: Int,
val startTime: Long,
val endTime: Long,
val sleepQuality: Int = -1
){
val totalTime: Long
get() = endTime - startTime
// maybe this as well, gets the Local Date as LocalDate in case you want to filter by that
val startDate: LocalDate
get() = LocalDate.ofInstant(Instant.ofEpochSecond(startTime), ZonedDateTime.now().offset)
}
#Dao
interface TimeTrackDao {
#Query("SELECT * FROM TimeTrack")
suspend fun getTimeTracks(): List<TimeTrack>
#Query("SELECT MAX(id) FROM TimeTrack ")
suspend fun highestID(): Int
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun save(vararg consensus: TimeTrack)
}
and then after getting a start- and stop-time for your activity:
// this gives a nice unambiguous timestamp
val startTime = Instant.now().epochSecond
val stopTime = Instant.now().epochSecond
val tt = TimeTrack(timeTrackDao.highestID()+1,
startTime,
stopTime,
sleepQuality = -1)
timeTrackDao.save(tt)
fun totalTimeSpentAt(timeTrackList: List<TimeTrack>): Duration = Duration.ofSeconds(timeTrackList.map{it.totalTime}.sum())
Related
Imagine receiving from an endpoint an object like this
data class Foo(
val name: String,
val start: String,
val end: String
)
where the properties start and end are string in the format of "00:00:00" to represent the hours, minutes and second from midnight.
I need to save this object in a Room table but with start and end parsed as Int instead of string.
The solution I resorted is this:
data class Foo(
val name: String,
val start: String,
val end: String,
var startInSeconds: Int = 0,
var endInSeconds: Int = 0
) {
init {
startInSeconds = this.convertTimeInSeconds(this.start)
endInSeconds = this.convertTimeInSeconds(this.end)
}
private fun convertTimeInSeconds(time: String): Int {
val tokens = time.split(":".toRegex())
val hours = Integer.parseInt(tokens[0])
val minutes = Integer.parseInt(tokens[1])
val seconds = Integer.parseInt(tokens[2])
return 3600 * hours + 60 * minutes + seconds
}
}
But I'd like to avoid to parse the start and end every time.
There is a way to parse before inserting?
you can try my solution if you want ,
first, instead of using the same object Foo to store your data , it is better to create another data class as Entity to encapsulate the data.
data class Bar(
val name: String,
val start: String,
val end: String,
val startInSeconds: Int, // <<-- using val to make data read only
val endInSeconds: Int // <<-- using val to make data read only
)
you might need to create a thousand of object depend on your data size, so using companion object seems to be a good idea to parse the data to avoid unnecessary memory allocation .
data class Bar(
val name: String,
val start: String,
val end: String,
val startInSeconds: Int,
val endInSeconds: Int
) {
companion object {
// overloading the invoke function
operator fun invoke(foo:Foo) : Bar {
return Bar(
name = foo.name,
start = foo.start,
end = foo.end,
startInSeconds = convertTimeInSeconds(foo.start),
endInSeconds = convertTimeInSeconds(foo.end)
)
}
private fun convertTimeInSeconds(time: String): Int {
val tokens = time.split(":".toRegex())
val hours = Integer.parseInt(tokens[0])
val minutes = Integer.parseInt(tokens[1])
val seconds = Integer.parseInt(tokens[2])
return 3600 * hours + 60 * minutes + seconds
}
}
}
//how to use
fun insertToDatabase(foo:Foo){
val parsedData = Bar(foo)
dao.insert(parsedData)
}
When I get the list that is stored I get this pattern
["12/08/2021", "12/07/2021", "12/08/2020", "11/07/2021", "11/05/2021"]
As you see the month and day are being sorted, but it looks like the year is not being read.
below is Query that I'm using with Room
#Query("SELECT * FROM timeSheet ORDER BY date DESC")
fun timeSheetsByDate(): PagingSource<Int, TimeSheet>
Here is a video of it
I then tried with the #TypeConverters,
#Query("SELECT * FROM timeSheet ORDER BY date(date) DESC")
fun timeSheetsByDate(): PagingSource<Int, TimeSheet>
#Entity(tableName = "timeSheet")
data class TimeSheet(
#PrimaryKey var id: String = "",
#ColumnInfo(name = "date")
#TypeConverters(TimestampConverter::class)
var dateSubmitted: String? = null
)
class TimestampConverter {
#TypeConverter
fun fromTimestamp(value: Long?): Date? {
return if (value == null) null else Date(value)
}
#TypeConverter
fun dateToTimestamp(dateString: String?): Date {
val format = TimeFormatter()
val date = Calendar.getInstance()
date.time = format.date(dateString)!!
return date.time
}
}
but for some reason when refreshing the data for the second time it doesn't show the values from the first page, so I have to scroll down until all the data is fully loaded and then when I scroll up again I'm able to see the values from the first page.
Here is a video of it
You could use
#Query("SELECT * FROM timeSheet ORDER BY substr(date,7,4)||substr(date,4,2)||substr(1,2) DESC")
However, that is awkward.
It is strongly suggested that you do not use dd/mm/yyyy to store the timestamp but instead store it in a recognised format e.g. yyyy-mm-dd
You can then sort and compare dates and also make use of the date and time functions that are built into SQLite.
Your TypeConverters will not be used by Room (and would not work). Room expects a TypeConverter to convert from an unknown type to an underlying type known/handled by Room.
Having var dateSubmitted: String? = null means that the type is String and room can handle storing a String without it being converted, there is no need to convert it and therefore Room won't convert it.
If however you had var dateSubmitted: Date? = null
Then Room does not know how to handle the Date type and will then demand a TypeConverter to convert the Date type into a type that Room can handle. It would also need a second TypeConverter to be able to convert the type stored in the database into a Date type when extracting data.
If you want to use a recognised format, say YYYY-MM-DD then a String can hold that and no TypeConverter is required as far as Room is concerned. However, you need to provide the date as a string in that format for use by Room and handle Room returning the TimeSheet with YYYY-MM-DD in the dateSubmitted member/field.
I would suggest considering the following demonstration of TypeConverters :-
#Entity(tableName = "timeSheet")
#TypeConverters(TimestampConverter::class) /* due to scoping TypeConveters needs to be here or at #Database level */
data class TimeSheet(
#PrimaryKey var id: String = "",
#ColumnInfo(name = "date")
var dateSubmitted: Date? = null,
)
{
companion object {
val format = SimpleDateFormat("yyyy-MM-dd")
}
}
class TimestampConverter {
#TypeConverter
fun fromDateToString(value: Date): String {
return format.format(value)
}
#TypeConverter
fun toDateFromString(value: String) : Date {
return format.parse(value)
}
}
and your original query would then work.
For example using the above along with :-
#Dao
interface AllDao {
#Insert
fun insert(timeSheet: TimeSheet): Long
#Query("SELECT * FROM timeSheet ORDER BY date DESC")
fun getTimeSheetsOrderedByDateSubmittedDesc(): List<TimeSheet>
}
and :-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
/* ["12/08/2021", "12/07/2021", "12/08/2020", "11/07/2021", "11/05/2021"] */
dao.insert(TimeSheet("A",TimeSheet.format.parse("2021-08-12")))
dao.insert(TimeSheet("B",TimeSheet.format.parse("2021-07-12")))
dao.insert(TimeSheet("C",TimeSheet.format.parse("2021-05-11")))
dao.insert(TimeSheet("D",TimeSheet.format.parse("2020-08-12")))
dao.insert(TimeSheet("E",TimeSheet.format.parse("2021-07-11")))
for (t: TimeSheet in dao.getTimeSheetsOrderedByDateSubmittedDesc()) {
Log.d("RESULTINFO","ID = ${t.id} DateSubmitted = ${t.dateSubmitted} or ${TimestampConverter().fromDateToString(t.dateSubmitted!!)}")
}
}
}
Then the resultant output is :-
2021-12-09 09:20:55.299 D/RESULTINFO: ID = A DateSubmitted = Thu Aug 12 00:00:00 GMT+10:00 2021 or 2021-08-12
2021-12-09 09:20:55.299 D/RESULTINFO: ID = B DateSubmitted = Mon Jul 12 00:00:00 GMT+10:00 2021 or 2021-07-12
2021-12-09 09:20:55.300 D/RESULTINFO: ID = E DateSubmitted = Sun Jul 11 00:00:00 GMT+10:00 2021 or 2021-07-11
2021-12-09 09:20:55.300 D/RESULTINFO: ID = C DateSubmitted = Tue May 11 00:00:00 GMT+10:00 2021 or 2021-05-11
2021-12-09 09:20:55.300 D/RESULTINFO: ID = D DateSubmitted = Wed Aug 12 00:00:00 GMT+10:00 2020 or 2020-08-12
I'm following an Android development tutorial that uses Room to store data and retrieve it.
I have the following entity:
#Entity
data class DogBreed(
#ColumnInfo(name = "breed_id")
val breedId: String?,
#ColumnInfo(name = "dog_name")
val dogBreed: String?,
#ColumnInfo(name = "life_span")
val lifeSpan: String?,
#ColumnInfo(name = "breed_group")
val breedGroup: String?,
#ColumnInfo(name = "bred_for")
val bredFor: String?,
val temperament: String?,
#ColumnInfo(name = "dog_url")
val imageUrl: String?
) {
#PrimaryKey(autoGenerate = true)
var uuid: Int = 0
}
and the following Room DAO:
#Dao
interface DogDao {
#Insert
suspend fun insertAll(vararg dogs: DogBreed): List<Long>
#Query("SELECT * FROM dogbreed")
suspend fun getAllDogs(): List<DogBreed>
#Query("SELECT * FROM dogbreed WHERE uuid = :dogId")
suspend fun getDog(dogId: Int): DogBreed?
#Query("DELETE FROM dogbreed")
suspend fun deleteAllDogs()
}
The insertAll method works fine. I can retrieve all data using getAllDogs() and the uuid is auto-generated as intended. However, the getDog(dogId) method does not work. It always returns null.
It's a simple query and I don't see anything wrong with it. What could be the issue?
The query is used in the following code:
val dao = DogDatabase(getApplication()).dogDao()
val dog = dao.getDog(uuid)
Toast.makeText(getApplication(), "Dog from DB ${uuid} / $dog", Toast.LENGTH_SHORT).show()
The toast shows something like "Dog from DB 12 / null".
It turned out that the uuid, which was supplied as an action parameter when navigating from a different fragment, was not the correct value. It was the breedId instead of the uuid property.
I used Room a couple of time but it seems your uuId is an Int type and you are using a String type in your query. As far as I remember you have to use like for sql queries.
My guess is that you have to use both Int values or String values but not a mixed of it. And if you are using String type use Like instead of equal.
"SELECT * FROM dogbreed WHERE uuid like :dogId"
I am using a Github Library to Display time in a TextView as the format like just now, yesterday, time ago... etc. It displays the time as I want. But the problem is that the time is not changing as it was just now at the moment of post-it remains that forever.
Model class for postAdapter
class Post {
private var date: String = ""
constructor(date: Long) {
fun getDate(): String
{
return date
}
//Initialization according to the library
val timeInMillis = System.currentTimeMillis()
val timeAgo = TimeAgo.using(timeInMillis)
//saving the data to firestore
val fStore: FirebaseFirestore = FirebaseFirestore.getInstance()
val post = Post(title, description, date = timeAgo)
fStore.collection("Posts").document()
.set(post)
.addOnSuccessListener{...}
Retrieving the data from the Firestore
private fun postInfo(title: TextView, description: TextView, date: TextView) {
val postRef = FirebaseFirestore.getInstance().collection("Posts").document()
postRef.get()
.addOnSuccessListener { documentSnapshot ->
if (documentSnapshot != null && documentSnapshot.exists()) {
val post = documentSnapshot.toObject(Post::class.java)
date.text = post?.getDate()
title.text = post?.getTitle()
description.text = post?.getDescription()
}
}
}
What you do is using a library to convert a date to a string, and then set the TextView text to that string. Strings are only lists of chars and they have no "consciousness" about what they mean or if they ever need to change/update.
I would suggest using a RelativeTimeTextView from android-ago. Once you set a reference time to that view, there's some code inside that will automatically trigger updates for you.
https://github.com/curioustechizen/android-ago
Edit: you're also doing conversion from timeInMillis: Long to date: String at the moment of storage in firestore. You should instead store timeInMillis as is (Long value) and then when reading use it as argument of relativeTimetextView.setReferenceTime(timeInMillis).
I'm using Room persistence library for Android and trying to make 'update' query for the boolean field.
#Update
suspend fun updateProduct(product: Product)
Product entity:
#Entity(tableName = "products")
data class Product(
#ColumnInfo(name = "name") val name: String = "",
#ColumnInfo(name = "price") val price: Int = 0,
#ColumnInfo(name = "count") val count: Int = 0,
#ColumnInfo(name = "description") val description: String = "",
#ColumnInfo(name = "isPurchased") val isPurchased : Boolean = false
) {
#PrimaryKey var id: String = UUID.randomUUID().toString()
#ColumnInfo(name = "date") var date: Long = Date().time
}
Similar queries as delete, insert work fine. The underhood query should find the id of product and update all fields but it doesn't work. Please don't write about insert query instead update, it's a dirty trick.
Update: update method returns 0 and it means it doesn't work, according to docs it should return num of updated record:
Although usually not necessary, you can have this method return an int
value instead, indicating the number of rows updated in the database.
you can try this
#Query("UPDATE products SET price=:price WHERE id = :id")
void update(Float price, int id);
Regarding the docs it says you have to do something like this:
#Update
fun updateProduct(product: Product) // no need of suspend
also you can control what happen onConflict. Note that if you don't specify by default it is OnConflictStrategy.ABORT which roll back the transaction and does nothing. So you might wanna add something like #Update(onConflict = OnConflictStrategy.REPLACE).