I currently have this class with dsl like building ability
class GRLMessage {
var headerMap : MutableMap<String, String> = mutableMapOf()
lateinit var methodType : GRLMethod
lateinit var multipartObject : IGRLMultipart
fun message(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun method(closure: GRLMessage.() -> GRLMethod) : GRLMessage {
methodType = closure()
return this
}
fun headers(closure: GRLMessage.() -> Unit) : GRLMessage {
closure()
return this
}
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
fun multipart(closure: GRLMessage.() -> IGRLMultipart) : GRLMessage {
multipartObject = closure()
return this
}
}
And I test it like this
class GRLMessageTest {
data class DummyMultipart(val field: String) : IGRLMultipart {
override fun getContent() {
this
}
}
#Test fun grlMessageBuilderTest() {
val grlMessage = GRLMessage().message {
method { GRLMethod.POST }
headers {
header { Pair("contentType", "object") }
header { Pair("objectType", "DummyMultipart") }
}
multipart { DummyMultipart("dummy") }
}
val multipart = DummyMultipart("dummy")
val headers = mapOf(
Pair("contentType", "object"),
Pair("objectType", "DummyMultipart")
)
val method = GRLMethod.POST
assertEquals(multipart, grlMessage.multipartObject)
assertEquals(method, grlMessage.methodType)
assertEquals(headers, grlMessage.headerMap)
}
}
But despite providing
header { Pair("contentType", "object") }
I still have to evaluate closure inside header method and directly put key and value into my MutableMap
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
var pair = closure()
headerMap.put(pair.first, pair.second)
return this
}
Is there a better way adding entries to Map?
Does your headerMap need to be a var? If not, you can change it to a val and use headerMap += closure().
Adding an extension function makes your fluent methods more obviously fluent:
fun <T: Any> T.fluently(func: ()->Unit): T {
return this.apply { func() }
}
With that your fluent function is always clear about its return:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return fluently { headerMap += closure() }
}
Which is really the same as:
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
return this.apply { headerMap += closure() }
}
But the extension function adds a touch of readability.
Above I use the answer given in by #Ruckus for solving your specific question of adding a Pair to the headerMap. But you have other options that you might want to know about for other use cases of your DSL...
You can use let, apply or with which would allow any type of decomposition of the results of closure() call (maybe it is more complicated than Pair in the future). All of these are basically the same, minus their resulting value:
with(closure()) { headerMap.put(this.first, this.second) }
closure().apply { headerMap.put(this.first, this.second) }
closure().let { headerMap.put(it.first, it.second) }
Using let or apply is nice if you want to handle a case where closure() allows nullable return, in which case you might want to take action only if not null:
closure()?.apply { headerMap.put(this.first, this.second) }
closure()?.let { headerMap.put(it.first, it.second) }
Other notes about your code:
use val instead of var unless you have no other choice
lateinit (or the similar Delegates.notNull()) seem dangerous to use in an uncontrolled lifecycle where there is no guarantee it will be completed, because the error message will be confusing and happen at some unexpected time in the future. There are likely other ways to solve this with a DSL that chains calls to create more of a multi-step grammar
You can shorten code by only having types on one side of the assignment, for example:
val myMap = mutableMapOf<String, String>()
instead of
var myMap : MutableMap<String, String> = mutableMapOf()
Well for now as a solution I created extension for MutableMap
fun MutableMap<String, String>.put(pair : Pair<String, String>) {
this.put(pair.first, pair.second)
}
Which allowed me to write like this
fun header(closure: GRLMessage.() -> Pair<String, String>) : GRLMessage {
headerMap.put(closure())
return this
}
Related
i'm facing hard times updating list of Orders in real time from firestore using stateflow !!
class RepositoryImp : Repository {
private fun Query.snapshotFlow(): Flow<QuerySnapshot> = callbackFlow {
val snapshott = addSnapshotListener { value, error ->
if (error != null) {
close()
return#addSnapshotListener
}
if (value != null)
trySend(value)
}
awaitClose {
snapshott.remove()
}
}
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot)) // **HERE** !!!!!!
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
}
i'm receiving the error from // emit(State.success(snapshot)) that says :
Type mismatch: inferred type is Flow<(Mutable)List<OrderModel!>> but List< OrderModel> was expected
sealed class State <T> {
class Loading <T> : State<T>()
data class Success <T> (val data: T) : State <T>()
data class Failed <T> (val message: String) : State <T>()
companion object {
fun <T> loading() = Loading <T>()
fun <T> success(data: T) = Success(data)
fun <T> failed(message: String) = Failed<T>(message)
}
}
My fun to LoadOrders :
private suspend fun loadOrders() {
viewModel.getAllOrders().collect { state ->
when (state) {
is State.Loading -> {
showToast("Loading")
}
is State.Success -> {
adapter.submitList(state.data)
}
is State.Failed -> showToast("Failed! ${state.message}")
}
}
}
Your snapshot variable is a Flow of lists, not a single List. If you want to just fetch the current list, you shouldn't use a flow for that. Instead use get().await().
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshot = ORDER_COLLECTION_REF.get().await()
.let { it.toObjects(OrderModel::class.java) }
emit(State.success(snapshot))
}.catch {
emit(State.failed(it.message.toString()))
}.flowOn(Dispatchers.IO)
The flowOn call is actually unnecessary because we aren't doing anything blocking. await() is a suspend function.
Based on comments discussion below, supposing we want to show a loading state only before the first item, then show a series of success states, and we want to show an error and stop emitting once there's an error, we could do:
override fun getAllOrders() = flow<State<List<OrderModel>>> {
emit(State.loading())
val snapshots = ORDER_COLLECTION_REF.snapshotFlow()
.mapNotNull { State.success(it.toObjects(OrderModel::class.java)) }
emitAll(snapshots)
}.catch {
emit(State.failed(it.message.toString()))
}
I want to set the adapter of ViewPager2 to FragmentStatePagerAdapter but I get this error:
Type mismatch. Required: (RecyclerView.Adapter<RecyclerView.ViewHolder!>?..RecyclerView.Adapter<*>?) Found: ViewPager2Adapter
My ViewPagerAdapter class is
class ViewPager2Adapter(fm:FragmentManager) :FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return when(position) {
0 -> {
MyScansListFragment()
}
1 -> {
PurchasedItemsFragment()
}
else -> {
Fragment()
}
}
}
override fun getCount(): Int {
return 2
}
override fun getItemPosition(`object`: Any): Int {
return POSITION_NONE
}}
And in the oncreateView() :
val viewPager2Adapter = ViewPager2Adapter(activity?.supportFragmentManager!!)
binding!!.viewPager.adapter = viewPager2Adapter
okay, let's change the code a little bit.
First of all, FragmentStatePagerAdapter has been deprecated.
FragmentStatePagerAdapter & FragmentPagerAdapter have been recently deprecated, and your code must look something like this. FragmentStatePagerAdapter and if you get your cursor over it and see details, there will be a statement "Deprecated Switch to androidx.viewpager2.widget.ViewPager2 and use androidx.viewpager2.adapter.FragmentStateAdapter instead."
try the following code.
class ViewPager2Adapter(private val listFragment: ArrayList<Fragment>,
fm: FragmentManager,
lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) {
override fun getItemCount(): Int {
return listFragment.size
}
override fun createFragment(position: Int): Fragment {
return listFragment[position]
}
}
so, this is now kind of your universal viewpager adapter.
The next thing is we require fragments to be passed in here.
//I don't think you need Fragment() but since it's there in your list.
val fragmentList = listOf(MyScansListFragment(), PurchasedItemsFragment(),Fragment())
val viewPager2Adapter = ViewPager2Adapter(fragmentList, activity?.supportFragmentManager!!, lifecycle)
binding!!.viewPager.adapter = viewPager2Adapter
I would like to simplify this code by reducing it to just a lambda. The interface has only one function. I'm not sure how to replace the override part of the code with just a lambda expression:
interface ITextWatcher {
fun onTextChanged(text: String) {
}
}
val textChangeHandler = object: ITextWatcher {
override fun onTextChanged(text: String)
var t = text
}
}
I'm looking for something like this:
val textChangeHandler = object: ITextWatcher {text ->
}
But that won't compile.
The syntax is val textChangeHandler = ITextWatcher {text -> ... }, but it doesn't work for interfaces declared in Kotlin, only for Java ones (at least for now).
Use (String) -> Unit directly instead. Or declare a function to convert one to another:
inline fun ITextWatcher(crossinline f: (String) -> Unit) = object : ITextWatcher {
override fun onTextChanged(text: String) {
f(text)
}
}
val textChangeHandler = ITextWatcher {text -> ... }
if you want.
val k = " asdfasdf "
fun test() {
if(k is String) {
// Do something
}
}
So, how do I pass that String through the function calls
eg:
fun test(xxxx) {
if(k is xxxx) {
// do something
}
}
Like this:
inline fun <reified T> testType(k: Any) {
if(k is T) {
println("is a ${T::class.simpleName}")
} else {
println("is not a ${T::class.simpleName}")
}
}
Call it like this:
test<String>("Hello") // is a String
test<String>(1) // is no String
Here some further reading.
There are two possibilities, depending on your needs.
1. Use inline and a reified type parameter
You can use the reified keyword on the type parameter in combination with an inline function:
inline fun <reified T> test(k: Any) {
if (k is T) {
println("k is a T!")
}
}
See the documentation on reified.
2. Use KClass<T>
If you do not want to or cannot make your function inline you can use a KClass parameter:
fun <T : Any> test(k: Any, type: KClass<T>) {
if (type.isInstance(k)) {
println("k is a T!")
}
}
You can either use a predicate, e.g.:
fun testIt(predicate: (Any?) -> Boolean) {
if (predicate(k)) {
println("matches!")
} else println("nope")
}
and call it as follows:
testIt { it is String }
testIt { it is Int }
Or you can use a reified type:
inline fun <reified T> testIt() {
when (k) {
is T -> println("matches!")
else -> println("nope")
}
}
and call it like:
testIt<String>()
testIt<Int>()
For simplicity I kept your current variable inside the testIt-method... you may want to redesign that ;-)
I basically assumed a member variable as follows: var k : Any? = null
inline fun <reified T> isType(obj: Any): Boolean {
return obj is T
}
fun main(args: Array<String>) {
val test = "This is a String"
if (isType<String>(test)) {
println("Success")
} else {
println("Failure")
}
}
I am trying to create a DSL for creating JSONObjects. Here is a builder class and a sample usage:
import org.json.JSONObject
fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
val builder = JsonObjectBuilder()
builder.build()
return builder.json
}
class JsonObjectBuilder {
val json = JSONObject()
infix fun <T> String.To(value: T) {
json.put(this, value)
}
}
fun main(args: Array<String>) {
val jsonObject =
json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To json {
"city" To "istanbul"
"email" To "xxx#yyy.com"
}
}
println(jsonObject)
}
The output of the above code is :
{"contact":{"city":"istanbul","email":"xxx#yyy.com"},"name":"ilkin","age":37,"male":true}
It works as expected. But it creates an additional JsonObjectBuilder instance every time it creates a json object. Is it possible to write a DSL for creating json objects without extra garbage?
You can use a Deque as a stack to track your current JSONObject context with a single JsonObjectBuilder:
fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
return JsonObjectBuilder().json(build)
}
class JsonObjectBuilder {
private val deque: Deque<JSONObject> = ArrayDeque()
fun json(build: JsonObjectBuilder.() -> Unit): JSONObject {
deque.push(JSONObject())
this.build()
return deque.pop()
}
infix fun <T> String.To(value: T) {
deque.peek().put(this, value)
}
}
fun main(args: Array<String>) {
val jsonObject =
json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To json {
"city" To "istanbul"
"email" To "xxx#yyy.com"
}
}
println(jsonObject)
}
Example output:
{"contact":{"city":"istanbul","email":"xxx#yyy.com"},"name":"ilkin","age":37,"male":true}
Calling json and build across multiple threads on a single JsonObjectBuilder would be problematic but that shouldn't be a problem for your use case.
Do you need a DSL? You lose the ability to enforce String keys, but vanilla Kotlin isn't that bad :)
JSONObject(mapOf(
"name" to "ilkin",
"age" to 37,
"male" to true,
"contact" to mapOf(
"city" to "istanbul",
"email" to "xxx#yyy.com"
)
))
Updated on Jan 11 2023:
Replaced infix fun String.to(json: Json -> Unit) with infix fun String.to(json: Json.() -> Unit) which uses Json block as receiver and invokes after a Json object is created. So no longe need to add Json key inside Json object.
I am not sure if I get the question correctly. You don't want a builder?
import org.json.JSONArray
import org.json.JSONObject
class Json() : JSONObject() {
constructor(init: Json.() -> Unit) : this() {
this.init()
}
infix fun String.to(json: Json.() -> Unit) {
put(this, Json().apply(json))
}
infix fun <T> String.to(value: T) {
put(this, value)
}
infix fun <T> String.to(values: List<T>) {
put(this, JSONArray().apply {
values.forEach { put(it) }
})
}
}
fun main(args: Array<String>) {
val json = Json {
"name" to "Roy"
"body" to {
"height" to 173
"weight" to 80
}
"cars" to listOf(
"Tesla"
"Porsche"
"BMW"
"Ferrari"
)
}
println(json)
}
You will get
{
"name": "Roy",
"body": {
"weight": 80,
"height": 173
},
"cars": [
"Tesla",
"Porsche",
"BMW",
"Ferrari"
]
}
Yes, it is possible if you don't need any intermediate representation of the nodes, and if the context is always the same (the recursive calls are no different from each other). This can be done by writing the output immediately.
However, this severely increases code complexity, because you have to process your DSL calls right away without storing them anywhere (again, to avoid redundant objects).
Example (see its demo here):
class JsonContext internal constructor() {
internal val output = StringBuilder()
private var indentation = 4
private fun StringBuilder.indent() = apply {
for (i in 1..indentation)
append(' ')
}
private var needsSeparator = false
private fun StringBuilder.separator() = apply {
if (needsSeparator) append(",\n")
}
infix fun String.to(value: Any) {
output.separator().indent().append("\"$this\": \"$value\"")
needsSeparator = true
}
infix fun String.toJson(block: JsonContext.() -> Unit) {
output.separator().indent().append("\"$this\": {\n")
indentation += 4
needsSeparator = false
block(this#JsonContext)
needsSeparator = true
indentation -= 4
output.append("\n").indent().append("}")
}
}
fun json(block: JsonContext.() -> Unit) = JsonContext().run {
block()
"{\n" + output.toString() + "\n}"
}
val j = json {
"a" to 1
"b" to "abc"
"c" toJson {
"d" to 123
"e" toJson {
"f" to "g"
}
}
}
If you don't need indentation but only valid JSON, this can be easily simplified, though.
You can make the json { } and .toJson { } functions inline to get rid even of the lambda classes and thus you achieve almost zero object overhead (one JsonContext and the StringBuilder with its buffers are still allocated), but that would require you to change the visibility modifiers of the members these functions use: public inline functions can only access public or #PublishedApi internal members.
Found another solution. You can just inherit JSONObject class without need to create other objects.
class Json() : JSONObject() {
constructor(init: Json.() -> Unit) : this() {
this.init()
}
infix fun <T> String.To(value: T) {
put(this, value)
}
}
fun main(args: Array<String>) {
val jsonObject =
Json {
"name" To "ilkin"
"age" To 37
"male" To true
"contact" To Json {
"city" To "istanbul"
"email" To "xxx#yyy.com"
}
}
println(jsonObject)
}
The output of code will be the same.
{"contact":{"city":"istanbul","email":"xxx#yyy.com"},"name":"ilkin","age":37,"male":true}
UPD: If you use gson library you can look at this awesome library. It doesn't create any garbage, source code is easy to read and understand.
You could use a library such as https://github.com/holgerbrandl/jsonbuilder to build json with
val myJson = json {
"size" to 0
"array" to arrayOf(1,2,3)
"aggs" to {
"num_destinations" to {
"cardinality" to {
"field" to "DestCountry"
}
}
}
}
Disclaimer: I'm the author of the library.