Elegant way of doing if else statement with a Flux - kotlin

I have the following kotlin method which currently uses a non-reactive mongodb repository (spring data) which returns a list.
fun register(userRegistration: UserRegistration) {
val existing = userRepository.findByTokenOrUserId(userRegistration.deviceToken, userRegistration.userId)
var alreadyRegistered = false
if (existing.isNotEmpty()) {
existing.forEach {
if (!isAlreadyRegistered(it, userRegistration)) {
userRepository.delete(it)
} else {
alreadyRegistered = true
}
}
}
if (!alreadyRegistered) {
val pnUser = PnUser(userRegistration.userId, userRegistration.deviceToken, userRegistration.region, userRegistration.locale, userRegistration.deviceType, userRegistration.osVersion, userRegistration.appVersion, userRegistration.timezone)
userRepository.save(pnUser)
}
}
How can I have the same behavior in an elegant way if userRepository.findByTokenOrUserId would return a Flux of PnUser instead of a List?
Thanks

As #Markus pointed out the most elegant way would be to split the Flux using Flux.groupBy and then doing the logic for each key.
Check out this answer for more information:

This doesn't do exactly the same but for me it will do:
fun registerUser(userRegistration: UserRegistration) {
userReactiveRepository.findByTokenOrUserId(userRegistration.userId, userRegistration.userId).map {
if (!isAlreadyRegistered(it, userRegistration)) {
userReactiveRepository.delete(it).map {
val pnUser = PnUser(userRegistration.userId, userRegistration.deviceToken, userRegistration.region, userRegistration.locale, userRegistration.deviceType, userRegistration.osVersion, userRegistration.appVersion, userRegistration.timezone)
userReactiveRepository.save(pnUser)
}
}
}
}

Related

Should I get rid of big switch case?

I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.

Kotlin - adding map function conditionally

I would like to feature toggle a map function on a list. I have a map that I would like to run only if the feature is on:
So for something like this:
items
.map { doTransformation(it) }
.map { runOnlyIfFeatureIsOn(it) }
Is there a way of adding the whole .map function conditionally in kotlin, so that it is only there if it is feature toggled?
let() is handy for doing arbitrary processing in a pipeline, e.g.:
items
.map{ doTransformation(it) }
.let{ if (someCondition) it.map{ runOnlyIfFeatureIsOn(it) } else it }
(For complex/costly conditions, this will be more efficient than putting the if inside the map call, as this'll only evaluate the condition once.)
Maybe just do if in map? There is no problem with that:
val list = listOf(1, 2, 3)
list
.map { it * 2 }
.map {
if (featureIsOn) {
runFeatureMapping(it)
} else {
it
}
}
Using sequences:
var sequence = items.asSequence()
.map { doTransformation(it) }
if (<feature_1_enabled>) {
sequence = sequence.map { runOnlyIfFeature1IsOn(it) }
}
if (<feature_2_enabled>) {
sequence = sequence.map { runOnlyIfFeature2IsOn(it) }
}
val result = sequence.toList()
Sequences are lazy-evaluated and should be used when mutliple operations (filter/map/etc) are applied

Transform Single<List<Maybe<Book>>> to Single<List<Book>>

could someone help, please?
I have these functions
fun getBooks(): Single<List<Book>> {
return getCollections()
.map {
it.map(::collectonToBook)
}
}
fun getCollections(): Single<List<Collection>> {
return db.fetchCollections()
.filter(::isBook)
}
fun collectonToBook(collection: Collection): Maybe<Book> {
return collection.toBook()
}
The problem is getBooks returns Single<List<Maybe<Book>>> when I need Single<List<Book>>. Can I do that inside the stream without calling blockingGet?
Try this:
getCollections() // Single<List<Collection>>
.flattenAsFlowable { it } // Flowable<Collection>
.concatMapMaybe { collectonToBook(it) } // Flowable<Book>
.toList() // Single<List<Book>>
In words, unwrap the inner List into its elements, transform the Collection into a Book, concatenate their respective Maybe sources, then finally collect the Books into a List again.

How can I override logRequest/logResponse to log custom message in Ktor client logging?

Currently, the ktor client logging implementation is as below, and it works as intended but not what I wanted to have.
public class Logging(
public val logger: Logger,
public var level: LogLevel,
public var filters: List<(HttpRequestBuilder) -> Boolean> = emptyList()
)
....
private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {
if (level.info) {
logger.log("REQUEST: ${Url(request.url)}")
logger.log("METHOD: ${request.method}")
}
val content = request.body as OutgoingContent
if (level.headers) {
logger.log("COMMON HEADERS")
logHeaders(request.headers.entries())
logger.log("CONTENT HEADERS")
logHeaders(content.headers.entries())
}
return if (level.body) {
logRequestBody(content)
} else null
}
Above creates a nightmare while looking at the logs because it's logging in each line. Since I'm a beginner in Kotlin and Ktor, I'd love to know the way to change the behaviour of this. Since in Kotlin, all classes are final unless opened specifically, I don't know how to approach on modifying the logRequest function behaviour. What I ideally wanted to achieve is something like below for an example.
....
private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {
...
if (level.body) {
val content = request.body as OutgoingContent
return logger.log(value("url", Url(request.url)),
value("method", request.method),
value("body", content))
}
Any help would be appreciative
No way to actually override a private method in a non-open class, but if you just want your logging to work differently, you're better off with a custom interceptor of the same stage in the pipeline:
val client = HttpClient(CIO) {
install("RequestLogging") {
sendPipeline.intercept(HttpSendPipeline.Monitoring) {
logger.info(
"Request: {} {} {} {}",
context.method,
Url(context.url),
context.headers.entries(),
context.body
)
}
}
}
runBlocking {
client.get<String>("https://google.com")
}
This will produce the logging you want. Of course, to properly log POST you will need to do some extra work.
Maybe this will be useful for someone:
HttpClient() {
install("RequestLogging") {
responsePipeline.intercept(HttpResponsePipeline.After) {
val request = context.request
val response = context.response
kermit.d(tag = "Network") {
"${request.method} ${request.url} ${response.status}"
}
GlobalScope.launch(Dispatchers.Unconfined) {
val responseBody =
response.content.tryReadText(response.contentType()?.charset() ?: Charsets.UTF_8)
?: "[response body omitted]"
kermit.d(tag = "Network") {
"${request.method} ${request.url} ${response.status}\nBODY START" +
"\n$responseBody" +
"\nBODY END"
}
}
}
}
}
You also need to add a method from the Ktor Logger.kt class to your calss with HttpClient:
internal suspend inline fun ByteReadChannel.tryReadText(charset: Charset): String? = try {
readRemaining().readText(charset = charset)
} catch (cause: Throwable) {
null
}

How to improve my code to show a data array in a table, with TornadoFX?

This is my way to display an array of data:
private val data = observableArrayList(
arrayOf("AAA", "111"),
arrayOf("BBB", "222"),
arrayOf("CCC", "333")
)
class HelloWorld : View() {
override val root = tableview<Array<String>>(data) {
column("name") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[0])
}
column("value") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[1])
}
}
}
It works but the code is quite complex. Is there any better way to do it?
(Maybe define a class to hold the data will make it much simpler, but I just want to test some uncommon cases)
Update:
A complete demo project for this: https://github.com/javafx-demos/tornadofx-tableview-array-data-demo
Here is a simpler way of defining your columns:
class HelloWorld : View() {
override val root = tableview(data) {
column<Array<String>, String>("name", { it.value[0].toProperty() })
column<Array<String>, String>("value", { it.value[1].toProperty() })
}
}
That said, using a specialized data structure would yield less headache :)
An alternative approach would be to configure just the cell item type and then a value factory:
column("name", String::class) {
value { it.value[0] }
}
column("value", String::class) {
value { it.value[1] }
}