What are MOD APKs ? Are they Safe to install ? What are MOD APKs ? Are they Safe to install ?

[Solved] How to force client code to initialize all required builder fields in Kotlin with contracts?

Introduction:

Kotlin, renowned for its conciseness and flexibility, offers developers the opportunity to tackle complex challenges with creativity and innovation. In this blog post, we will delve into the realm of null-safe builders, exploring two distinct implementations and introducing a novel solution that combines the best of both worlds.

The Challenge:

The pursuit of a null-safe builder centers around ensuring that all required fields are initialized, promoting code robustness and predictability. Inspired by JetBrains’ open day 2019, where context contracts were discussed, developers sought to emulate these contracts to enforce specific conditions during the building process.

Generic Flags Based Builder:

One approach involves a generic flags-based builder, utilizing sealed classes and companion objects. This method ensures a fluent and null-safe construction process; however, limitations exist. The inability to use the builder with DSL and the requirement to specify all generics each time pose challenges.

sealed class Flag {
    object ON : Flag()
    object OFF : Flag()
}

class PersonBuilder<NAME : Flag, AGE : Flag> private constructor() {
    var _name: String? = null
    var _age: Int? = null

    companion object {
        operator fun invoke() = PersonBuilder<OFF, OFF>()
    }
}

val PersonBuilder<ON, *>.name get() = _name!!
val PersonBuilder<*, ON>.age get() = _age!!

fun <AGE : Flag> PersonBuilder<OFF, AGE>.name(name: String): PersonBuilder<ON, AGE> {
    _name = name
    @Suppress("UNCHECKED_CAST")
    return this as PersonBuilder<ON, AGE>
}

fun <NAME : Flag> PersonBuilder<NAME, OFF>.age(age: Int): PersonBuilder<NAME, ON> {
    _age = age
    @Suppress("UNCHECKED_CAST")
    return this as PersonBuilder<NAME, ON>
}

fun PersonBuilder<ON, ON>.build() = Person(name, age)

Contracts-Based Builder:

In contrast, the contracts-based builder leverages Kotlin’s contract system to enforce conditions on builder methods, providing a DSL-compatible solution. While this approach allows easy addition of new properties, it introduces some boilerplate code and faces challenges with lambdas using receivers.

PersonBuilder().name("Bob").age(21).build()
PersonBuilder().age(21).name("Bob").build()
PersonBuilder().name("Bob").name("Ann") // doesn't compile
PersonBuilder().age(21).age(21) // doesn't compile
PersonBuilder().name("Bob").build() // doesn't compile
PersonBuilder().age(21).build() // doesn't compile

val newbornBuilder = PersonBuilder().newborn() // builder with age but without name
newbornBuilder.build() // doesn't compile
newbornBuilder.age(21) // doesn't compile
val age = newbornBuilder.age
val name = newbornBuilder.name // doesn't compile
val bob = newbornBuilder.name("Bob").build()
val person2019 = newbornBuilder.nameByAge().build()
PersonBuilder().nameByAge().age(21).build() // doesn't compile

fun PersonBuilder<OFF, ON>.nameByAge() = name("Person #${Year.now().value - age}")
fun <NAME : Flag> PersonBuilder<NAME, OFF>.newborn() = age(0)

A Novel Proposal [Solution] :

To address the limitations posed by the previous implementations, a novel proposal suggests a combination of builders, each dedicated to a specific property. This approach provides a fluent interface, disallowing the build process until all required fields are initialized. The use of separate builder classes for name and age ensures a clean and extendable structure.

class PersonBuilder(private val name: String, private val age: Int) {
    fun build() = Person(name, age)
}

class PersonNameBuilder(private val name: String) {

    fun withAge(age: Int) = PersonBuilder(name, age)
}

class PersonAgeBuilder(private val age: Int) {

    fun withName(name: String) = PersonBuilder(name, age)
}

data class Person(val name: String, val age: Int)

Pros and Cons Comparison:

Generic Flags Based Builder:

  • Pros: Fluent interface, null-safe construction, properties cannot be reassigned.
  • Cons: Incompatible with DSL, limited flexibility for adding new properties, type parameters must be specified each time.

Contracts-Based Builder:

  • Pros: Compatible with DSL, flexibility for adding new properties, partly built objects can be safely used.
  • Cons: Lambdas with receivers face challenges, properties can be reassigned, boilerplate code in the where clause.

Novel Proposal:

  • Pros: Fluent interface, null-safe construction, easy to extend and refactor, DSL-friendly, ensures a clean and structured implementation.
  • Cons: Boilerplate code for separate builder classes.

Conclusion:

In the ever-evolving landscape of Kotlin development, developers are empowered to explore and innovate. The choice between these approaches depends on the specific needs of the project. The novel proposal introduces a pragmatic solution, emphasizing a clean and extendable structure. As Kotlin continues to evolve, developers have the tools to craft solutions that align with their development philosophies and project requirements.