Search functionality in room database - android

I want to create a search function for my user to quick access to my items .
Well , the first thing is that i have my product in a room table(List) and store them in database and show them with a recyclerview in the my main activity(Home activity ) .
So i want code a Query to search between them after user click on button search .
I code my query and after use it in my home activity nothing happend .i'm using mvvm model. pls help me with this .
Code :
My Table (List of Product ) :
#Entity(tableName = "cart")
data class RoomTables(
#PrimaryKey(autoGenerate = true) val id: Int?,
#ColumnInfo val title: String,
#ColumnInfo val price: Int,
#ColumnInfo val image: Int,
#ColumnInfo var amount: Int
)
My dao :
#Query ("SELECT * FROM cart WHERE title LIKE :search")
fun searchItem (search : String?):List<RoomTables>
My Repository :
fun searchItem(search :String) = db.GetDao().searchItem(search)
My Viewmodel :
fun searchItem(search : String) = CoroutineScope(Dispatchers.Default).launch {
repository.searchItem(search)
}
And HomeActivity :
class HomeActivity : AppCompatActivity() {
lateinit var viewModelRoom: ViewModelRoom
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.home_activity)
val list = ArrayList<RoomTables>()
for (i in 0..20) {
list.add(
RoomTables(
null, "$i banana", 12,
R.drawable.bannana, 0
)
)
}
recycler_main.apply {
layoutManager = GridLayoutManager(this#HomeActivity, 2)
adapter = RecyclerAdapterMain(list, context)
}
val database = DataBaseRoom(this)
val repositoryCart = RepositoryCart (database)
val factoryRoom = FactoryRoom(repositoryCart)
viewModelRoom = ViewModelRoom(repositoryCart)
viewModelRoom = ViewModelProvider(this , factoryRoom ).get(ViewModelRoom::class.java)
val editText : EditText = findViewById(R.id.edittextSearch)
val searchbtn : ImageView = findViewById(R.id.search_main)
searchbtn.setOnClickListener{
viewModelRoom.searchItem(editText.text.toString())
}

Let's try this approach.
First get list items from the table.
#Query ("SELECT * FROM cart")
fun searchItem():List<RoomTables>
Now from your repository.
fun searchItem() : List<RoomTables> = db.GetDao().searchItem()
In ViewModel.
fun searchItem(search : String): <List<RoomTables> {
filterWithQuery(query)
return filteredList
}
private fun filterWithQuery(query: String, repository: YourRepository) {
val filterList = ArrayList<RoomTables>()
for (currentItem: RoomTables in repository.searchItem()) {
val formatTitle: String = currentItem.title.toLowerCase(Locale.getDefault())
if (formatTitle.contains(query)) {
filterList.add(currentItem)
}
}
filteredList.value = filterList
}
Make sure you add Coroutines above.
Now you have all elements filtered and returns new list items based on search query user entered.
In your fragment or activity observe data.
searchbtn.setOnClickListener{
viewModel.searchItem(query).observe(viewLifecycleOwner, Observer { items -> {
// Add data to your recyclerview
}
}
The flow and approach is correct and it is working, it is hard to follow your code since i'm not sure if the return types match because you are not using LiveData, in this case you must.
If you found confusing or hard to follow, i have a working example in github, compare and make changes.
https://github.com/RajashekarRaju/ProjectSubmission-GoogleDevelopers

Related

Is the RecyclerView absolutely wrong when there is only one item?

I'm making a function that looks like an image.
Although not shown in the image, these items can be added or deleted through the button.
Item is composed of Header and Detail. The adapter also has HeaderAdapter and DetailAdapter respectively.
I'm using ConcatAdapter to make sure there are HeaderAdapter and DetailAdatper per group rather than the whole list.
Because I thought it would be more manageable than using multiview types in one adapter (purely my opinion).
But I have a question. HeaderAdapter.
As you can see from the image, there is one header per group. So, there must be only one HeaderItem in the HeaderAdapter of each group.
In this case, I don't think there is much reason to use the Adapter.
In my case, is it better to use a multiview type for one Adapter?
RoutineItem
sealed class RoutineItem(
val layoutId: Int
) {
data class Header(
val id: String = "1",
val workout: String = "2",
val unit: String = "3",
) : RoutineItem(VIEW_TYPE) {
companion object {
const val VIEW_TYPE = R.layout.routine_item
}
}
data class Detail(
val id: String = UUID.randomUUID().toString(), // UUID
val set: Int = 1,
var weight: String ="",
val reps: String = "1"
) : RoutineItem(VIEW_TYPE) {
companion object {
const val VIEW_TYPE = R.layout.item_routine_detail
}
}
}
HeaderAdapter
class HeaderAdapter(item: RoutineItem.Header) : BaseAdapter<RoutineItem.Header>(initialItem = listOf(item)) {
override fun createViewHolder(itemView: View): GenericViewHolder<RoutineItem.Header> {
return HeaderViewHolder(itemView)
}
override fun getItemCount(): Int = 1
class HeaderViewHolder(itemView: View) : GenericViewHolder<RoutineItem.Header>(itemView)
}
DetailAdapter
class DetailAdapter(private val items: List<RoutineItem.Detail> = emptyList())
: BaseAdapter<RoutineItem.Detail>(initialItem = items) {
override fun createViewHolder(itemView: View): GenericViewHolder<RoutineItem.Detail> {
return DetailViewHolder(itemView)
}
override fun getItemCount(): Int = items.size
class DetailViewHolder(itemView: View) : GenericViewHolder<RoutineItem.Detail>(itemView)
}
Activity
class MainActivity : AppCompatActivity() {
var concatAdpater: ConcatAdapter = ConcatAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rv: RecyclerView = findViewById(R.id.rv)
val adapterItems: ArrayList<Pair<RoutineItem.Header, List<RoutineItem.Detail>>> = arrayListOf()
val childItems : List<RoutineItem.Detail> = listOf(
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail(),
RoutineItem.Detail()
)
adapterItems.add(Pair(RoutineItem.Header(), childItems))
adapterItems.add(Pair(RoutineItem.Header(), childItems))
adapterItems.add(Pair(RoutineItem.Header(), childItems))
for ((header, list) in adapterItems) { // 1 adapter per group
concatAdpater.addAdapter(HeaderAdapter(header))
concatAdpater.addAdapter(DetailAdapter(list))
}
rv.adapter = concatAdpater
}
}
Because it is a test code, there are parts that are not functionally implemented! (Ex. Dynamically adding and deleting items..)
It's always better to use a single adapter because item animation and state changes are way more managable with DiffUtil. Also it's easier to maintain and way more efficient (in terms of speed and resource managment).
More detailed answers:
https://stackoverflow.com/a/53117359/6694770
https://proandroiddev.com/writing-better-adapters-1b09758407d2

handle firebase data changes without adapter

I have a kotlin app of ListView of items from firebase.
The app can insert items to the firebase and everything works great with simple array adapter.
BUT what if I need to do that without using simple array adapter?
In the data class I have toString() and I need to use it without the simple adapter
data class Product(var name:String, var quantity: Int, var size: String, var comment:String){
private var _ky:String = ""
constructor() : this("", 0, "", "")
override fun toString(): String {
return "%s, %d, %s\n%s".format(this.name,this.quantity,this.size,this.comment)
}
fun fillKey(ky:String) {
this._ky = ky
}
fun giveKey():String {
return this._ky
}
}
In the activity I have this
val products = ArrayList<Product>()
val adptr: ArrayAdapter<Product> by lazy {ArrayAdapter<Product>(this, android.R.layout.simple_list_item_checked, products)}
But I don't know what to do with this if I don't use an adapter
lvItems.adapter = adptr
lvItems.choiceMode = ListView.CHOICE_MODE_MULTIPLE
myRef.child("products").addChildEventListener(object : ChildEventListener {
override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
val p: Product = snapshot.getValue(Product::class.java) as Product
p.fillKey(snapshot.key!!)
products.add(p)
adptr.notifyDataSetChanged()
lvItems.smoothScrollToPosition(products.size - 1)
I need to do make it work without an adapter because of my mentor decision.

Room not updating entities with #Update(onConflict = OnConflictStrategy.REPLACE)

My application uses Google Places API which data I later use to get weather from openweather.
I have a SearchFragment with RecyclerView where this happens.
Inside SearchFragment I observe the list I'm getting:
viewModel.predictions.observe(viewLifecycleOwner) {
citiesAdapter.submitList(it)
}
<...>
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_fragment_weather, menu)
<...>
searchView.onQueryTextChanged {
viewModel.searchQuery.value = it
}
}
My viewModel:
class SearchViewModel #Inject constructor(
private val repository: AutocompleteRepository,
private val weatherRepository: WeatherRepository
) : ViewModel() {
fun provideClient(client: PlacesClient) {
repository.provideClient(client)
}
val searchQuery = MutableStateFlow("")
private val autocompleteFlow = searchQuery.flatMapLatest {
repository.getPredictions(it)
}
val predictions = autocompleteFlow.asLiveData()
fun onAddPlace(place: PlacesPrediction, added: Boolean) {
viewModelScope.launch {
repository.update(place, added)
if (added) weatherRepository.addWeather(place)
else weatherRepository.delete(place)
}
}
fun onDestroy() = viewModelScope.launch {repository.clearDb()}
}
Inside adapter I bind my items like this:
inner class CityViewHolder(private val binding: ItemCityToAddBinding) : RecyclerView.ViewHolder(binding.root) {
init {
binding.apply {
btnAdd.setOnClickListener {
val position = adapterPosition
if (position != RecyclerView.NO_POSITION) {
val place = getItem(position)
btnAdd.animate().rotation(if (place.isAdded) 45f else 0f).start()
println("Current item state (isAdded): ${place.isAdded}")
listener.onAddClick(place, !place.isAdded)
}
}
}
}
fun bind(prediction : PlacesPrediction) {
binding.apply {
val cityName = prediction.fullText.split(", ")[0]
locationName.text = cityName
fullName.text = prediction.fullText
btnAdd.animate().rotation(if (prediction.isAdded) 45f else 0f).start()
}
}
}
Where listener is passed to my adapter as a parameter from my fragment:
override fun onAddClick(place: PlacesPrediction, isAdded: Boolean) {
viewModel.onAddPlace(place, isAdded)
println("Parameter passed to onClick: $isAdded, placeId = ${place.placeId}")
}
<...>
val citiesAdapter = CitiesAdapter(this)
My repository's update() method looks like this:
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().update(place.copy(isAdded = added))
And finally, my dao's update:
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(prediction: PlacesPrediction)
This is all tied up on PlacesPrediction class, an here it is:
#Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
val isAdded: Boolean = false
) {
#PrimaryKey(autoGenerate = true) var id: Int = 0
}
So, my problem is that PlacesPredictions entries in my database are not getting updated. Actually, the only field I want to update with the code provided above is isAdded, but it stays the same after I press btnAdd of my list item. I used Android Studio's Database Inspector to verify that.
I tried using #Insert instead like so:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(prediction: PlacesPrediction)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added))
But strangely it only inserts a copy of place, the original item I clicked on stays the same.
Workaround
I get the desired behavior only if I hack my way to it:
#Entity(tableName = "autocomplete_table")
data class PlacesPrediction(
val fullText: String,
val latitude: Double,
val longitude: Double,
val placeId: String,
var isAdded: Boolean = false,
#PrimaryKey(autoGenerate = true) var id: Int = 0
)
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.copy(isAdded = added, id = place.id))
And I don't like this soution at all. So my question is: how do I make #Update work?
As you probably already understood, the generated copy method of data classes ignores all members declared outside the constructor. So place.copy(isAdded = added) will generate a copy of all constructor parameters, but leave the id as the default 0, meaning a new element should be inserted, instead of updating an existing one.
Now this is my personal opinion:
Having the id as constructor parameter is the most elegant solution, as updates will work out of the box.
However if you dislike it that much, maybe an extension function might help you:
inline fun PlacesPrediction.preserveId(copyBuilder: PlacesPrediction.() -> PlacesPrediction): PlacesPrediction{
val copy = copyBuilder(this)
copy.id = this.id
return copy
}
//usage
suspend fun update(place: PlacesPrediction, added: Boolean) =
database.dao().insert(place.preserveId { copy(isAdded = added) })

Property value changes in ViewModel before it is updated in RecyclerViewAdapter

What I have is a ViewModel, a Fragment and a RecyclerViewAdapter.
The use case is as follows:
User wants to change the name of one item in the RecyclerView.
His command is sent to the ViewModel.
ViewModel updates the item and postValue using LiveData
Fragment observes on the live property and sends a command to the RecyclerViewAdapter to update the list.
List is updated.
Snippet containing only methods that I checked are valid for this problem.
data class Name(
var firstName: String = "",
var lastName: String = "",
)
class NameListRecyclerViewAdapter : RecyclerView.Adapter<NameListRecyclerViewAdapter.ListViewHolder>() {
private var names: List<Name> = listOf()
fun setData(newList: List<Name>) {
names = newList
notifyDataSetChanged()
}
}
class NameListViewModel : ViewModel() {
private var names: MutableList<Name>? = null
private val _nameList = MutableLiveData<MutableList<Name>>()
val nameList: LiveData<MutableList<Name>>
get() = _nameList
fun changeFirstName(index: Int, name: String) {
names?.get(0)?.firstName = name // in this very moment the names property in NameListRecyclerViewAdapter is being changed
_nameList.postValue(names)
}
}
class NamesListFragment : Fragment() {
private lateinit var viewModel: NameListViewModel
private lateinit var recyclerViewAdapter: NameListRecyclerViewAdapter
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
viewModel = ViewModelProvider(
requireActivity(),
NameListViewModelFactory(requireActivity().applicationContext)
).get(NameListViewModel::class.java)
recyclerViewAdapter = NameListRecyclerViewAdapter()
recycler_view_layout.apply {
setHasFixedSize(true)
layoutManager =
LinearLayoutManager(context).apply {
orientation = LinearLayoutManager.VERTICAL
}
adapter = recyclerViewAdapter
}
viewModel.nameList.observe(
viewLifecycleOwner,
Observer {
val result = it ?: return#Observer
recyclerViewAdapter.setData(result)
}
)
}
}
For the first time, the name is changing as it should, but the second one is very peculiar.
The name (the property in the RecyclerViewAdapter) is updated not when it should (in step 5) but in step 3 - even before the new value is posted using liveData.
My theory is that its the same list in the ViewModel and the RecyclerViewAdapter, but I have no idea why is that, why one is not a copy of the other??
Update The problem seems to be solved when in the ViewModel i add .toMutableList() as follows:
class NameListViewModel : ViewModel() {
//...
fun changeFirstName(index: Int, name: String) {
names?.get(0)?.firstName = name
_nameList.postValue(names.toMutableList()) // instead of simple _nameList.postValue(names)
}
}
Does that mean that live data property has the exact same list and I have to make sure to copy it always?

Android Room, how to save an entity with one of the variables being a sealed class object

I want to save in my Room database an object where one of the variables can either be of on type or another. I thought a sealed class would make sense, so I took this approach:
sealed class BluetoothMessageType() {
data class Dbm(
val data: String
) : BluetoothMessageType()
data class Pwm(
val data: String
) : BluetoothMessageType()
}
Or even this, but it is not necessary. I found that this one gave me even more errors as it did not know how to handle the open val, so if I find a solution for the first version I would be happy anyway.
sealed class BluetoothMessageType(
open val data: String
) {
data class Dbm(
override val data: String
) : BluetoothMessageType()
data class Pwm(
override val data: String
) : BluetoothMessageType()
}
Then the Entity class
#Entity(tableName = MESSAGES_TABLE_NAME)
data class DatabaseBluetoothMessage(
#PrimaryKey(autoGenerate = true)
val id: Long = 0L,
val time: Long = Instant().millis,
val data: BluetoothMessageType
)
I have created a TypeConverter to convert it to and from a String as well, so I assume that it is not a problem.
First, is this possible? I assume this should function in a similar way that it would with an abstract class, but I have not managed to find a working solution with that either. If it is not possible, what sort of approach should I take when I want to save some data that may be either of one or another type if not with sealed classes?
We faced such problem when we tried using Polymorphism in our domain, and we solved it this way:
Domain:
We have a Photo model that looks like this:
sealed interface Photo {
val id: Long
data class Empty(
override val id: Long
) : Photo
data class Simple(
override val id: Long,
val hasStickers: Boolean,
val accessHash: Long,
val fileReferenceBase64: String,
val date: Int,
val sizes: List<PhotoSize>,
val dcId: Int
) : Photo
}
Photo has PhotoSize inside, it looks like this:
sealed interface PhotoSize {
val type: String
data class Empty(
override val type: String
) : PhotoSize
data class Simple(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val size: Int,
) : PhotoSize
data class Cached(
override val type: String,
val location: FileLocation,
val width: Int,
val height: Int,
val bytesBase64: String,
) : PhotoSize
data class Stripped(
override val type: String,
val bytesBase64: String,
) : PhotoSize
}
Data:
There is much work to do in our data module to make this happen. I will decompose the process to three parts to make it look easier:
1. Entity:
So, using Room and SQL in general, it is hard to save such objects, so we had to come up with this idea. Our PhotoEntity (Which is the Local version of Photo from our domain looks like this:
#Entity
data class PhotoEntity(
// Shared columns
#PrimaryKey
val id: Long,
val type: Type,
// Simple Columns
val hasStickers: Boolean? = null,
val accessHash: Long? = null,
val fileReferenceBase64: String? = null,
val date: Int? = null,
val dcId: Int? = null
) {
enum class Type {
EMPTY,
SIMPLE,
}
}
And our PhotoSizeEntity looks like this:
#Entity
data class PhotoSizeEntity(
// Shared columns
#PrimaryKey
#Embedded
val identity: Identity,
val type: Type,
// Simple columns
#Embedded
val locationLocal: LocalFileLocation? = null,
val width: Int? = null,
val height: Int? = null,
val size: Int? = null,
// Cached and Stripped columns
val bytesBase64: String? = null,
) {
data class Identity(
val photoId: Long,
val sizeType: String
)
enum class Type {
EMPTY,
SIMPLE,
CACHED,
STRIPPED
}
}
Then we have this compound class to unite PhotoEntity and PhotoSizeEntity together, so we can retrieve all data required by our domain's model:
data class PhotoCompound(
#Embedded
val photo: PhotoEntity,
#Relation(entity = PhotoSizeEntity::class, parentColumn = "id", entityColumn = "photoId")
val sizes: List<PhotoSizeEntity>? = null,
)
2. Dao
So our dao should be able to store and retrieve this data. You can have two daos for PhotoEntity and PhotoSizeEntity instead of one, for the sake of flexibility, but in this example we will use a shared one, it looks like this:
#Dao
interface IPhotoDao {
#Transaction
#Query("SELECT * FROM PhotoEntity WHERE id = :id")
suspend fun getPhotoCompound(id: Long): PhotoCompound
#Transaction
suspend fun insertOrUpdateCompound(compound: PhotoCompound) {
compound.sizes?.let { sizes ->
insertOrUpdate(sizes)
}
insertOrUpdate(compound.photo)
}
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entity: PhotoEntity)
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrUpdate(entities: List<PhotoSizeEntity>)
}
3. Adapter:
After solving the problem of saving data to SQL database, we now need to solve the problem of converting between domain and local entities. Our Photo's converter aka adapter looks like this:
fun Photo.toCompound() = when(this) {
is Photo.Empty -> this.toCompound()
is Photo.Simple -> this.toCompound()
}
fun PhotoCompound.toModel() = when (photo.type) {
PhotoEntity.Type.EMPTY -> Photo.Empty(photo.id)
PhotoEntity.Type.SIMPLE -> this.toSimpleModel()
}
private fun PhotoCompound.toSimpleModel() = photo.run {
Photo.Simple(
id,
hasStickers!!,
accessHash!!,
fileReferenceBase64!!,
date!!,
sizes?.toModels()!!,
dcId!!
)
}
private fun Photo.Empty.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.EMPTY
)
return PhotoCompound(photo)
}
private fun Photo.Simple.toCompound(): PhotoCompound {
val photo = PhotoEntity(
id,
PhotoEntity.Type.SIMPLE,
hasStickers = hasStickers,
accessHash = accessHash,
fileReferenceBase64 = fileReferenceBase64,
date = date,
dcId = dcId,
)
val sizeEntities = sizes.toEntities(id)
return PhotoCompound(photo, sizeEntities)
}
And for the PhotoSize, it looks like this:
fun List<PhotoSize>.toEntities(photoId: Long) = map { photoSize ->
photoSize.toEntity(photoId)
}
fun PhotoSize.toEntity(photoId: Long) = when(this) {
is PhotoSize.Cached -> this.toEntity(photoId)
is PhotoSize.Empty -> this.toEntity(photoId)
is PhotoSize.Simple -> this.toEntity(photoId)
is PhotoSize.Stripped -> this.toEntity(photoId)
}
fun List<PhotoSizeEntity>.toModels() = map { photoSizeEntity ->
photoSizeEntity.toModel()
}
fun PhotoSizeEntity.toModel() = when(type) {
PhotoSizeEntity.Type.EMPTY -> this.toEmptyModel()
PhotoSizeEntity.Type.SIMPLE -> this.toSimpleModel()
PhotoSizeEntity.Type.CACHED -> this.toCachedModel()
PhotoSizeEntity.Type.STRIPPED -> this.toStrippedModel()
}
private fun PhotoSizeEntity.toEmptyModel() = PhotoSize.Empty(identity.sizeType)
private fun PhotoSizeEntity.toCachedModel() = PhotoSize.Cached(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
bytesBase64!!
)
private fun PhotoSizeEntity.toSimpleModel() = PhotoSize.Simple(
identity.sizeType,
locationLocal?.toModel()!!,
width!!,
height!!,
size!!
)
private fun PhotoSizeEntity.toStrippedModel() = PhotoSize.Stripped(
identity.sizeType,
bytesBase64!!
)
private fun PhotoSize.Cached.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.CACHED,
locationLocal = location.toEntity(),
width = width,
height = height,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Simple.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.SIMPLE,
locationLocal = location.toEntity(),
width = width,
height = height,
size = size
)
private fun PhotoSize.Stripped.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.STRIPPED,
bytesBase64 = bytesBase64
)
private fun PhotoSize.Empty.toEntity(photoId: Long) = PhotoSizeEntity(
PhotoSizeEntity.Identity(photoId, type),
PhotoSizeEntity.Type.EMPTY
)
That's it!
Conclusion:
To save a sealed class to Room or SQL, whether as an Entity, or as an Embedded object, you need to have one big data class with all the properties, from all the sealed variants, and use an Enum type to indicate variant type to use later for conversion between domain and data, or for indication in your code if you don't use Clean Architecture. Hard, but solid and flexible. I hope Room will have some annotations that can generate such code to get rid of the boilerplate code.
PS: This class is taken from Telegram's scheme, they also solve the problem of polymorphism when it comes to communication with a server. Checkout their TL Language here: https://core.telegram.org/mtproto/TL
PS2: If you like Telegram's TL language, you can use this generator to generate Kotlin classes from scheme.tl files: https://github.com/tamimattafi/mtproto
EDIT: You can use this code generating library to automatically generate Dao for compound classes, to make it easier to insert, which removes a lot of boilerplate to map things correctly.
Link: https://github.com/tamimattafi/android-room-compound
Happy Coding!
In my case I did the following:
sealed class Status2() {
object Online : Status2()
object Offline : Status2()
override fun toString(): String {
return when (this) {
is Online ->"Online"
is Offline -> "Offline"
}
}
}
class StatusConverter{
#TypeConverter
fun toHealth(value: Boolean): Status2 {
return if (value){
Status2.Online
} else{
Status2.Offline
}
}
#TypeConverter
fun fromHealth(value: Status2):Boolean {
return when(value){
is Status2.Offline -> false
is Status2.Online -> true
}
}
}
#Dao
interface CourierDao2 {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertStatus(courier: CourierCurrentStatus)
#Query("SELECT * FROM CourierCurrentStatus")
fun getCourierStatus(): Flow<CourierCurrentStatus>
}
#Entity
data class CourierCurrentStatus(
#PrimaryKey
val id: Int = 0,
var status: Status2 = Status2.Offline
)
and it works like a charm

Categories

Resources