Thursday, 14 April 2022

String concatenation using listOfNotNull & joinToString - Kotlin

data class User(val fName: String?, val mName: String?, val lName: String?)
val users = listOf(
        User("Ragavan", null, "V.S."),
        User("Rajeev", "Anbu", "Devan"),
        User("Ravinder", null, null),
        User("", "", ""),
        User(null, null, null)
    )
users.map {
        listOfNotNull(it.fName, it.mName, it.lName)
        .joinToString(" ").trim().takeIf { it.isNotEmpty() } ?: "NA"
}.forEach(::println)

Output:

[Ragavan V.S., Rajeev Anbu Devan, Ravinder, NA, NA]

In the above example here we are using the listOfNotNull method which implicitly handles the null check and ignore the same, then we are easy to concatenate the data using joinToString(...) method. 

Sunday, 16 January 2022

Android FAB Action to appear across the Application

Recently we got a requirement to an existing product and which have some legacy implementation too. Here the requirement to have a FAB (Chat Bot) action to be available on all the application screens (Activity).

Discussion where went like below:

1. Adding FAB to all the activity is a very tedious process, even creating a common layout and add using <include> in all layout also not that simple task. 😟 (Bad Practice too)

2. Will create a simple FAB Action class and planned to initialize it in all the activity class, since it is also a required some more efforts to update the initialization code in all the activity classes. (This implementation will reduces the effort but not fully, It helps to update on our Common BaseActivity and few other independent Activities by doing the initialization)  😌   

3. Finally Achieved!..  Implemented through Application class and ActivityLifecycleCallbacks, which made the implementation easier to work seamlessly across the application. 😎

FabButton

object FabButton {
    fun init(activity: Activity, savedInstanceState: Bundle?=null) {
        val root: ViewGroup = activity.window.decorView as ViewGroup
        val fab = FloatingActionButton(activity).apply {
            id = View.generateViewId()
            setImageResource(android.R.drawable.sym_action_chat)
            elevation = 2f
            imageTintList = ColorStateList.valueOf(Color.WHITE)
            backgroundTintList = ColorStateList.valueOf(Color.RED)
            size = FloatingActionButton.SIZE_NORMAL
            isFocusable = true
            layoutParams = FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT
            ).apply {
                gravity = Gravity.BOTTOM or Gravity.END
                setMargins(24, 24, 24, 24)
            }
        }
        fab.setOnClickListener {
            Toast.makeText(activity, "Yes, May I help you?", Toast.LENGTH_SHORT).show()
        }
        root.addView(fab)
    }
}

Activity

class FirstActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second2)
        FabButton.init(this)  // See here the fab initialization
    }
}

Later as per the #3, we found an other option instead of initializing the FabButton.init(this) to all the activities, implementing the below things resolved the issue without doing a larger code change.  

class TrailsApplication : Application(), Application.ActivityLifecycleCallbacks {
    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(this)
    }
    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        FabButton.init(activity, savedInstanceState)
    }
// Override other class ... 
}

P.S:  The application class has to override all the interface method of the ActivityLifecycleCallbacks, so as per the standard it would be good to handle by creating a separate handler class and register the same on application class.   For the simple and understanding purpose, I have override all the methods of lifecycle callbacks in application class itself. 

Happy Coding :-) 

  

Saturday, 25 December 2021

Kotlin Delegation

In object-oriented programming, delegation refers to evaluating a member (property or method) of one object (the receiver) in the context of another original object (the sender).

Delegation is a design pattern in which an object handles a request by delegating to a helper object, called the delegate. The delegate is responsible for handling the request on behalf of the original object and making the results available to the original object.

Kotlin supports “delegation” design pattern by introducing a new keyword “by”. Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object

Delegation should be used when:

  • When your subclass violates the Liskov substitution principle.  For example, when we are dealing with situations where inheritance was implemented only to reuse code of the superclass, but it is not really acting like it.
  • When the subclass uses only a portion of the methods of the superclass. In this case, it is only a matter of time before someone calls a superclass method that they were not supposed to call. Using delegation, we reuse only methods we choose (defined in the interface).
  • When we cannot or we should not inherit, because:
    • The class is final
    • It is not accessible and used from behind interface
    • It is just not designed for inheritance
Example: It will be very easy and self explanatory code snippet, here we have an interface for image loader and two other implementation with Glide and COIL Library.  There will be a delegation done through the ImageLoad class using "by" keyword.  

interface ImgSrcLoader {
    val loaderName: String
    fun onLoad()
    fun transition()
}

class GlideImpl(private val url: String, private val view: Any) : ImgSrcLoader {
    override val loaderName: String
        get() = "Glide"

    override fun onLoad() {
        println("Implementation with $loaderName : $url")
    }

    override fun transition() {
        println("Implementation with $loaderName transition Here")
    }
}

class CoilImpl(private val url: String, private val view: Any) : ImgSrcLoader {
    override val loaderName: String
        get() = "COIL"

    override fun onLoad() {
        println("Implementation with $loaderName : $url")
    }

    override fun transition() {
        println("Implementation with $loaderName transition Here")
    }
}

class ImageLoad(private val iLoader: ImgSrcLoader) : ImgSrcLoader by iLoader {
    /* This property change will not reflect/accessed on
     the super class implementations.*/
    override val loaderName: String
        get() = "NA"

    override fun transition() {
        if (iLoader is CoilImpl) {
            iLoader.transition()
            return
        }
        println("Transition with ${iLoader.loaderName} is restricted")
    }
}

fun main() {
    val url = "http://yourapp.photos.now/photo/0"

    ImageLoad(GlideImpl(url, "")).apply {
        transition()
        onLoad()
        println("ImageLoad: $loaderName")
    }

    ImageLoad(CoilImpl(url, "")).apply {
        transition()
        onLoad()
        println("ImageLoad: $loaderName")
    }
}

Output:

Transition with Glide is restricted
Implementation with Glide : http://yourapp.photos.now/photo/0
ImageLoad: NA
Implementation with COIL transition Here
Implementation with COIL : http://yourapp.photos.now/photo/0
ImageLoad: NA

Happy Coding :-)

Saturday, 13 November 2021

Vetoable - Delegates -- Kotlin

Vetoable, this allows us to modify the values when the argument input by the user(s) fulfills the specified condition, it can be used in place of observable properties if the user wants to intercept assignment. Vetoable is like Observable Properties with additional features to allows to modify and notify the values when the condition is met.

Simple example: 

The string which you never want to be reassigned with empty or to some specific text. 

The number values which is assigned should be match some condition.  

The generic observable kind of where the condition check will happen and then the value will be get assigned to the variable, incase if the condition fails, older value will be retained. 

var acceptOnlyEven: Int by Delegates.vetoable(0) { property, prevStr, nextStr ->
        nextStr.mod(2) == 0
}

var allowNonEmptyText: String by Delegates.vetoable("NA") { property, oldValue, newValue ->
        newValue.isNotEmpty()
}

var acceptStatus: String by Delegates.vetoable(STATUS_START) { property, oldValue, newValue ->
        listOf(STATUS_START, STATUS_INPROGRESS, STATUS_DONE).contains(newValue)
}

println("Str: $acceptStatus")
acceptStatus = "Status"
println("Str: $acceptStatus")
acceptStatus = ""
println("Str: $acceptStatus")
acceptStatus = "DonE"
println("Str: $acceptStatus")
acceptStatus = "Done"
println("Str: $acceptStatus")

println("Value: $acceptOnlyEven")
acceptOnlyEven = 10
println("Value: $acceptOnlyEven")
acceptOnlyEven = 5
println("Value: $acceptOnlyEven")

println("Str: $allowNonEmptyText")
allowNonEmptyText = "Status"
println("Str: $allowNonEmptyText")
allowNonEmptyText = ""
println("Str: $allowNonEmptyText")

Output:

Str: Start
Str: Start
Str: Start
Str: Start
Str: Done
Value: 0
Value: 10
Value: 10
Str: NA
Str: Status
Str: Status

Good Luck, Happy Coding :-)

Sunday, 17 October 2021

Kotlin getOrElse & getOrPut

 getOrElse: 

This provides safe access to elements of a collection. It takes an index and a function that provides the default value in cases when the index is out of bound.

getOrPut: 

This returns the value of the key.  If the key is not found in the map, calls the default value function, puts its result into the map under the given key and returns value of the same.

Code Snippet:

    data class Header(val heading: String, val subHeading: String)    

    val items = listOf("AAA", "BBB", "CCC", "DDD")
    val itemMap = mutableMapOf("x" to "AAA", "y" to "BBB")

    val header = listOf(
        Header("Heading1", "SubHeading1"), Header("Heading2", "SubHeading2")
    )

    println("Item at index 2 ${items.getOrElse(2) { "NA" }}")
    println("Item at index 4 ${items.getOrElse(4) { "NA" }}")
    println("Header at index 1 ${header.getOrElse(1) { "NA" }}")
    println("Header at index 2 ${header.getOrElse(2) { "NA" }}")
    
    println("Map Data: $itemMap")
    println("Map Key x ${itemMap.getOrElse("x") { "NA" }}")
    println("Map Key z ${itemMap.getOrElse("z") { "NA" }}")
    
    println("Map Key x ${itemMap.getOrPut("x") { "XXX" }}")
    println("Map Key z ${itemMap.getOrPut("z") { "ZZZ" }}")

    // Remove Map Key "x"
    itemMap.remove("x")

    println("Refreshed Map Data: $itemMap")
    println("Map Key x ${itemMap.getOrPut("x") { "AAA" }}")
    println("Refreshed Map Data: $itemMap")

Output:

Item at index 2 CCC
Item at index 4 NA
Header at index 1 Header(heading=Heading2, subHeading=SubHeading2)
Header at index 2 NA
Map Data: {x=AAA, y=BBB}
Map Key x AAA
Map Key z NA
Map Key x AAA
Map Key z ZZZ
Refreshed Map Data: {y=BBB, z=ZZZ}
Map Key x AAA
Refreshed Map Data: {y=BBB, z=ZZZ, x=AAA}

Happy Coding :-)

Sunday, 26 September 2021

groupBy vs associateBy in Kotlin

associateBy and groupBy function helps to build the maps from the elements of a collection indexed by the specified key. 

keySelector: The key is defined in the keySelector parameter. 

valueSelector: We can also specify an optional valueSelector to define what will be stored in the value of the map element, in case of not specifying the same it will consider the complete object as a value. 

The difference between associateBy and groupBy is about how they process the objects with the respective key

associateBy - It uses the last suitable element as the result value.

groupBy - it produces the list of all suitable elements and puts it in the result value(s).

P.S. The returned map preserves the entry iteration order of the original collection.

val boardingList = listOf(
        OnBoarding("1", "AAA", "Chennai", "Bang", "IT"),
        OnBoarding("2", "BBB", "Bang", "Hyd", "IT"),
        OnBoarding("3", "CCC", "Bang", "Chennai", "Finance"),
        OnBoarding("4", "DDD", "Hyd", "Pune", "Finance"),
        OnBoarding("5", "DDD", "Chennai", "Bang", "IT")
    )

// Param either be 'it' or field references, both does the same job.
println(boardingList.groupBy({ it.baseLocation }, { it.eName })) 
println(boardingList.groupBy(OnBoarding::baseLocation, OnBoarding::eName))

println(boardingList.associateBy({ it.baseLocation }, { it.eName }))
println(boardingList.associateBy(OnBoarding::baseLocation,OnBoarding::eName))

Output:

{Chennai=[AAA, DDD], Bang=[BBB, CCC], Hyd=[DDD]}
{Chennai=[AAA, DDD], Bang=[BBB, CCC], Hyd=[DDD]}
{Chennai=DDD, Bang=CCC, Hyd=DDD}
{Chennai=DDD, Bang=CCC, Hyd=DDD}

Good Luck, Happy Coding :-)

Sunday, 12 September 2021

DSL Code References

We should use DSL to simplify and improve the complexity of the app and make the code more readable, Before jumping into writing our own DSL we need to aware about the concept of lambda with receiver, invoke and operator.

Assume an example we have a class called Device, Please see below different ways how the object got initialized

class Device() {
    var id: Int = 0
    var name: String = ""
    var make: String = ""

    fun printDeviceInfo() {
        println("ID:  $id | Name: $name | Make: $make")
    }

    operator fun invoke(device: Device.() -> Unit): Device {
        device()
        return this
    }
}

Now, to instantiate the Class we can create an object like below ways,

// Traditional Way
    val env = Device()
    env.id = 102
    env.name = "5.4 GHz Wifi"
    env.make = "Amazon-Cable"
    env.printDeviceInfo()

// Invoke Way
    val en = Device()
    val obj = en {
        id = 102
        name = "5.4 GHz Wifi"
        make = "Amazon-Cable"
    }
    obj.printDeviceInfo()
    en.printDeviceInfo()

There is also other way where we are able to do without using invoke operator function, but here we can't use the traditional way of object creation. Since we pass it as parameter to the class primary constructor and followed by init method should call the same.

class Device(device: Device.() -> Unit) {
    var id: Int = 0
    var name: String = ""
    var make: String = ""

    fun printDeviceInfo() {
        println("ID:  $id | Name: $name | Make: $make")
    }

    init {
        device()
    }
}

val device = Device {
        id = 102
        name = "5.4 GHz Wifi"
        make = "Amazon-Cable"
    }
    device.printDeviceInfo()

Here we can also use it on the Data class also, but we should declare the constructor param as val/var, whereas it is not required on the normal model class like above.

data class User(val obj: User.() -> Unit) {
    var id: String = "NA"
    var age: Long = 0

    init {
        obj()
    }
}

fun main() {
    val userObj = User {}
    printUser(userObj)
    val usersList = listOf(userObj, User {
        id = "ABC567"
        age = 28
    }, User {
        id = "BCG678"
        age = 24
    }, User {
    })

    usersList[0].id = "BGH905"
    usersList.forEach(::printUser)
}

fun printUser(user: User) {
    println("Id: ${user.id}|| Age: ${user.age.takeIf { it > 0 } ?: "NA"}")
}

Good Luck :-) Happy Coding !!!