Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to RxKotlin

Introduction to RxKotlin

Kotlin Yorkshire Meetup Group talk

Mario Arias

June 15, 2016
Tweet

More Decks by Mario Arias

Other Decks in Programming

Transcript

  1. Introduction to RxKotlin - Reactive Extensions for Kotlin Mario Arias

    -Kotlin Yorkshire Meetup Group
  2. Topics About me Introduction Example: RxKotlin-Chat (RxKotlin + Spring Boot

    + RabbitMQ) RxKotlin in detail
  3. 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
  4. Introduction “RxJava bindings for Kotlin” - GitHub RxKotlin helps you

    to use RxJava in an idiomatic way
  5. RxKotlin-Chat git clone git@github.com:MarioAriasC/rxkotlin-chat.git ./mvnw clean package java -jar target/rxchat-1.0.jar

    --chat.name=JohnDoe —- spring.rabbitmq.host=<your.rabbitmq.server.ip>
  6. RxKotlin-Chat scanner: Obvserbable<String> general: Observable<Message> private: Obvserbable<Message> chat: Observer<Message> chat.general.JohnDoe

    chat.private.JohnDoe
  7. scanner fun scanner(): ConnectableObservable<String> {
 return Observable.create<String> { 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()
 }
  8. general fun generalObservable(name: String, input: Observable<String>): Observable<ChatMessage> {
 return input.filter

    { s  !s.startsWith(PrivateMessage.AT) }
 .map { s  "${PrivateMessage.AT}$name says:$s" }
 .map { s  GeneralMessage(s) }
 }
  9. private fun privateObservable(name: String, input: Observable<String>): Observable<ChatMessage> {
 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]))
 }
 
 }
  10. chat class Chat(val context: ConfigurableApplicationContext,
 val name: String,
 general: Observable<ChatMessage>,


    priv: Observable<ChatMessage>) : Observer<ChatMessage> {
 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()
 }
 
 }
  11. 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<String>) {
 
 
 //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()
 }
  12. … but at this point we didn’t used RxKotlin yet!!

  13. scanner with RxKotlin fun scanner(): ConnectableObservable<String> = observable<String> { 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()

  14. chat with RxKotlin class Chat(val context: ConfigurableApplicationContext,
 val name: String,


    vararg obs: Observable<ChatMessage>) {
 
 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()
 }
 }
 }
 }
  15. You don’t need RxKotlin to develop RxJava applications with Kotlin…

    but code is prettier if you use it
  16. RxKotlin in Detail fun <T> observable(body : (s : Subscriber<in

    T>)  Unit) : Observable<T> = Observable.create(body) fun <T> Iterable<T>.toObservable() : Observable<T> = Observable.from(this) fun <T> Iterable<Observable<out T.merge() : Observable<T> = Observable.merge(this.toObservable()) inline fun <T> Observable<T>.subscribeWith( body : FunctionSubscriberModifier<T>.()  Unit ) : Subscription {
 val modifier = FunctionSubscriberModifier(subscriber<T>())
 modifier.body()
 return subscribe(modifier.subscriber)
 } 

  17. 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 ]"))
 })
 }

  18. 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)
  19. Thanks!! P.D. We’re hiring, ;) marioa@cakesolutions.net