Skip to content

Latest commit

 

History

History
154 lines (124 loc) · 4.68 KB

part4.md

File metadata and controls

154 lines (124 loc) · 4.68 KB

Part 4 - Even more Kotlin

In this part you will improve Kotlin-JavaScript inter-op by adding Kotlin headers (or bindings) for Firebase libraries. After that we will actually try to utilise some language features to make code a bit more idiomatic.

Steps:

  • Create the file Admin.kt in src/main/kotlin/bindings.

  • Write a basic JS binding to remove the require call and abstract away the module import:

@JsModule("firebase-admin")
@JsNonModule
external object Admin {
    fun initializeApp(config: dynamic)
    fun database(): dynamic
}
  • In a similar way add another binding in Functions.kt:
@JsModule("firebase-functions")
@JsNonModule
external object Functions {
    fun config(): dynamic
    val https: dynamic
}
  • Also rewrite some dynamic code in the main function with the newly written bindings:
fun main(args: Array<String>) {
    Admin.initializeApp(Functions.config().firebase)

    exports.testPush = Functions.https.onRequest { req, res ->
        Admin.database().ref("/conversions")
                .push(InputConversions(req.query.eur.toString().toFloat()))
                .then { res.status(200).send("done") }
    }
}
  • Build, deploy and make sure it still works.

  • Copy the rest of my custom bindings from the bindings folder of this repo to src/main/kotlin/bindings.

  • Optional: Examine copied bindings to see how they represents their respective JavaScript APIs.

  • Rewrite the database table types to Kotlin:

data class Rates(
    val usd: Float,
    val czk: Float,
    val pln: Float,
    val rub: Float
)

data class Conversions(
    val eur: Float,
    val usd: Float? = null,
    val czk: Float? = null,
    val pln: Float? = null,
    val rub: Float? = null,
    val timestamp: Long? = null
)
  • Rewrite the function convert to Kotlin:
exports.convert = Functions.database.ref("/conversions/{conversionID}").onWrite<Conversions> { event ->
    val input = event.data.`val`()
    if (input.timestamp != null) return@onWrite
    Admin.database().ref("/rates").once<Rates>("value")
        .then<Conversions> { ratesSnap ->
            ratesSnap.`val`().let {
                Conversions(
                    eur = input.eur,
                    usd = input.eur * it.usd,
                    czk = input.eur * it.czk,
                    pln = input.eur * it.pln,
                    rub = input.eur * it.rub,
                    timestamp = Date.now()
                )
            }
        }
        .then { result ->
            Admin.database().ref("/conversions")
                .child(event.params.conversionID)
                .set(result)
        }
}
  • Build and deploy to verify that it still works.

And a bit more of Kotlin

  • Move data classes to a separate package e.g. domain

  • Add functions inNew() and convert(Rates, Long) to Conversions.kt:

fun Conversions.isNew() = timestamp == null

fun Conversions.convert(rates: Rates, timestamp: Long) = Conversions(
    eur = eur,
    usd = eur * rates.usd,
    czk = eur * rates.czk,
    pln = eur * rates.pln,
    rub = eur * rates.rub,
    timestamp = timestamp
)
  • Make use of the new Conversions code:
fun main(args: Array<String>) {
    Admin.initializeApp(Functions.config().firebase)
    val database = Admin.database()

    exports.testPush = Functions.https.onRequest { req, res ->
        database.ref("/conversions")
            .push(Conversions(req.query.eur.toString().toFloat()))
            .then { res.status(200).send("done") }
    }

    exports.convert = Functions.database.ref("/conversions/{conversionID}").onWrite<Conversions> { event ->
        event.data.`val`().takeIf { it.isNew() }?.let { input ->
            database.ref("/rates").once<Rates>("value")
                .then<Conversions> { it.`val`().let { rates -> input.convert(rates, Date.now()) } }
                .then { conversions ->
                    database.ref("/conversions")
                        .child(event.params.conversionID)
                        .set(conversions)
                }
        }
    }
}

Conclusions:

Congratulations! You just completed this workshop and successfully created Firebase Functions project in Kotlin. I encourage you play and expand your project as much as you want (I do not encourage using this in production enviroment, do it at your own risk).

Check out optional extra part for some more info on generating Kotlin headers.

Links: