Kotlin DSL for collections - kotlin

This is a simple data structure House which contains a (nullable) list of Person :
data class Person(val name: String,
val militaryYear: Int? = null,
val firstPregnantYear: Int? = null)
data class House(val persons: List<Person>?)
Person maybe male or female. Male has militaryYear value , Female has firstPregnantYear value.
I want to write a DSL to build the House object , like this :
val house = house {
person("John") {
militaryYear = 2003
}
person("Anne") {
firstPregnantYear = 2010
}
}
This is what I have done :
data class Person(val name: String,
val militaryYear: Int? = null,
val firstPregnantYear: Int? = null) {
class Builder(private val name: String) {
var militaryYear : Int? = null
var firstPregnantYear : Int? = null
fun build(): Person = Person(name, militaryYear, firstPregnantYear)
}
}
fun person(name: String , block: Person.Builder.() -> Unit) = Person.Builder(name).apply(block).build()
data class House(val persons: List<Person>?) {
class Builder {
var persons = mutableListOf<Person>()
fun person(name: String , block: Person.Builder.() -> Unit) {
persons.add(Person.Builder(name).apply(block).build())
}
fun build(): House = House(persons = persons.takeIf { it.isNotEmpty() })
}
}
It seems works , but it doesn't forbid both militaryYear & firstPregnantYear , for example :
val house = house {
person("John") {
militaryYear = 2003
}
person("Anne") {
firstPregnantYear = 2010
}
person("Alien") {
militaryYear = 2005
firstPregnantYear = 2009
}
}
println(house)
The result :
House(persons=[
Person(name=John, militaryYear=2003, firstPregnantYear=null)
, Person(name=Anne, militaryYear=null, firstPregnantYear=2010)
, Person(name=Alien, militaryYear=2005, firstPregnantYear=2009)
])
The Alien has militaryYear & firstPregnantYear , which is incorrect.
So , I add another MaleBuilder and FemaleBuilder :
class MaleBuilder(private val name: String) {
var militaryYear = 0
fun build(): Person = Person(name, militaryYear, null)
}
class FemaleBuilder(private val name: String) {
var firstPregnantYear = 0
fun build() : Person = Person(name , null , firstPregnantYear)
}
fun male(name: String , block: Person.MaleBuilder.() -> Unit) = Person.MaleBuilder(name).apply(block).build()
fun female(name: String , block: Person.FemaleBuilder.() -> Unit) = Person.FemaleBuilder(name).apply(block).build()
And the DSL becomes :
val house = house {
male("John") {
militaryYear = 2003
}
female("Anne") {
firstPregnantYear = 2010
}
}
println(house)
OK , here is the problem , male("John") and female("Anne") are not inserted into persons . It outputs :
House(persons=null)
I think the problem comes from House's person() function :
fun person(name: String , block: Person.Builder.() -> Unit) {
persons.add(Person.Builder(name).apply(block).build())
}
The defined receiver is Person.Builder , but it is MaleBuilder and FemaleBuilder in the code.
How to solve this problem ?
Full code : https://pastebin.com/1Q6D8rzx
------- updated -------
It can be solved by introducing House.Builder.male() and House.Builder.female() functions :
fun male(name: String , block: Person.MaleBuilder.() -> Unit) {
persons.add(Person.MaleBuilder(name).apply(block).build())
}
fun female(name: String , block: Person.FemaleBuilder.() -> Unit) {
persons.add(Person.FemaleBuilder(name).apply(block).build())
}
But I think it is not a good solution. In real world example , there maybe a lot of properties , and a lot of restrictions. Is there a way to use one function to receive it ?

Related

Save a class created with the Builder pattern in Room

I need to create a table in my db where I can save Filters so I created this class like this:
#Entity
class Filter private constructor(
val name: String?,
#TypeConverters(Converters::class)
val sortBy: Sort?,
val withGenres: List<Int>?,
val withCast: List<Int>?,
) : Serializable {
#PrimaryKey(autoGenerate = true)
var id: Int? = null
data class Builder(
var name: String? = null,
var sortBy: Sort? = null,
var withGenres: List<Int> = listOf(),
var withCast: List<Int> = listOf(),
) {
fun name(name: String) = apply { this.name = name }
fun sortBy(sort: Sort?) = apply { this.sortBy = sort }
fun withGenres(genresId: List<Int>) = apply { this.withGenres = genresId }
fun withCast(castIds: List<Int>) = apply { this.withCast = castIds }
fun build() = Filter(name, sortBy, withGenres, withCast)
}
/**
* Sorting options, default order is Descendant (desc)
*/
enum class Sort {
POPULARITY, RELEASE_DATE, REVENUE, VOTE_AVERAGE;
private var order: Order = Order.DESC
override fun toString(): String {
return this.name.lowercase() + "." + this.order.name.lowercase()
}
fun setOrder(order: Order) {
this.order = order
}
enum class Order {
ASC, DESC
}
}
override fun toString(): String {
return "sortBy: ${sortBy.toString()}, withGenres: $withGenres"
}
}
But upon compilation I get the following errors:
Cannot configure how to save field withGenres into database
Cannot configure how to save field withCast into database
Entities and POJOs must have public constructor
Cannot find setter for field (all the fields)
How can I modify the code so that it works, what are the problems?

Jetpack Paging 3.0 with Airbnb/epoxy only load the first page

I am trying to use Jetpack Paging 3.0 to get all the characters in Rick and Morty API using retrofit and display it using Airbnb/epoxy. But I only get the first page I've spent several hours trying to look for a solution but no luck.
Link to API
(Rick and Morty API)
CODE:
1 Retrofit
#GET("character/")
suspend fun getAllCharactersByPage(
#Query("page") pageIndex : Int
): Response<GetAllCharactersByPageResponse>
[2] Paging Source
class AllCharacterPagingSource(
private val repository: AllCharactersRepository) : PagingSource<Int, Character>() {
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Character> {
val pageNumber = params.key ?: 1
val prevKey = if (pageNumber == 1) null else pageNumber + 1
val response = NetworkLayer.apiClient.getAllCharactersByPage(pageNumber)
response.getException?.let {
return LoadResult.Error(it)
}
return LoadResult.Page(
data = response.body.results.map { CharacterMapper.buildFrom(it) },
prevKey = prevKey,
nextKey = getPageIndexFromNext(response.body.info.next)
)
}
override fun getRefreshKey(state: PagingState<Int, Character>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
private fun getPageIndexFromNext(next: String?): Int?{
return next?.split("?page=")?.get(1)?.toInt()
}
}
[3] View Model (Pager)
class AllCharactersViewModel : ViewModel() {
private val repository = AllCharactersRepository()
val flow = Pager(
PagingConfig(Constants.PAGE_SIZE, Constants.PREFETCH_SIZE, enablePlaceholders = false)
) {
AllCharacterPagingSource(repository)
}.flow.cachedIn(viewModelScope)
}
[4] Fragment (Submitting data)
iewLifecycleOwner.lifecycleScope.launch {
allCharactersViewModel.flow.collectLatest {
pagingEpoxyController.submitData(it)
}
}
binding.charactersEpoxyRecyclerView.setController(pagingEpoxyController)
[5] Epoxy Controller
class CharactersPagingEpoxyController : PagingDataEpoxyController<Character>() {
var context : Context? = null
override fun buildItemModel(currentPosition: Int, item: Character?): EpoxyModel<*> {
return CharacterCardModel(
character = item!!,
context = context!!,
onClick = { characterId ->
}
).id("characters_${item.id}")
}
data class CharacterCardModel(
val character : Character,
val context : Context,
val onClick: (Int) -> Unit
) : ViewBindingKotlinModel<CharacterCardContainerModelBinding>(R.layout.character_card_container_model) {
override fun CharacterCardContainerModelBinding.bind() {
Glide.with(context)
.load(character.image)
.into(imageView)
characterName.text = character.name
mirroredCharacterName.text = character.name
}
}
}
Thanks in advance!!!
I guess prevKey should be something like this
val previewPage= if (pageNumber ==1 ) null else pageNumber -1

How to solve this strong dependency between those classes?

I have two classes A and B.
Class A has several properties. One of them are an instance of class B.
At some point in the main function I will define an instance a of A. I will need to do some computation on its property of type B.
This computation, however, needs another property of a.
The result is a.property3.computation(a.property1,someValue). I think it's ugly.
Here is some "pseudo-code" (in Kotlin but I am facing the same problem using other languages as well):
class B {
val property : Map<String,Int>
fun computation(val parameter1: Int, val parametre2: Double) : Int {
//doing some computation
return result
}
}
class A {
var property1 : Int
var property2 : Stirng
var property3 : B
}
fun main (){
val someValue = 0.4 //It's here to show the idea that the function `computation` needs also some other arguments that does not come from `A`'s propery
val a = A()
val result = a.property3.computation(a.property1,someValue) // here is the line that bothers me !
}
I think you should use Builder design pattern to remove computation function from B class like this:
class B {
val properties : MutableMap<String,Int> = HashMap()
fun setProperty(name: String, value: Int) {
this.properties[name] = value
}
}
class A {
var property1 : Int = 0
var property2 : String = ""
var property3 : B = B()
}
class Calculator(builder: Builder) {
private val property1 = builder.property1
private val someValue = builder.someValue
private val properties = builder.properties
companion object {
fun builder() = Builder()
}
fun computation() : Int {
//doing some computation
val result = property1 + someValue.toInt() + properties.getOrDefault("value", 0)
return result
}
class Builder {
var property1: Int = 0
var someValue: Double = 0.0;
var properties : MutableMap<String,Int> = HashMap()
fun property1(property1: Int): Builder {
this.property1 = property1
return this
}
fun someValue(someValue: Double): Builder {
this.someValue = someValue
return this
}
fun properties(properties : Map<String,Int>): Builder {
this.properties.putAll(properties);
return this
}
fun build(): Calculator {
return Calculator(this)
}
}
}
fun main (){
val someValue = 0.4 //It's here to show the idea that the function `computation` needs also some other arguments that does not come from `A`'s propery
val a = A()
a.property1 = 10
a.property3.setProperty("value", 20)
val result = Calculator.builder()
.properties(a.property3.properties)
.property1(a.property1)
.someValue(someValue)
.build()
.computation()
println(result)
}
May be you want this?
fun main (){
val someValue = 0.4
val a = A()
val result = with(a) {
property3.computation(property1,someValue)
}
}

Cannot bind a nullable property with tornadofx viewmodel

I'm trying to bind a nullable property (String?) from my domain model to a tornadofx view model. But I get a compiler error.
The nullable properties are: idCard, phone, and discharge
My code:
class Patient(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Patient>(Patients)
var patientId: Int by Patients.patientId
var name: String by Patients.name
var lastName: String by Patients.lastName
var recordNumber: Int by Patients.recordNumber
var idCard: String? by Patients.idCard
var phone: String? by Patients.phone
var age: Int by Patients.age
var gender: Char by Patients.gender
var admission: DateTime by Patients.admission
var discharge: DateTime? by Patients.discharge
var specialty: String by Patients.specialty
}
class PatientViewModel(patient: Patient) : ViewModel() {
val patientId = bind { patient.observable(Patient::patientId) }
val name = bind { patient.observable(Patient::name)}
val lastName = bind { patient.observable(Patient::lastName) }
val recordNumber = bind { patient.observable(Patient::recordNumber) }
val idCard = bind { patient.observable(Patient::idCard) }
val phone = bind { patient.observable(Patient::phone)}
val age = bind { patient.observable(Patient::age) }
val gender = bind { patient.observable(Patient::gender) }
val admission = bind { patient.observable(Patient::admission) }
val discharge = bind { patient.observable(Patient::discharge) }
val specialty = bind { patient.observable(Patient::specialty) }
}
Compiler error:
e: D:\projects\manager\src\main\kotlin\manager\model\Patient.kt: (49, 18): Type inference failed: Cannot infer type parameter T in inline fun <reified PropertyType : Property<T>, reified T : Any, ResultType : PropertyType> bind(autocommit: Boolean = ..., forceObjectProperty: Boolean = ..., defaultValue: T? = ..., noinline propertyProducer: () -> PropertyType?): ResultType
None of the following substitutions
(Boolean,Boolean,String?,() -> Property<String?>?)
(Boolean,Boolean,Any?,() -> Property<String?>?)
can be applied to
(() -> ObjectProperty<String?>)
Try doing this with your model bindings:
val patientId = bind(Patient::patientId)

Handling lists of two different types with same code using functional programming in kotlin

I have two lists with different types list1 and list2 . I have a method which does the same operation on the lists.
I'm using lambdas where I cannot access the property as (it.prop1) if I'm using List of type Any.
Is there any solution to avoid this issue with lambdas?
val list1: List<Student> = ..
val list2: List<Teacher> = ..
list1.filter {
school.contains(it.prop1) }
.forEach {
total += it.prop2.toLong()
}
list2.filter {
school.contains(it.prop1) }
.forEach {
total += it.prop2.toLong()
}
Thanks.
Try this:
object Test {
private fun isContains(school: Set<Int>, any: Any) = when (any) {
is Student -> school.contains(any.prop1)
is Teacher -> school.contains(any.prop1)
else -> false
}
private fun value(any: Any) = when (any) {
is Student -> any.prop2
is Teacher -> any.prop2
else -> throw NoWhenBranchMatchedException("any should be Student or Teacher")
}
#JvmStatic
fun main(args: Array<String>) {
val school = setOf(1, 2)
val list = listOf(Student(1, 1), Student(2, 2), Student(3, 3), Teacher(1, 1), Teacher(2, 2), Teacher(3, 3))
val total = list.filter {
isContains(school, it)
}.map {
value(it)
}.sum()
println("Total: $total")
}
private class Student(val prop1: Int, val prop2: Int)
private class Teacher(val prop1: Int, val prop2: Int)
}
You may use Type Checks and Casts
class Student(val prop1:Int, val prop2:Int)
class Teacher(val prop1:Int, val prop2:Int)
val list : List<Any> = listOf(Student(1,1),Student(2,2),Student(3,3),Teacher(1,1),Teacher(2,2),Teacher(3,3))
var total : Long = 0
val school : Array<Int> = arrayOf(1,2)
list.filter{
if(it is Student)
{
school.contains((it as Student).prop1)
}
else if(it is Teacher)
{
school.contains((it as Teacher).prop1)
}
else
{
false
}
}.forEach{
if(it is Student)
{
total += (it as Student).prop2.toLong()
}
else if(it is Teacher)
{
total += (it as Teacher).prop2.toLong()
}
}
println(total) //print 6 in this example
This is ugly tough. It is better to make Student and Teacher either inherit a common superclass or implement a common interface
As far as I know you can't. You can take advantage of common interface.
For example:
interface Human{
val age: Int
}
class Student(override val age: Int): Human
class Teacher(override val age: Int, val salary: Double):Human
fun x(){
val list1: List<Student> = ...
val list2: List<Teacher> = ...
val school: List<Human> = ...
val result = school
.filter { it is Student }
.sumBy { it.age}
val result2 = school
.filter { it is Teacher }
.sumBy { it.age }
}