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

Taming WebSocket with Scarlet - Droidcon SF

Zhixuan Lai
November 19, 2018

Taming WebSocket with Scarlet - Droidcon SF

Despite being a well-established standard for bidirectional persistent connection between client and server, WebSocket is complicated to set up on Android. When Tinder migrated to WebSocket for its realtime chat experience, they developed Scarlet: an extensible Kotlin library inspired by Retrofit that eliminates the boilerplate code required to handle data serialization and specify when to connect and retry. After this talk, you’ll be able to configure Scarlet using many of its plugins and integrate any WebSocket API in 10 minutes.

Zhixuan Lai

November 19, 2018
Tweet

More Decks by Zhixuan Lai

Other Decks in Programming

Transcript

  1. WebSocket // Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080');

    // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server', data); });
  2. WebSocket // Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080');

    // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server', data); });
  3. WebSocket // Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080');

    // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server', data); });
  4. WebSocket // Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080');

    // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server', data); });
  5. WebSocket // Create WebSocket connection. const socket = new WebSocket('ws://localhost:8080');

    // Connection opened socket.addEventListener('open', function (event) { socket.send('Hello Server!'); }); // Listen for messages socket.addEventListener('message', function (event) { console.log('Message from server’, event.data); });
  6. Why WebSocket? • What problems does it solve? • How

    can it help you build a better app?
  7. WebSocket with OkHttp class OkHttp { fun newWebSocket( request: Request,

    listener: WebSocketListener ): WebSocket } interface WebSocketListener { fun onOpen(response: Response) fun onMessage(text: Message) fun onMessage(bytes: ByteMessage) fun onClosed(code: Int, reason: Message) fun onFailure(t: Throwable) } interface WebSocket { fun send(text: Message) fun send(bytes: ByteMessage) fun close(code: Int, reason: Message?) fun cancel() }
  8. class ChatViewModel { val messages: LiveData<List<Message>> val isConnected: LiveData<Boolean> fun

    sendMessage(message: Message) } class OkHttp { //... } interface WebSocketListener { //... } interface WebSocket { //... }
  9. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton On Opened
  10. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton On Message
  11. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener
  12. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener On Opened
  13. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener On Failed
  14. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener On Timer Tick
  15. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener
  16. Android Lifecycle • Your app should minimize its activity when

    in the background and when the device is running on battery power.
  17. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer schedule() cancel() TimerListener
  18. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener
  19. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Lifecycle Start
  20. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Lifecycle Stop
  21. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Lifecycle Start
  22. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Failed
  23. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Timer Tick
  24. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Failed
  25. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener On Lifecycle Stop
  26. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener
  27. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener
  28. ChatView ChatViewModel OkHttp val messages val isConnected sendMessage() WebSocketListener WebSocket

    newWebSocket() Socket RecyclerView EditText SendButton Timer LifecycleOwner schedule() cancel() LifecycleListener TimerListener
  29. odel Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() Socket interface ChatService { @Receive

    fun observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val chatService = scarlet.create<ChatService>()
  30. Declarative interface ChatService { @Receive fun observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive

    fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val chatService = scarlet.create<ChatService>()
  31. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>()
  32. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>()
  33. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>() chatService.sendMessage(Message("subscribe"))
  34. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>()
  35. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>() chatService.observeWebSocketEvent() .subscribe { Log.d(“Received WebSocketEvent ${it}”) }
  36. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>()
  37. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>() chatService.observeIncomingMessage() .subscribe { Log.d(“Received message ${it}”) }
  38. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } val chatService = scarlet.create<ChatService>()
  39. Declarative class ChatViewModel( private val chatService: ChatService ) : ViewModel()

    { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().toList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  40. Declarative class ChatViewModel( private val chatService: ChatService ) : ViewModel()

    { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().toList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  41. Declarative class ChatViewModel( private val chatService: ChatService ) : ViewModel()

    { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().scanToList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  42. Declarative class ChatViewModel( private val chatService: ChatService ) : ViewModel()

    { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().scanToList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  43. Declarative class ChatViewModel( private val chatService: ChatService ) : ViewModel()

    { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().scanToList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  44. Declarative interface ChatService { @Send fun sendMessage(message: Message) @Receive fun

    observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> } class ChatViewModel( private val chatService: ChatService ) : ViewModel() { val messages: LiveData<List<Message>> = LiveDataReactiveStreams.fromPublisher( chatService.observeIncomingMessage().scanToList() ) val isConnected: LiveData<Boolean> = LiveDataReactiveStreams.fromPublisher( chatService.observeWebSocketEvent() .map { it == WebSocket.Event.OnConnectionOpen } ) fun sendMessage(message: Message) { chatService.sendMessage(message) } }
  45. odel Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() Socket interface ChatService { @Receive

    fun observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val chatService = scarlet.create<ChatService>()
  46. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() OkHttp WebSocketListener WebSocket newWebSocket() Socket LifecycleListener LifecycleOwner
  47. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() OkHttp WebSocketListener WebSocket newWebSocket() Socket LifecycleListener LifecycleOwner
  48. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() Customized Linear Exponential ExponentialWithJitter Customized MockWebServer OkHttp
  49. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() Customized Linear Exponential ExponentialWithJitter Customized MockWebServer Android OkHttp
  50. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() Customized Linear Exponential ExponentialWithJitter Customized MockWebServer Android Customized OkHttp
  51. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() interface ChatService { @Receive fun observeWebSocketEvent(

    ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val chatService = scarlet.create<ChatService>()
  52. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() interface ChatService { @Receive fun observeWebSocketEvent(

    ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val chatService = scarlet.create<ChatService>()
  53. Modular const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder()

    .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build()
  54. Modular const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder()

    .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build()
  55. Modular const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder()

    .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build()
  56. Modular const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder()

    .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build()
  57. Modular const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder()

    .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build()
  58. Modular interface ChatService { @Receive fun observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive

    fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build() val chatService = scarlet.create<ChatService>()
  59. Modular interface ChatService { @Receive fun observeWebSocketEvent( ): Flowable<WebSocket.Event> @Receive

    fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } const val URL = "wss://ws.tinder.com" val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .build() val chatService = scarlet.create<ChatService>()
  60. Message & Stream • 2 more plugins • Serialization &

    deserialization • Asynchronous abstraction
  61. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() WebSocketFactory WebSocket create() BackoffStrategy Lifecycle subscribe()

    backoffDurationMillisAt() Customized Linear Exponential ExponentialWithJitter Customized MockWebServer Android Customized OkHttp
  62. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter fromMessage() Moshi Gson Protobuf toMessage()

    WebSocketFactory OkHttp BackoffStrategy Lifecycle Android MockWebServer Linear Exponential ExponentialWithJitter
  63. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter fromMessage() Moshi Gson Protobuf toMessage()

    RxJava Kotlin Coroutine WebSocketFactory OkHttp BackoffStrategy Lifecycle Android MockWebServer Linear Exponential ExponentialWithJitter
  64. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter fromMessage() Moshi Gson Protobuf toMessage()

    StreamAdapter adapt() RxJava Kotlin Coroutine WebSocketFactory OkHttp BackoffStrategy Lifecycle Android MockWebServer Linear Exponential ExponentialWithJitter
  65. Changing Server API interface ChatService { @Receive fun observeWebSocketEvent( ):

    Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) @Send fun sendTypingIndicator(message: TypingIndicator) } val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .addMessageAdapterFactory(MoshiMessageAdapter.Factory()) .addStreamAdapterFactory(RxJava2StreamAdapter.Factory()) .build() val chatService = scarlet.create<ChatService>()
  66. Changing Server API interface ChatService { @Receive fun observeWebSocketEvent( ):

    Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) @Send fun sendTypingIndicator(message: TypingIndicator) } val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .addMessageAdapterFactory(MoshiMessageAdapter.Factory()) .addStreamAdapterFactory(RxJava2StreamAdapter.Factory()) .build() val chatService = scarlet.create<ChatService>()
  67. Switching JSON Library interface ChatService { @Receive fun observeWebSocketEvent( ):

    Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .addMessageAdapterFactory(MoshiMessageAdapter.Factory()) .addStreamAdapterFactory(RxJava2StreamAdapter.Factory()) .build()
  68. Switching JSON Library interface ChatService { @Receive fun observeWebSocketEvent( ):

    Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(AndroidLifecycle.ofApplicationForeground(app)) .addMessageAdapterFactory(JacksonMessageAdapter.Factory()) .addStreamAdapterFactory(RxJava2StreamAdapter.Factory()) .build()
  69. Write you own Lifecycle interface ChatService { @Receive fun observeWebSocketEvent(

    ): Flowable<WebSocket.Event> @Receive fun observeIncomingMessage(): Flowable<Message> @Send fun sendMessage(message: Message) } val scarlet = Scarlet.Builder() .webSocketFactory(okHttpClient.newWebSocketFactory(URL)) .backoffStrategy(LinearBackoffStrategy()) .lifecycle(createLoggedInAndAppForegroundLifecycle()) .addMessageAdapterFactory(MoshiMessageAdapter.Factory()) .addStreamAdapterFactory(RxJava2StreamAdapter.Factory()) .build()
  70. State Machine DSL sealed class State { object Solid :

    State() object Liquid : State() object Gas : State() } sealed class Event { object OnMelted : Event() object OnFroze : Event() object OnVaporized : Event() object OnCondensed : Event() }
  71. State Machine DSL sealed class State { object Solid :

    State() object Liquid : State() object Gas : State() } sealed class Event { object OnMelted : Event() object OnFroze : Event() object OnVaporized : Event() object OnCondensed : Event() }
  72. State Machine DSL sealed class State { object Solid :

    State() object Liquid : State() object Gas : State() } sealed class Event { object OnMelted : Event() object OnFroze : Event() object OnVaporized : Event() object OnCondensed : Event() }
  73. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  74. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  75. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  76. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  77. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  78. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  79. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  80. State Machine DSL StateMachine.create<State, Event> { initialState(Solid) state<Solid> { on<OnMelted>

    { transitionTo(Liquid) } } state<Liquid> { on<OnFroze> { transitionTo(Solid) } on<OnVaporized> { transitionTo(Gas) } } state<Gas> { on<OnCondensed> { transitionTo(Liquid) } } }
  81. Testing State Machine val stateMachine = StateMachine.create<State, Event> { //

    ... } assertThat(stateMachine.state).isEqualTo(Solid) // When stateMachine.transition(OnMelted) // Then assertThat(stateMachine.state).isEqualTo(Liquid)
  82. Testing State Machine val stateMachine = StateMachine.create<State, Event> { //

    ... } assertThat(stateMachine.state).isEqualTo(Solid) // When stateMachine.transition(OnMelted) // Then assertThat(stateMachine.state).isEqualTo(Liquid)
  83. Scarlet v2 Protocol Bidirectional Topics Client-Server WebSocket ✔ ✘ SSE

    ✘ ✘ Socket IO ✔ ✔ STOMP ✔ ✔ MQTT ✔ ✔
  84. Scarlet v2 Protocol Bidirectional Topics Client-Server WebSocket ✔ ✘ SSE

    ✘ ✘ Socket IO ✔ ✔ STOMP ✔ ✔ MQTT ✔ ✔
  85. Scarlet v2 Protocol Bidirectional Topics Client-Server WebSocket ✔ ✘ ✔

    SSE ✘ ✘ ✔ Socket IO ✔ ✔ ✔ STOMP ✔ ✔ ✘ MQTT ✔ ✔ ✘
  86. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter Moshi Gson Protobuf StreamAdapter RxJava

    Kotlin Coroutine WebSocketFactory OkHttp BackoffStrategy Lifecycle Android MockWebServer Linear Exponential ExponentialWithJitter
  87. Scarlet v2 observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter Moshi StreamAdapter RxJava ProtocolClient

    WebSocket-OkHttp BackoffStrategy Lifecycle Android WebSocket-MockServer Linear SocketIo-Client SocketIo-MockServer SSE-OkHttp Stomp-Gozirra MQTT-Paho
  88. Scarlet observeIncomingMessage() sendMessage observeWebSocketEvent() MessageAdapter Moshi StreamAdapter RxJava ProtocolClient WebSocket-OkHttp

    BackoffStrategy Lifecycle Android WebSocket-MockServer Linear SocketIo-Client SocketIo-MockServer SSE-OkHttp Stomp-Gozirra MQTT-Paho