How do I create a mapper from entity to dto, where dto is nested? - spring

Two entities with corresponding dtos are given
entities:
class GalleryBlock(
var images: List<Image?>,
override val sortIndex: Int = 0,
) : ArticleBlock(sortIndex)
class Image(
var url: String,
var imageSize: ImageSize,
override var id: Long,
override var lastModified: Date,
override var lastModifiedBy: String? = null ) : DBEntity
enum class ImageSize {
SMALL,
MEDIUM,
LARGE, }
dtos
data class GalleryBlockDto(
var images: List<ImageDto>,
override val sortIndex: Int,
) : ArticleBlockDto
data class ImageDto(
var id: Long,
var url: String,
var imageSize: ImageSize,
)
for the mapping I have written an interface Mapper
interface Mapper<E, D> {
fun fromEntity(entity: E): D
}
for the mapping from class Image to ImageDto I have create a class ImageMapper
#Component
class ImageMapper: Mapper<Image, ImageDto> {
override fun fromEntity(entity: Image): ImageDto {
return ImageDto(entity.id, entity.url, entity.imageSize)
}
}
when mapping the GalleryBlock I did the same but I get a type mismatch.
#Component
class GalleryBlockMapper: Mapper<GalleryBlock, GalleryBlockDto> {
override fun fromEntity(entity: GalleryBlock): GalleryBlockDto {
val images = entity.images
val sortIndex = entity.sortIndex
return GalleryBlockDto(images, sortIndex)
}
}
is my approach correct? and how do I get the type mismatch fixed without changing the fields of Dto and entities?

In your GalleryBlockMapper you pass Image instances to the GalleryBlockDto. But ImageDto instances are needed. The ImageMapper needs to be injected into the GalleryBlockMapper, so the images can be mapped.
#Component
class GalleryBlockMapper(private val imageMapper: ImageMapper): Mapper<GalleryBlock, GalleryBlockDto> {
override fun fromEntity(entity: GalleryBlock): GalleryBlockDto {
val images = entity.images.map { imageMapper.fromEntity(it) }
val sortIndex = entity.sortIndex
return GalleryBlockDto(images, sortIndex)
}
}

Related

LazyColumn item not updated accordingly while list in room table already updated

When the Icon clicked, viewModel.onLockIconClicked(it) is called to reverse the value of isLock in db.
The Icon is expected to be updated according based on the value of isLock.
I've checked the value did reversed in db table. But LazyColumn not update accordingly.
What did I miss? Thanks a lot!
Ex, initially, Screen: icon = lock and Db: isLock = true,
when Icon clicked, Screen: icon = lock and Db: isLock = false,
while expected is Screen: icon = lock_open and Db: isLock = false.
ListScreen:
#Composable
fun ListScreen(context: Context) {
val viewModel: ListViewModel =
viewModel(factory = ListViewModelFactory(Db.getInstance(context)))
val list by viewModel.list.collectAsState(initial = emptyList())
Scaffold() {
SwipeRefresh(
state = rememberSwipeRefreshState(viewModel.isRefreshing),
onRefresh = { }
) {
LazyColumn(
state = rememberLazyListState(),
) {
items(list) {
Row() {
Icon(
painter = painterResource(if (it.isLock) R.drawable.ic_baseline_lock_24 else R.drawable.ic_baseline_lock_open_24),
contentDescription = null,
modifier = Modifier.clickable() { viewModel.onLockIconClicked(it) }
)
Text(it.code)
}
}
}
}
}
}
ListViewModel:
class ListViewModel(db: Db) : ViewModel() {
private val sumDao = db.sumDao()
val list = sumDao.getAllRows()
var isRefreshing by mutableStateOf(false)
private set
//init
init {
viewModelScope.launch(Dispatchers.IO) {
val initialCodeList = listOf("aaa", "bbb")
for (code in initialCodeList) {
val sum = Sum()
sum.code = code
sumDao.insert(sum)
}
}
}
fun onLockIconClicked(sum: Sum) {
sum.isLock = !sum.isLock
viewModelScope.launch(Dispatchers.IO) {
sumDao.update(sum)
}
}
}
class ListViewModelFactory(private val db: Db) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ListViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return ListViewModel(db) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Sum:
#Entity(tableName = "sum", primaryKeys = ["code"])
data class Sum(
#ColumnInfo(name = "code")
var code: String = "",
#ColumnInfo(name = "is_lock")
var isLock: Boolean = true
)
SumDao:
#Dao
interface SumDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(sum: Sum): Long
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(sum: Sum): Int
#Delete
suspend fun delete(sum: Sum): Int
#Query("select * from sum")
fun getAllRows(): Flow<List<Sum>>
}
Db:
#Database(entities = [Sum::class], version = 1, exportSchema = false)
abstract class Db : RoomDatabase() {
abstract fun sumDao(): SumDao
companion object {
#Volatile
private var INSTANCE: Db? = null
fun getInstance(context: Context): Db {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
Db::class.java,
"db"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
Consider taking the State-in-Compose for a better understanding of the concepts of state-handling in Compose.
I'm sorry but the information that you have provided is massive, so I can't pinpoint the source of the bug, but here's what you can do for now:
In your Dao class, just replace the words Flow<List<Sum>> with LiveData<List<Sum>>
In your ViewModel, you can get access to the LiveData inside the init like so
var list by mutableStateListOf<Sum>()
init{
sumDao.getAllRows().observeForever{
list = it
}
}
Now, list would ideally be updated every time the value in the databse changes, which infact would trigger recompositions since I am using a direct mutableStateListOf object here.
The problem may lie anywhere:
Since the class Sum is a custom-made class, it may have been experiencing issues triggering recompositions, which is a common problem among new developers, and even some experienced ones nowadays.
Since you are declaring the viewModel inside the Composable, wrong instances of ViewModels may have been passed around, leading to state-inconsistency - always try to declare your viewModels in the top-most layer possibly, i.e., somewhere like the onCreate method of the activity. Fragments are discourages so you should not face any problems over there.
Since you were not actively observing the Flow anywhere, that could have lead to the variable not being updated at all in the ViewModel, which would again lead to UI-inconsistency.

performing query operations in Room ORM restarts my application

I am using Room to save objects in the database. and any time I perform an operation like insert or get all my application restarts.
this is my entity class:
#Entity
class Task(
#PrimaryKey val id: Int,
#ColumnInfo(name = "title") val title : String,
#ColumnInfo(name = "detail") val detail : String
)
this is my database class:
#Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun taskDao() : TaskDao
companion object{
var INSTANCE: AppDatabase? = null
fun getAppDatabase(context: Context): AppDatabase? {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context,
AppDatabase::class.java,
"task-database"
)
.build()
}
return INSTANCE
}
}
}
I try calling it from my AndroidViewModel class like this:
class AddTaskViewModel(application: Application) : AndroidViewModel(application) {
private val context = application
var title : MutableLiveData<String> = MutableLiveData("")
var detail : MutableLiveData<String> = MutableLiveData("")
fun saveTask(){
val task = Task(id = 1, title = title.value!!, detail = detail.value!! )
val taskDao : TaskDao? = AppDatabase.getAppDatabase(context)?.taskDao()
taskDao!!.insertAll(task)
Toast.makeText(context, "it is done", Toast.LENGTH_LONG).show()
}
fun changeTitle(text: String){
title.value = text
}
fun changeDetail(text: String){
detail.value = text
}
}
and at the point where taskDao!!.insertAll(task) is called the app restarts.
I think the issue might be that when you are inserting tasks into your database the id which is supposed to be the primary key is violated, in the sense that it's not unique, so the unique constraints fail
val task = Task(id = 1, title = title.value!!, detail = detail.value!!)
Try incrementing the id for example or letting room do that for you
I had to run the function in a coroutine because it was locking the UI processas seen in this post

Improving DSL syntax

To start learning design of DSLs using Kotlin language features, I have
the attempt below at a toy DSL for creating groups of members with members
having names. I am looking for pointers/hints on the following
How can I avoid having to separate groups by a semicolon, if no semicolon the compiler gives
Groups.kt:31:45: error: unresolved reference: member
val grp = group { member { name ("Bob") } member { name ("Sandy") } }
Can i get to use a lambda for setting name instead of function call?
Can I avoid having to have name be mutable in class MEMBER?
My code is
fun group(create: GROUP.() -> Unit) = GROUP().apply(create)
class GROUP {
private val members = mutableSetOf<MEMBER>()
fun member(create: MEMBER.() -> Unit) {
val member = MEMBER()
member.create()
members.add(member)
}
override fun toString() = members.toString()
}
class MEMBER() {
var name = ""
set(value) {
field = value
}
fun name(nameToSet: String) {
name = nameToSet
}
override fun toString() = "MEMBER(" + name + ")"
}
fun main(args: Array<String>) {
val grp = group { member { name ("Bob") }; member { name ("Sandy") } }
println(grp)
}
Currently the output of above code is
[MEMBER(Bob), MEMBER(Sandy)]
How can I avoid having to separate groups by a semicolon
By using the idiomatic format, using separate lines. After all, the whole point of a DSL is to make the code very readable by showing the hierarchical structure, and doing everything on a single line defeats the wole purpose:
val grp = group {
member {
name ("Bob")
}
member {
name ("Sandy")
}
}
Can I get to use a lambda for setting name instead of function call?
It would be more logical and idiomatic to remove the name function and to simply assign a value to the property:
name = "Bob"
But yes, you can also replace your name function by
fun name(block: () -> String) {
this.name = block()
}
and use
name {
"Sandy"
}
Can I avoid having to have name be mutable in class MEMBER?
Yes: the lambda passed to the member() function would customize an additional MemberBuilder class, that would be mutable, but would allow to create an immutable MEMBER:
fun group(create: GROUP.() -> Unit) = GROUP().apply(create)
class GROUP {
private val members = mutableSetOf<MEMBER>()
fun member(configure: MemberBuilder.() -> Unit) {
val memberBuilder = MemberBuilder()
memberBuilder.configure()
members.add(memberBuilder.build())
}
override fun toString() = members.toString()
}
class MEMBER(val name: String) {
override fun toString() = "MEMBER($name)"
}
class MemberBuilder {
var name = "";
fun build() = MEMBER(name)
}
fun main(args: Array<String>) {
val grp = group {
member {
name = "Bob"
}
member {
name = "Sandy"
}
}
println(grp)
}
Also, note that classes, by convention, are PascalCased, not ALL_CAPS.

LiveData Transformation not getting triggered

I subscribed to ids and search in the ui but i wasn't getting any results so i stepped through with the debugger and found out that the transformation is not getting triggered after the first time. So when i call setIds the first time ids gets updated but for every call after the first one the transformation won't trigger. Same goes for the search.
Any ideas what might possible go wrong?
class MyViewModel : ViewModel() {
private val repository = Repository.sharedInstance
var recentRadius: LiveData<List<RecentRadius>>?
var recentRoute: LiveData<List<RecentRoute>>?
init {
recentRadius = repository.recentRadius()
recentRoute = repository.recentRoute()
}
private val idsInput = MutableLiveData<String>()
fun setIdsInput(textId: String) {
idsInput.value = textId
}
val ids: LiveData<List<String>> = Transformations.switchMap(idsInput) { id ->
repository.ids(id)
}
private val searchInput = MutableLiveData<Search>()
fun setSearchInput(search: Search) {
searchInput.value = search
}
val search: LiveData<SearchResult> = Transformations.switchMap(searchInput) { search ->
when (search.type) {
SearchType.ID -> repository.id(search)
SearchType.RADIUS -> repository.radius(search)
SearchType.ROUTE -> repository.route(search)
}
}
}
The most common reason why transformation don't get triggered is when there is no Observer observing it or the input LiveData is not getting changed.
Below example illustrates use of map when observer is attached in the activity.
Activity
class MainActivity : AppCompatActivity() {
lateinit var mBinding : ActivityMainBinding
private val mViewModel : MainViewModel by lazy {
getViewModel { MainViewModel(this.application) }
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
mBinding.vm = mViewModel
// adding obeserver
mViewModel.videoName.observe(this, Observer<String> { value ->
value?.let {
//Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
}
}
ViewModel with map
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.map(videoData) { "updated : "+it.webUrl }
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
ViewModel with switchMap
class MainViewModel(val appContext : Application) : AndroidViewModel(appContext) {
private val TAG = "MainViewModel"
var videoData = MutableLiveData<VideoDownload>()
var videoName : LiveData<String>
init {
// Update the data
videoName = Transformations.switchMap(videoData) { modData(it.webUrl) }
}
private fun modData(str: String): LiveData<String> {
val liveData = MutableLiveData<String>()
liveData.value = "switchmap : "+str
return liveData
}
fun onActionClick(v : View) {
// change data
videoData.value = VideoDownload(System.currentTimeMillis().toString())
}
fun onReActionClick(v : View) {
// check data
Toast.makeText(appContext, videoName.value, Toast.LENGTH_LONG).show()
}
}
for me, it was because the observer owner was a fragment. It stopped triggering when navigating to different fragments. I changed the observer owner to the activity and it triggered as expected.
itemsViewModel.items.observe(requireActivity(), Observer {
The view model was defined as a class property:
private val itemsViewModel: ItemsViewModel by lazy {
ViewModelProvider(requireActivity()).get(ItemsViewModel::class.java)
}
If you really want it to be triggered.
fun <X, Y> LiveData<X>.forceMap(
mapFunction: (X) -> Y
): LiveData<Y> {
val result = MutableLiveData<Y>()
this.observeForever {x->
if (x != null) {
result.value = mapFunction.invoke(x)
}
}
return result
}

Swift: Instance member cannot be used on type

I'm trying to call a variable from my Users class in Data class but I'm thrown this error
Instance member 'user' cannot be used on type 'Data'
Data class code:
import Foundation
class Data {
static let sharedInstance = Data()
let user = Users()
var id = (user.userId).string
//Login.swift
static let quarterlyEndpoint: String = "http://anyapi/api/users/\(id)/quarterly/tasks"
// for prevent from creating this class object
private init() { }
}
Users class code:
var userId: Int {
guard let _userId = getId() else {
return 0
}
return _userId
}
UPDATE
I can call the function but when I print my weeklyEnd I would get http://anyapi/api/users/(Function)/quarterly/tasks instead of http://anyapi/api/users/3/quarterly/tasks
let user = Users()
func getId() -> String
{
let id = String(user.userId)
return id
}
static let weeklyEndpoint: String = "http://anyapi/api/users/\(getId)/quarterly/tasks"
UPDATE 2
import Foundation
class Data {
static let sharedInstance = Data()
let user = Users()
let id:String?
let weeklyEndpoint: String?
// for prevent from creating this class object
private init() {
id = String((user.userId))
//Login.swift
weeklyEndpoint = "http://anyapi/api/users/\(id)/quarterly/tasks"
}
}
I'm having trouble calling my weeklyEndPoint from outside the Data class. I've tried Data.weeklyEndPoint but it's not working
Try making a method inside the Data class like this .
func getusrid ()-> String
{
var id = (user.userId).string
return id ;
}
You cannot get value from instance variable outside any method
EDIT:
class Data {
static let sharedInstance = Data()
let id:String?
let quarterlyEndpoint: String?
let user = Users()
private init()
{
id = String((user.userId))
quarterlyEndpoint = "http://anyapi/api/users/\(id)/quarterly/tasks"
}
}

Resources