-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Search history: Data layer #758
Search history: Data layer #758
Conversation
94f0a83
to
8a37a8a
Compare
val publishedDate: Date, | ||
val duration: Double |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These fields might change based on how an episode's recent search (logged when user taps an item from the episode search results) is displayed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I understanding correctly that the idea here is that each Search History Item would be for either a Podcast
,Folder
, or Episode
, so the embedded fields for only one of those three would not be null
in any given SearchHistoryItem
?
If that's right, it might be worth considering at some point using a sealed class for this so we don't have so many nullable fields to deal with in our code. I'm not sure I'd do that at the db level necessarily (what you have here is nice and straightforward), but perhaps when we start using this part of the db, it would be worth creating a class for accessing the database that would convert all of the entries to/from an appropriate sealed class. Very rough pseudo code of what I'm thinking of:
pseudo code
sealed class SearchHistoryEntry(
val modified: Long,
/* ... */
) {
class Podcast(
modified: Long,
val author: String,
// ...
) : SearchHistoryEntry(modified /* ... */)
class Folder(
modified: Long,
val color: Int
// ...
): SearchHistoryEntry(modified /* ... */)
class Episode(
modified: Long,
val publishedDate: Date,
// ...
): SearchHistoryEntry(modified, /* ... */)
}
class SearchHistoryDbUtil(private val searchHistoryDao: SearchHistoryDao) {
suspend fun insert(entry: SearchHistoryEntry) {
val item: SearchHistoryItem = when (entry) {
is SearchHistoryEntry.Episode -> // create SearchHistoryItem from SearchHistoryEntry.Episode
is SearchHistoryEntry.Folder -> // create SearchHistoryItem from SearchHistoryEntry.Folder
is SearchHistoryEntry.Podcast -> // create SearchHistoryItem from SearchHistoryEntry.Podcast
}
searchHistoryDao.insert(item)
}
suspend fun findAll(limit: Int = 10): List<SearchHistoryEntry> {
val entries = searchHistoryDao.findAll(limit)
return entries.map { it.toEntry() }
}
private fun SearchHistoryItem.toEntry(): SearchHistoryEntry {
// create appropriate SearchHistoryEntry from SearchHistoryItem
}
}
We may figure out that this is actually a bad idea once we start implementing this, but I just wanted to mention it as a possibility in case you might find that it would actually be useful as you start using the db.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I understanding correctly that the idea here is that each Search History Item would be for either a Podcast,Folder, or Episode, so the embedded fields for only one of those three would not be null in any given SearchHistoryItem?
That's correct.
If that's right, it might be worth considering at some point using a sealed class for this so we don't have so many nullable fields to deal with in our code. I'm not sure I'd do that at the db level necessarily (what you have here is nice and straightforward), but perhaps when we start using this part of the db, it would be worth creating a class for accessing the database that would convert all of the entries to/from an appropriate sealed class. Very rough pseudo code of what I'm thinking of:
I did try to add relations and different tables for each sub-entity and foreign key and all, but it looked complicated. So I didn't use it at the db level to not over-optimize it considering that embedded objects are small. I like your suggestion to use a sealed class at another point and will use it in another PR.
I might also add a query to keep a limited number of records in the table at a later point.
3c6b738
to
6f50ad9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! 👍
...ervices/model/src/main/java/au/com/shiftyjelly/pocketcasts/models/db/dao/SearchHistoryDao.kt
Outdated
Show resolved
Hide resolved
val publishedDate: Date, | ||
val duration: Double |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I understanding correctly that the idea here is that each Search History Item would be for either a Podcast
,Folder
, or Episode
, so the embedded fields for only one of those three would not be null
in any given SearchHistoryItem
?
If that's right, it might be worth considering at some point using a sealed class for this so we don't have so many nullable fields to deal with in our code. I'm not sure I'd do that at the db level necessarily (what you have here is nice and straightforward), but perhaps when we start using this part of the db, it would be worth creating a class for accessing the database that would convert all of the entries to/from an appropriate sealed class. Very rough pseudo code of what I'm thinking of:
pseudo code
sealed class SearchHistoryEntry(
val modified: Long,
/* ... */
) {
class Podcast(
modified: Long,
val author: String,
// ...
) : SearchHistoryEntry(modified /* ... */)
class Folder(
modified: Long,
val color: Int
// ...
): SearchHistoryEntry(modified /* ... */)
class Episode(
modified: Long,
val publishedDate: Date,
// ...
): SearchHistoryEntry(modified, /* ... */)
}
class SearchHistoryDbUtil(private val searchHistoryDao: SearchHistoryDao) {
suspend fun insert(entry: SearchHistoryEntry) {
val item: SearchHistoryItem = when (entry) {
is SearchHistoryEntry.Episode -> // create SearchHistoryItem from SearchHistoryEntry.Episode
is SearchHistoryEntry.Folder -> // create SearchHistoryItem from SearchHistoryEntry.Folder
is SearchHistoryEntry.Podcast -> // create SearchHistoryItem from SearchHistoryEntry.Podcast
}
searchHistoryDao.insert(item)
}
suspend fun findAll(limit: Int = 10): List<SearchHistoryEntry> {
val entries = searchHistoryDao.findAll(limit)
return entries.map { it.toEntry() }
}
private fun SearchHistoryItem.toEntry(): SearchHistoryEntry {
// create appropriate SearchHistoryEntry from SearchHistoryItem
}
}
We may figure out that this is actually a bad idea once we start implementing this, but I just wanted to mention it as a possibility in case you might find that it would actually be useful as you start using the db.
Part of: #752
Description
This PR
search_history
(+ migration) to the databasesearch term
/podcast
/folder
/episode
search history item insertionpodcast
/folder
/episode
search history item details as embedded objects (with minimum details to render corresponding search history item) so that search history can be displayed for them even when there's no corresponding localpodcast
/folder
/episode
in the database.SearchHistoryDao
for search history item operationsNote: Targets a feature branch to allow modifying the
search_history
table until the feature is ready.Testing Instructions
Tests
SearchHistoryDaoTest
cover important scenarios:search term
/podcast
/folder
/episode
search history item replaces the previous itemsearch term
/podcast
/folder
/episode
search history items are insertedMigration
main
branchsearch_history
table is createdFresh Install
search_history
table is createdChecklist
modules/services/localization/src/main/res/values/strings.xml
I have tested any UI changes...