Slide 1

Slide 1 text

Introduction to RxKotlin - Reactive Extensions for Kotlin Mario Arias -Kotlin Yorkshire Meetup Group

Slide 2

Slide 2 text

Topics About me Introduction Example: RxKotlin-Chat (RxKotlin + Spring Boot + RabbitMQ) RxKotlin in detail

Slide 3

Slide 3 text

Software Engineer at Cake Solutions 10+ years of experience with JVM technologies Spring certified trainer 5+ years with Scala 3+ years with Kotlin funKTionale KotlinPrimavera RxKotlin original developer and team leader* NOT an expert on reactive programming @dh44t * I hit “Merge” and “Release” buttons

Slide 4

Slide 4 text

Introduction “RxJava bindings for Kotlin” - GitHub RxKotlin helps you to use RxJava in an idiomatic way

Slide 5

Slide 5 text

RxKotlin-Chat git clone [email protected]:MarioAriasC/rxkotlin-chat.git ./mvnw clean package java -jar target/rxchat-1.0.jar --chat.name=JohnDoe —- spring.rabbitmq.host=

Slide 6

Slide 6 text

RxKotlin-Chat scanner: Obvserbable general: Observable private: Obvserbable chat: Observer chat.general.JohnDoe chat.private.JohnDoe

Slide 7

Slide 7 text

scanner fun scanner(): ConnectableObservable {
 return Observable.create { subscriber 
 try {
 if (subscriber.isUnsubscribed) {
 return@create
 }
 
 val scanner = Scanner(System.`in`)
 while (true) {
 val line = scanner.nextLine()
 if (line.equals(":q!")) {
 break
 }
 subscriber.onNext(line)
 }
 } catch (e: IOException) {
 subscriber.onError(e)
 }
 
 if (!subscriber.isUnsubscribed) {
 subscriber.onCompleted()
 }
 }.publish()
 }

Slide 8

Slide 8 text

general fun generalObservable(name: String, input: Observable): Observable {
 return input.filter { s  !s.startsWith(PrivateMessage.AT) }
 .map { s  "${PrivateMessage.AT}$name says:$s" }
 .map { s  GeneralMessage(s) }
 }

Slide 9

Slide 9 text

private fun privateObservable(name: String, input: Observable): Observable {
 return input.filter { s  s.startsWith("@") }
 .map { s  s.split(PrivateMessage.SPACE_PATTERN, 2) }
 .filter { parts 
 val condition = parts.size  2
 if (!condition) {
 println("Wrong format")
 }
 condition
 }
 .filter { parts 
 val condition = !parts[0].equals("@")
 if (!condition) {
 println("Invalid user")
 }
 condition
 }
 .map { parts 
 PrivateMessage("chat.private.${parts[0].replace("@", "")}",
 name.says(parts[1]))
 }
 
 }

Slide 10

Slide 10 text

chat class Chat(val context: ConfigurableApplicationContext,
 val name: String,
 general: Observable,
 priv: Observable) : Observer {
 val subs = Observable.merge(general, priv).subscribeOn(Schedulers.io()).subscribe(this)
 val latch = CountDownLatch(1);
 val template = context[RabbitTemplateclass.java]
 
 init {
 template.general(GeneralMessage("$name CONNECTED"))
 }
 
 override fun onNext(message: ChatMessage) {
 when(message){
 is GeneralMessage  template.general(message)
 is PrivateMessage  template.privately(message)
 }
 }
 
 override fun onError(e: Throwable) {
 println(e.message)
 }
 
 override fun onCompleted() {
 template.general(GeneralMessage("$name DISCONNECTED"))
 println("Bye")
 latch.countDown()
 subs.unsubscribe()
 context.close()
 }
 
 }

Slide 11

Slide 11 text

main fun RabbitTemplate.general(message: GeneralMessage): Unit {
 convertAndSend(chatGeneral, "", message.body)
 }
 
 fun RabbitTemplate.privately(message: PrivateMessage): Unit {
 convertAndSend(message.routingKey, message.body)
 }
 
 fun main(args: Array) {
 
 
 //val log = LoggerFactory.getLogger(RxChatApplicationclass.java)
 val context = SpringApplication.run(RxChatApplicationclass.java, *args)
 val props = context[ChatPropertiesclass.java]
 val name = props.name
 println("""
 $name, Welcome to the RxKotlin Chat
 -Every message that you send will published in the general chat
 -To send private messages, use '@' before your friend's alias (e.g.: @JohnDoe Hello)
 -To exit use the command ':q!'
 """)
 
 val scannerObservable = scanner()
 val general = generalObservable(name, scannerObservable)
 val priv = privateObservable(name, scannerObservable)
 
 val chat = Chat(context, name, general, priv)
 scannerObservable.connect()
 chat.latch.await()
 }

Slide 12

Slide 12 text

… but at this point we didn’t used RxKotlin yet!!

Slide 13

Slide 13 text

scanner with RxKotlin fun scanner(): ConnectableObservable = observable { subscriber 
 try {
 if (subscriber.isUnsubscribed) {
 return@observable
 }
 
 val scanner = Scanner(System.`in`)
 while (true) {
 val line = scanner.nextLine()
 if (line.equals(":q!")) {
 break
 }
 subscriber.onNext(line)
 }
 } catch (e: IOException) {
 subscriber.onError(e)
 }
 
 if (!subscriber.isUnsubscribed) {
 subscriber.onCompleted()
 }
 }.publish()


Slide 14

Slide 14 text

chat with RxKotlin class Chat(val context: ConfigurableApplicationContext,
 val name: String,
 vararg obs: Observable) {
 
 val latch = CountDownLatch(1);
 val template = context[RabbitTemplateclass.java]
 
 init {
 template.general(GeneralMessage("$name CONNECTED"))
 obs.asIterable().merge().subscribeOn(Schedulers.io()).subscribeWith {
 onNext { message 
 when (message) {
 is GeneralMessage  template.general(message)
 is PrivateMessage  template.privately(message)
 }
 }
 
 onError { e 
 println(e.message)
 }
 
 onCompleted {
 template.general(GeneralMessage("$name DISCONNECTED"))
 println("Bye")
 latch.countDown()
 context.close()
 }
 }
 }
 }

Slide 15

Slide 15 text

You don’t need RxKotlin to develop RxJava applications with Kotlin… but code is prettier if you use it

Slide 16

Slide 16 text

RxKotlin in Detail fun observable(body : (s : Subscriber)  Unit) : Observable = Observable.create(body) fun Iterable.toObservable() : Observable = Observable.from(this) fun Iterable = Observable.merge(this.toObservable()) inline fun Observable.subscribeWith( body : FunctionSubscriberModifier.()  Unit ) : Subscription {
 val modifier = FunctionSubscriberModifier(subscriber())
 modifier.body()
 return subscribe(modifier.subscriber)
 } 


Slide 17

Slide 17 text

Appendix: Spring Configuration (I) @Configuration
 @EnableRabbit
 open class AmqpConfiguration : RabbitListenerConfigurer {
 
 
 override fun configureRabbitListeners(registrar: RabbitListenerEndpointRegistrar) {
 
 fun listener(prefix: String = ""): (Message)  Unit = { message: Message 
 println(prefix + String(message.body))
 }
 
 registrar.registerEndpoint(SimpleRabbitListenerEndpoint().apply {
 id = "generalEndpoint"
 setQueues(generalQueue())
 setMessageListener(listener())
 })
 registrar.registerEndpoint(SimpleRabbitListenerEndpoint().apply {
 id = "privateEndpoint"
 setQueues(privateQueue())
 setMessageListener(listener("[PRIVATE ]"))
 })
 }


Slide 18

Slide 18 text

Appendix: Spring Configuration (II) @Bean
 open fun connectionFactory(props: RabbitProperties) = CachingConnectionFactory().apply {
 setAddresses(props.addresses)
 setUsername(props.username)
 setPassword(props.password)
 }
 
 @Bean
 open fun template(connectionFactory: ConnectionFactory) = RabbitTemplate(connectionFactory)
 
 @Bean
 open fun admin(connectionFactory: ConnectionFactory) = RabbitAdmin(connectionFactory).apply {
 afterPropertiesSet()
 }
 
 @Bean
 open fun privateQueue() = Queue("chat.private.${chatProperties().name}")
 
 @Bean
 open fun generalQueue() = Queue("chat.general.${chatProperties().name}")
 
 @Bean
 open fun chatProperties() = ChatProperties()
 
 @Bean
 open fun generalFanout() = FanoutExchange("chat.general")
 
 @Bean
 open fun binding(generalFanout: FanoutExchange,
 generalQueue: Queue): Binding = BindingBuilder.bind(generalQueue).to(generalFanout)

Slide 19

Slide 19 text

Thanks!! P.D. We’re hiring, ;) [email protected]