Slide 1

Slide 1 text

Mohit Sarveiya gRPC with Kotlin Coroutines www.twitter.com/heyitsmohit www.codingwithmohit.com

Slide 2

Slide 2 text

gRPC with Kotlin Coroutines ● Build gRPC Server ● Using Channels & Flow with gRPC ● Build gRPC Client in Kotlin

Slide 3

Slide 3 text

Server gRPC

Slide 4

Slide 4 text

Server gRPC service

Slide 5

Slide 5 text

Server(Go) gRPC service Client(Java) Client(Python) Client(Javascript) service service service

Slide 6

Slide 6 text

Server(Go) gRPC service Client(Java) Client(Python) Client(Javascript)

Slide 7

Slide 7 text

Server(Go) gRPC service Client(Java) Client(Python) Client(Javascript) call call call

Slide 8

Slide 8 text

Server(Go) gRPC service Client(Java) Client(Python) Client(Javascript) call call call HTTP 2

Slide 9

Slide 9 text

gRPC-Kotlin kroto-plus Client Server

Slide 10

Slide 10 text

Use Case ● Tracks your route.

Slide 11

Slide 11 text

Use Case ● Tracks your route. ● Get a place from location.

Slide 12

Slide 12 text

Use Case ● Tracks your route. ● Get a place from location. ● List Places around location.

Slide 13

Slide 13 text

Use Case ● Tracks your route. ● Get a place from location. ● List Places around location. ● Chat with others at location. Shoreline Golf links 2940 N Shoreline Blvd Mountain View, CA 94043 Start typing

Slide 14

Slide 14 text

Building gRPC Server

Slide 15

Slide 15 text

Building gRPC Server ● Configure Server with Kroto-Plus ● Create Service with Protocol Buffers ● Implement Service ● Start Server

Slide 16

Slide 16 text

marcoferrer/kroto-plus Kroto-Plus+ gRPC Kotlin Coroutines Protobuf DSL Scripting for Protoc Build passing Download 0.6.1

Slide 17

Slide 17 text

gRPC Server Modules Server src database api

Slide 18

Slide 18 text

gRPC Server Modules Server src database api Server Startup / Read from RPC

Slide 19

Slide 19 text

gRPC Server Modules Server src database api Reading/Writing from DB

Slide 20

Slide 20 text

gRPC Server Modules Server src database api Define RPC Calls and Messages

Slide 21

Slide 21 text

gRPC Server Modules Server src database api Proto Buff Service proto

Slide 22

Slide 22 text

gRPC Server Modules Server src database api Create Proto Buff File proto places.proto

Slide 23

Slide 23 text

Service syntax = “proto3”; Protocol Buffer Version

Slide 24

Slide 24 text

Service syntax = “proto3”; service Places { } Declare Service

Slide 25

Slide 25 text

RPC Call Types ● Unary ● Server Streaming ● Client Streaming ● Bidirectional

Slide 26

Slide 26 text

Unary RPC Call Client Server Location

Slide 27

Slide 27 text

Unary RPC Call Client Server Place

Slide 28

Slide 28 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Unary RPC Call

Slide 29

Slide 29 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; keyword

Slide 30

Slide 30 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Name of RPC call

Slide 31

Slide 31 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Take a Location

Slide 32

Slide 32 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Returns Place

Slide 33

Slide 33 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Unary RPC Call

Slide 34

Slide 34 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; How do we define our messages?

Slide 35

Slide 35 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } } Location message

Slide 36

Slide 36 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } } Keyword

Slide 37

Slide 37 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } } Message name

Slide 38

Slide 38 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } Fields

Slide 39

Slide 39 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } Type

Slide 40

Slide 40 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } Type .proto Type C++ Java Go double double Double *float64 int32 int32 int *int32 Int64 long int/long *int64 https://developers.google.com/protocol-buffers/docs/overview#scalar

Slide 41

Slide 41 text

Unary RPC Call service Places { message Location { double latitude = 1; double longitude = 2; } Field numbers

Slide 42

Slide 42 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } How do we define Place message?

Slide 43

Slide 43 text

Unary RPC Call message Place { string name = 1; Location location = 2; } Name & Location

Slide 44

Slide 44 text

Unary RPC Call message Place { string name = 1; Location location = 2; 
 PlaceType placeType = 3; } Enum

Slide 45

Slide 45 text

Unary RPC Call message Place { string name = 1; Location location = 2; 
 PlaceType placeType = 3; enum PlaceType { Landmark = 0; Driving_Range = 1; Golf_Course = 2; Restaurant = 3; Retail = 4; } Enum

Slide 46

Slide 46 text

Unary RPC Call message Place { string name = 1; Location location = 2; PlaceType placeType = 3; enum PlaceType { … } int64 checkins = 4; int64 comments = 5; } Int Scaler Types

Slide 47

Slide 47 text

Unary RPC Call syntax = “proto3”; service Places { rpc GetPlace(Location) returns (Place) {}; } Unary RPC Call

Slide 48

Slide 48 text

Unary RPC Call syntax = “proto3”; service Places { rpc CheckIn(Place) returns () {}; } Check in to place

Slide 49

Slide 49 text

Unary RPC Call syntax = “proto3”; service Places { rpc CheckIn(Place) returns () {}; } How do we return an empty?

Slide 50

Slide 50 text

Unary RPC Call protocolbuffers/protobuf protobuf/src/google/protobuf/empty.proto message Empty { }

Slide 51

Slide 51 text

Unary RPC Call syntax = “proto3”; import "google/protobuf/empty.proto"; service Places { rpc CheckIn(Place) returns () {}; } Import

Slide 52

Slide 52 text

Unary RPC Call syntax = “proto3”; import "google/protobuf/empty.proto"; service Places { rpc CheckIn(Place) returns (google.protobuf.Empty) {}; } Empty

Slide 53

Slide 53 text

Unary RPC Call syntax = “proto3”; import "google/protobuf/empty.proto"; service Places { rpc CheckIn(Place) returns (google.protobuf.Empty) {}; }

Slide 54

Slide 54 text

RPC Call Types ● Unary ● Server Streaming ● Client Streaming ● Bidirectional

Slide 55

Slide 55 text

Server Streaming Client Server Location(s)

Slide 56

Slide 56 text

Server Streaming Client Server Stream of Places

Slide 57

Slide 57 text

Server Streaming service Places { rpc ListPlaces(Area) returns (stream Place) {}; } Server Streaming RPC Call

Slide 58

Slide 58 text

Server Streaming service Places { rpc ListPlaces(Area) returns (stream Place) {}; } Cluster of Locations

Slide 59

Slide 59 text

Server Streaming message Area { Location lo = 1; Location hi = 2; } Cluster of Locations

Slide 60

Slide 60 text

Server Streaming service Places { rpc ListPlaces(Area) returns (stream Place) {}; } stream keyword

Slide 61

Slide 61 text

Server Streaming service Places { rpc ListPlaces(Area) returns (stream Place) {}; } Server Streaming RPC Call

Slide 62

Slide 62 text

RPC Call Types ● Unary ● Server Streaming ● Client Streaming ● Bidirectional

Slide 63

Slide 63 text

Client Streaming Client Server Location(s)

Slide 64

Slide 64 text

Client Streaming Client Server Trip Summary

Slide 65

Slide 65 text

Client Streaming service Places { rpc RecordTrip(stream Location) returns (TripSummary) {}; }

Slide 66

Slide 66 text

Client Streaming service Places { rpc RecordTrip(stream Location) returns (TripSummary) {}; } Client Stream

Slide 67

Slide 67 text

Client Streaming service Places { rpc RecordTrip(stream Location) returns (TripSummary) {}; } Return single message

Slide 68

Slide 68 text

Client Streaming service Places { rpc RecordTrip(stream Location) returns (TripSummary) {}; }

Slide 69

Slide 69 text

RPC Call Types ● Unary ● Server Streaming ● Client Streaming ● Bidirectional

Slide 70

Slide 70 text

Bidirectional RPC Call Client Server Comments

Slide 71

Slide 71 text

Bidirectional RPC Call Client Server Replies

Slide 72

Slide 72 text

Bidirectional RPC Call service Places { rpc Chat(stream Comment) returns (stream Comment) {}; } Bidirectional RPC Call

Slide 73

Slide 73 text

Bidirectional RPC Call service Places { rpc Chat(stream Comment) returns (stream Comment) {}; } stream keyword

Slide 74

Slide 74 text

RPC Call Types ● Unary ● Server Streaming ● Client Streaming ● Bidirectional

Slide 75

Slide 75 text

RPC Call Types service Places { rpc GetPlace(Location) returns (Place) {}; rpc ListPlaces(Area) returns (stream Place) {}; rpc CheckIn(Place) returns (google.protobuf.Empty) {}; rpc Chat(stream Comment) returns (stream Comment) {}; }

Slide 76

Slide 76 text

Building gRPC Server ● Configure Server with Kroto-Plus ● Create Service with Protocol Buffers ● Implement Service ● Start Server

Slide 77

Slide 77 text

Generate Service Proto buffer file Protoc & Kroto-plus Message Builders Coroutine Service

Slide 78

Slide 78 text

Creating Messages message Location { double latitude = 1; double longitude = 2; } class Location { static class Builder { Builder setLatitude(double value) Builder setLongitude(double value) Location build() } }

Slide 79

Slide 79 text

Creating Messages message Location { double latitude = 1; double longitude = 2; } Location.newBuilder() .setLatitude(40.9888341) .setLongitude(-73.8502007) .build()

Slide 80

Slide 80 text

Kotlin-friendly api src / krotoPlusConfig.yml protoBuilders: - unwrapBuilders: true Create inline functions

Slide 81

Slide 81 text

Creating Messages message Location { double latitude = 1; double longitude = 2; } inline fun Location( block: Location.Builder.() "-> Unit ): Location = Location.newBuilder() .apply(block) .build()

Slide 82

Slide 82 text

Creating Messages message Location { double latitude = 1; double longitude = 2; } Location { longitude = 40.9888341 latitude = -73.8502007 }

Slide 83

Slide 83 text

Kotlin-friendly api src / krotoPlusConfig.yml protoBuilders: - unwrapBuilders: true - useDslMarkers: true

Slide 84

Slide 84 text

Creating Messages @DslMarker @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.BINARY) annotation class PlacesProtoDslMarker @PlacesProtoDslMarker interface PlacesProtoDslBuilder

Slide 85

Slide 85 text

Generate Coroutines Proto buffer file Protoc & Kroto-plus Message Builders Coroutine Service

Slide 86

Slide 86 text

gRPC with Coroutines api src / krotoPlusConfig.yml grpcCoroutines: [{}] protoBuilders: - unwrapBuilders: true - useDslMarkers: true Generate Coroutines

Slide 87

Slide 87 text

Generate Coroutines abstract class PlacesImplBase }

Slide 88

Slide 88 text

Unary RPC Call abstract class PlacesImplBase rpc GetPlace(Location) returns (Place) {}; }

Slide 89

Slide 89 text

Unary RPC Call abstract class PlacesImplBase rpc GetPlace(Location) returns (Place) {}; suspend fun getPlace(request: Location): Place }

Slide 90

Slide 90 text

Unary RPC Call Client Server Request

Slide 91

Slide 91 text

Unary RPC Call Client Server Scope (Dispatcher) Request Suspending

Slide 92

Slide 92 text

Unary RPC Call Client Server Scope (Dispatcher) Request Suspending

Slide 93

Slide 93 text

Unary RPC Call Client Server Scope (Dispatcher) Response Suspending

Slide 94

Slide 94 text

Unary RPC Call fun serverCallUnary(…) { with(newRpcScope(initialContext)) { launch(start = CoroutineStart.ATOMIC) { handleRequest() } } }

Slide 95

Slide 95 text

Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; }

Slide 96

Slide 96 text

Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel )

Slide 97

Slide 97 text

Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel )

Slide 98

Slide 98 text

Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel )

Slide 99

Slide 99 text

Channel Coroutine interface Channel : SendChannel, ReceiveChannel interface SendChannel { suspend fun send(element: E) }

Slide 100

Slide 100 text

Server Streaming abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel )

Slide 101

Slide 101 text

Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment) {};

Slide 102

Slide 102 text

Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment) {}; suspend fun chat( requestChannel: ReceiveChannel, responseChannel: SendChannel )

Slide 103

Slide 103 text

Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment) {}; suspend fun chat( requestChannel: ReceiveChannel, responseChannel: SendChannel )

Slide 104

Slide 104 text

Channel Coroutine interface Channel : SendChannel, ReceiveChannel interface ReceiveChannel { suspend fun receive(): E }

Slide 105

Slide 105 text

Bidirectional abstract class PlacesImplBase rpc Chat(stream Comment) returns (stream Comment) {}; suspend fun chat( requestChannel: ReceiveChannel, responseChannel: SendChannel )

Slide 106

Slide 106 text

Streaming Client Server Scope (Dispatcher) Request Send Channel Receive Channel

Slide 107

Slide 107 text

Streaming Client Server Scope (Dispatcher) Request Send Channel Receive Channel

Slide 108

Slide 108 text

Streaming Client Server Scope (Dispatcher) Response Send Channel Receive Channel

Slide 109

Slide 109 text

Generate Service abstract class PlacesImplBase { open suspend fun getPlace(…): Place open suspend fun listPlaces(…) open suspend fun chat(…) open suspend fun recordTrip(…) }

Slide 110

Slide 110 text

Implement Service class PlacesService(): PlacesImplBase() { } Inherit base Implementation

Slide 111

Slide 111 text

Implement Service class PlacesService(): PlacesImplBase() { override val initialContext: CoroutineContext get() = Dispatchers.IO } Specify Dispatcher

Slide 112

Slide 112 text

Implement Service class PlacesService(dispatcher): PlacesImplBase() { override val initialContext: CoroutineContext get() = Dispatchers.IO }

Slide 113

Slide 113 text

Implement Service class PlacesService(dispatcher): PlacesImplBase() { override val initialContext: CoroutineContext get() = dispatcher } Specify Dispatcher

Slide 114

Slide 114 text

Implement Service class PlacesService(dispatcher): PlacesImplBase() { override suspend fun getPlace(request: Location): Place { } }

Slide 115

Slide 115 text

Implement Service class PlacesService(dispatcher): PlacesImplBase() { override suspend fun getPlace(request: Location): Place { val placeFromDB = getPlacesFromDb() .first { it.location "== request } } } Get Place from DB

Slide 116

Slide 116 text

Implement Service class PlacesService(dispatcher): PlacesImplBase() { override suspend fun getPlace(request: Location): Place { val placeFromDB = getPlacesFromDb() .first { it.location "== request } return Place { name = placeFromDb.name !!... } } Map it to Proto Message

Slide 117

Slide 117 text

Implement Service abstract class PlacesImplBase rpc ListPlaces(Area) returns (stream Place) {}; suspend fun listPlaces( request: Area, responseChannel: SendChannel )

Slide 118

Slide 118 text

Implement Service abstract class PlacesImplBase suspend fun listPlaces( area: Area, responseChannel: SendChannel ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } Send to Channel

Slide 119

Slide 119 text

Implement Service abstract class PlacesImplBase suspend fun listPlaces( area: Area, responseChannel: SendChannel ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } How does channel close?

Slide 120

Slide 120 text

Close Channel kroto-plus kroto-plus/ServerCalls.kt rpcScope.launch { block(responseChannel) responseChannel.close() } Closes Channel

Slide 121

Slide 121 text

Errors abstract class PlacesImplBase suspend fun listPlaces( area: Area, responseChannel: SendChannel ) { val places = getPlaces(area) places.forEach { responseChannel.send(it) } } } Exception occurs?

Slide 122

Slide 122 text

Errors kroto-plus kroto-plus/ServerCalls.kt rpcScope.launch { try { block(responseChannel) responseChannel.close() } finally { cancelScope() } Clean up on error

Slide 123

Slide 123 text

Implement Service class PlacesService(dispatcher): PlacesImplBase { suspend fun getPlace(…): Place suspend fun listPlaces(…) suspend fun chat(…) suspend fun recordTrip(…) }

Slide 124

Slide 124 text

Building gRPC Server ● Configure Server with Kroto-Plus ● Create Service with Protocol Buffers ● Implement Service ● Start Server

Slide 125

Slide 125 text

grpc-java grpc-java/ServerBuilder.java class ServerBuilder { forPort(port) addService(service) interceptor(interceptor)

Slide 126

Slide 126 text

Configure gRPC Server val server: Server = ServerBuilder .forPort(port) .addService(PlacesService()) .build()

Slide 127

Slide 127 text

Configure gRPC Server val server: Server = ServerBuilder .forPort(port) .addService(PlacesService()) .build()

Slide 128

Slide 128 text

Start gRPC Server fun start() { server.start() Runtime.getRuntime().addShutdownHook( Thread { server.shutdown() } ) }

Slide 129

Slide 129 text

Start gRPC Server fun main() { val port = 50051 val server = configureServer(port) server.start() server.blockUntilShutdown() }

Slide 130

Slide 130 text

Building gRPC Server ● Configure Server with Kroto-Plus ● Create Service with Protocol Buffers ● Implement Service ● Start Server

Slide 131

Slide 131 text

Resources ● gRPC Java https:!//github.com/grpc/grpc-java ● Kroto-plus https:!//github.com/marcoferrer/kroto-plus ● Protocol Buffers https:!//developers.google.com/protocol-buffers/docs/overview ● Micronaut https:!//micronaut.io/

Slide 132

Slide 132 text

Building gRPC Client

Slide 133

Slide 133 text

grpc/grpc-kotlin gRPC-Kotlin/JVM - An RPC library and framework grpc-kotlin-stub v0.1.4 protoc-gen-grpc-kotlinstub v0.1.4 grpc-kotlin-stub-lite v0.1.4 A Kotlin/JVM implementation of gRPC: A high performance, open source, general RPC framework that puts mobile and HTTP/2 first. Bazel Build passing Gradle Build passing

Slide 134

Slide 134 text

Server Service Proto File

Slide 135

Slide 135 text

Client Server Service Proto File Service Proto File

Slide 136

Slide 136 text

Client Server Service Proto File Service Proto File Client Stub

Slide 137

Slide 137 text

Generated Client class CoroutineStub { }

Slide 138

Slide 138 text

Generated Client class CoroutineStub { } rpc GetPlace(Location) returns (Place) {};

Slide 139

Slide 139 text

Generated Client class CoroutineStub { } rpc GetPlace(Location) returns (Place) {}; suspend fun getPlace(request: Location): Place

Slide 140

Slide 140 text

Generated Client class CoroutineStub { } rpc ListPlaces(Area) returns (stream Place) {}; fun listPlaces(request: Area): Flow Stream is map to Flow

Slide 141

Slide 141 text

Generated Client class CoroutineStub { } rpc Chat(stream Comment) returns (stream Comment) {}; fun chat(requests: Flow): Flow Stream is map to Flow

Slide 142

Slide 142 text

Generated Client class CoroutineStub { } suspend fun getPlace(request: Location): Place fun recordTrip(requests: Flow): TripSummary fun listPlaces(request: Area): Flow fun chat(requests: Flow): Flow

Slide 143

Slide 143 text

Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .useTransportSecurity() .build() Specify host and port

Slide 144

Slide 144 text

Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .useTransportSecurity() .build() for https

Slide 145

Slide 145 text

Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .intercept(object : ClientInterceptor { fun interceptCall(method, callOptions, channel) { }) .build() Intercept RPC Calls

Slide 146

Slide 146 text

Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .useTransportSecurity() .build()

Slide 147

Slide 147 text

Using Client val managedChannel = ManagedChannelBuilder .forAddress(host, port) .useTransportSecurity() .build() val client = CoroutineStub(managedChannel)

Slide 148

Slide 148 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { }

Slide 149

Slide 149 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun getPlace(location: Location) { viewModelScope.launch { val place = client.getPlace(location) } } }

Slide 150

Slide 150 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun listPlaces(area: Area) { viewModelScope.launch { val places: Flow = client.listPlaces(area) places.collect { } } } }

Slide 151

Slide 151 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments: ReceiveChannel) { viewModelScope.launch { client.chat(comments.consumeAsFlow()).collect { } } } } Stream of comments

Slide 152

Slide 152 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments: ReceiveChannel) { viewModelScope.launch { client.chat(comments.consumeAsFlow()) .collect { } } } } Convert Channel to Flow

Slide 153

Slide 153 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun chat(comments: ReceiveChannel) { viewModelScope.launch { client.chat(comments.consumeAsFlow()) .collect { } } } } Collect from Flow

Slide 154

Slide 154 text

Using Client class GrpcViewModel(val client: CoroutineStub): ViewModel() { fun getPlace(location: Location) fun listPlaces(area: Area) fun chat(chat: ReceiveChannel) }

Slide 155

Slide 155 text

View Model gRPC Client Stub

Slide 156

Slide 156 text

gRPC Client Stub How does it use coroutines?

Slide 157

Slide 157 text

Consumer Coroutine

Slide 158

Slide 158 text

gRPC-java client Start Consumer Coroutine

Slide 159

Slide 159 text

gRPC-java client Request Consumer Coroutine Producer Coroutine Response

Slide 160

Slide 160 text

gRPC-java client Request Consumer Coroutine Producer Coroutine Response Channel (size = 1)

Slide 161

Slide 161 text

gRPC-java client Request Consumer Coroutine Producer Coroutine Response Channel (size = 1)

Slide 162

Slide 162 text

gRPC-java client Request Consumer Coroutine Producer Coroutine Response Channel (size = 1) Flow

Slide 163

Slide 163 text

View Model gRPC Client Stub

Slide 164

Slide 164 text

Resources ● gRPC Kotlin https:!//github.com/grpc/grpc-kotlin ● Wire https:!//github.com/square/wire

Slide 165

Slide 165 text

gRPC-Kotlin kroto-plus Client Server

Slide 166

Slide 166 text

https:"//codingwithmohit.com/

Slide 167

Slide 167 text

Resources ● Unit Testing Delays, Errors & Retries with Kotlin Flows https:!//codingwithmohit.com/coroutines/unit-testing-delays- errors-retries-with-kotlin-flows/ ● Kotlin Assert Flow Delight https:!//codingwithmohit.com/coroutines/kotlin-assert-flow-delight/ ● Channels & Flows in Practice https:!//speakerdeck.com/heyitsmohit/channels-and-flows-in-practice

Slide 168

Slide 168 text

Thank You! www.twitter.com/heyitsmohit www.codingwithmohit.com