Blog Infos
Author
Published
Topics
,
Published
Topics
,

grpc-image

 

gRPC is a modern open-source high-performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking, and authentication. It Automatically generates idiomatic client and server stubs for your service in a variety of languages and platforms. It supports Bidirectional streaming between the client and server with HTTP/2-based transport.

A RPC is a form of Client-Server Communication that uses a function call rather than a usual HTTP call.

gRPC uses ProtocolBuffers which is google’s mechanism of serializing structured data.

Protocol Buffers:

Protocol buffers provide a language-neutral, platform-neutral, extensible mechanism for serializing structured data in a forward-compatible and backward-compatible way. It’s like JSON, except it’s smaller and faster, and it generates native language bindings.

You have to create a file with an extension proto

Protocol buffers are the most commonly-used data format used by gRPC.

message Person {
  string name = 1;
  int32 id = 2;
}

You can consider the message as an object.

To create gRPC services.

follow this:

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

gRPC uses protoc with a special gRPC plugin to generate code from your proto file: you get generated gRPC client and server code, as well as the regular protocol buffer code for populating, serializing, and retrieving your message types.

gRPC provides four kinds of services.

  1. Unary RPCs where the client sends a single request to the server and gets a single response back, just like a normal function call.
  2. Server streaming RPCs where the client sends a request to the server and gets a stream to read a sequence of messages back. The client reads from the returned stream until there are no more messages. gRPC guarantees message ordering within an individual RPC call.
  3. Client streaming RPCs where the client writes a sequence of messages and sends them to the server, again using a provided stream. Once the client has finished writing the messages, it waits for the server to read them and return its response. Again gRPC guarantees message ordering within an individual RPC call.
  4. Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream.
Let’s connect the gRPC server with our Android App.
checkout my app code here.

checkout my gRPC server here

Step 1: Create a new android project.

Step 2:

Add this in project level Gradle file’s build

buildscript {
    ...
    dependencies {
        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.18"
    }
}

Now, go to app level gradle file.

Add this in plugins block

id 'com.google.protobuf'

Above dependencies block add this.

protobuf {
    protoc { artifact = 'com.google.protobuf:protoc:3.19.2' }
    plugins {
        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.47.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { option 'lite' }
            }
            task.plugins {
                grpc {
                    option 'lite' }
            }
        }
    }
}

Now, in the dependencies block

//grpc dependency
implementation 'io.grpc:grpc-okhttp:1.47.0'
implementation 'io.grpc:grpc-protobuf-lite:1.47.0'
implementation 'io.grpc:grpc-stub:1.47.0'
implementation 'org.apache.tomcat:annotations-api:6.0.53'

click Sync Now

Step 3:

Now, you need to create a Proto file.

go to

root -> app -> src -> main -> proto (create this folder)

Add this file greeter.proto

syntax = "proto3";
option java_multiple_files = true;
option java_package = "com.example.grpc_app_demo";
option java_outer_classname = "GreeterProto";
option objc_class_prefix = "GRT";
package greeter;
// The greeting service definition.
service Greeter {
//unary call
rpc SayHello (HelloRequest) returns (HelloResponse) {}
//server_streaming
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
//client_streaming
rpc LotsOfRequests(stream HelloRequest) returns (HelloResponse);
//bi-directional_streaming
rpc BidirectionalHello(stream HelloRequest) returns (stream HelloResponse);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloResponse {
string message = 1;
}
view raw greeter.proto hosted with ❤ by GitHub

Now rebuild the project. you will find a few generated files.

these are the client stubs using which we will connect to grpc server.

Step 4:

Before, exchanging data between client and server. we need to create a channel that is basically connecting the client and server.

channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()

we will later provide host address and port number.

Now, to send a simple unary call.

fun sendMessage(
message: String,
channel: ManagedChannel?
):String? {
return try {
val stub = GreeterGrpc.newBlockingStub(channel)
val request = HelloRequest.newBuilder().setName(message).build()
val reply = stub.sayHello(request)
reply.message
} catch (e: Exception) {
e.message
}
}
view raw grpc.kt hosted with ❤ by GitHub

Here,

we are using generated stub GreeterGrpcStub

and creating a HelloRequest which is our input.

and fetching the reply.

before calling this method we need to set up our local server.

Step 5:

You need Node.js.

So download from here and setup

Download | Node.js

Now, create a folder grpc-server

and run npm init -y

you need to override package.json file with this.

{
...
"dependencies": {
 "@grpc/proto-loader": "⁰.5.0",
 "async": "¹.5.2",
 "google-protobuf": "³.0.0",
 "@grpc/grpc-js": "¹.1.0",
 "lodash": "⁴.6.1",
 "minimist": "¹.2.0"
 }
}
view raw package.json hosted with ❤ by GitHub

Now, run npm install

Now, put the same greeter.proto here in this folder.

and now create an index.js file

and put this code

var PROTO_PATH = __dirname+'/greeter.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var greeter_proto =
grpc.loadPackageDefinition(packageDefinition).greeter;
function lotsOfReplies(call) {
console.log("Request:"+call.request);
var name = call.request.name
for(let i=0;i<10;++i){
call.write({message: "Yo "+name+" "+i});
}
call.end();
}
function sayHello(call, callback) {
callback(null, {message: 'Yo ' + call.request.name});
}
function lotsOfRequests(call, callback) {
var input = [];
var index = 0;
call.on('data',function(request){
console.log("Request:"+request.name);
input.push("Hello "+request.name+" "+index+"\n");
index++;
});
call.on('end',function(){
callback(null, {message: input.toString()});
});
}
function bidirectionalHello(call) {
var input = [];
var index = 0;
call.on('data',function(request){
console.log("Request: "+request.name);
if(index < 2){
call.write({message: "Hello "+request.name+" "+(index*2)});
}
else{
call.write({message: "Yo "+request.name+" "+(index*3)});
}
input.push(request.name+"\n");
index++;
});
call.on('end',function(){
call.write({message: "\n"+input.toString()+" "+(index*index)});
call.end();
});
}
function main() {
var server = new grpc.Server();
server.addService(greeter_proto.Greeter.service, {
sayHello: sayHello,
lotsOfReplies: lotsOfReplies,
lotsOfRequests: lotsOfRequests,
bidirectionalHello: bidirectionalHello
});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
});
}
main();
view raw index.js hosted with ❤ by GitHub

Job Offers

Job Offers


    Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Android Test Automation Engineer

    Komoot
    Remote
    • Full Time
    apply now

    Senior Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

OUR VIDEO RECOMMENDATION

, ,

From Scoped Storage to Photo Picker: Everything to know about Storage

Persistence is a core element of every mobile app. Android provides different APIs to access or expose files with different tradeoffs.
Watch Video

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocate
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer ad ...
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocat ...
Google

Jobs

Now, run node index.js

Your localhost is up and running

Step 6:

Go to MainActivity.kt

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val mainViewModel: MainViewModel by viewModels()
super.onCreate(savedInstanceState)
setContent {
GRPC_APP_DEMOTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Scaffold(
topBar = {
TopAppBar(
title = {
Text("GRPC Demo")
}
)
}
) {
Column(
modifier = Modifier
.padding(24.dp)
){
Row(
modifier = Modifier.fillMaxWidth()
){
OutlinedTextField(
enabled = mainViewModel.hostEnabled.value,
value = mainViewModel.ip.value,
onValueChange = {
mainViewModel.onIpChange(it)
},
modifier = Modifier
.fillMaxWidth()
.weight(1f),
placeholder = {
Text("IP address")
},
label = {
Text("Server")
}
)
Spacer(modifier = Modifier.size(16.dp))
OutlinedTextField(
enabled = mainViewModel.portEnabled.value,
value = mainViewModel.port.value,
onValueChange = {
mainViewModel.onPortChange(it)
},
modifier = Modifier
.fillMaxWidth()
.weight(1f),
placeholder = {
Text("Port")
},
label = {
Text("Port")
}
)
}
Row(
modifier = Modifier.fillMaxWidth()
){
Button(
enabled = mainViewModel.startEnabled.value,
onClick = {
mainViewModel.start()
},
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Text("Start")
}
Spacer(modifier = Modifier.size(16.dp))
Button(
enabled = mainViewModel.endEnabled.value,
onClick = {
mainViewModel.exit()
},
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) {
Text("End")
}
}
Button(
enabled = mainViewModel.buttonsEnabled.value,
onClick = {
mainViewModel.sayHello("Arun")
},
modifier = Modifier.fillMaxWidth()
) {
Text("Simple RPC: Say Hello")
}
Text("Result: ${mainViewModel.result.value}")
}
}
}
}
}
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

In the MainViewModel.kt

put this method

fun sayHello(name: String) {
viewModelScope.launch(context = Dispatchers.IO) {
try {
updateResult(sendMessage(name,channel) ?: "")
} catch (e: Exception) {
updateResult(e.message?:"")
}
}
}

and in grpc.kt

fun sendMessage(
message: String,
channel: ManagedChannel?
):String? {
return try {
val stub = GreeterGrpc.newBlockingStub(channel)
val request = HelloRequest.newBuilder().setName(message).build()
val reply = stub.sayHello(request)
reply.message
} catch (e: Exception) {
e.message
}
}
view raw grpc.kt hosted with ❤ by GitHub

Now, run the app.

In the field, Server add 10.0.2.2 and Port 50051

and click the Start button and click Simple RPC button. you will get a response from your local server.

So, you will get a reply from Server as per your code.

Now, for server streaming.

in MainViewModel.kt put this code.

fun sendMessageWithReplies(message: String) {
    viewModelScope.launch(Dispatchers.IO) {
        try {
            updateResult(sendMessageWithReplies(message,channel).toString())
        } catch (e: Exception) {
            updateResult(e.message?:"")
        }
    }
}

and in grpc.kt

fun sendMessageWithReplies(
    message: String,
    channel: ManagedChannel?
):Any? {
    return try {
        val stub = GreeterGrpc.newBlockingStub(channel)
        val request = HelloRequest.newBuilder().setName(message).build()
        val reply = stub.lotsOfReplies(request)
        reply.asSequence().toList().map { it -> it.message+"\n" }
    } catch (e: Exception) {
        e
    }
}

you will receive an iterator.

 

Click on Server Streaming Button and you will get a stream of messages.

Now, for Client Streaming

You need to create an asynchronous stub.

fun sendMessageWithRequests(
channel: ManagedChannel?
):Any {
return try {
val stub = GreeterGrpc.newStub(channel)
var failed: Throwable? = null
val finishLatch = CountDownLatch(1)
val responseList = mutableListOf<HelloResponse>()
val requestObserver = stub.lotsOfRequests(object : StreamObserver<HelloResponse> {
override fun onNext(response: HelloResponse) {
responseList.add(response)
}
override fun onError(t: Throwable) {
failed = t
finishLatch.countDown()
}
override fun onCompleted() {
finishLatch.countDown()
}
})
try {
val requests = arrayOf(
newHelloResponse("TOM"),
newHelloResponse("ANDY"),
newHelloResponse("MANDY"),
newHelloResponse("John")
)
for (request in requests) {
requestObserver.onNext(request)
}
} catch (e: java.lang.RuntimeException) {
requestObserver.onError(e)
return e.message?:""
}
requestObserver.onCompleted()
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
return "Timeout error"
}
if (failed != null) {
return failed?.message?:""
}
return responseList.map { it.message }
} catch (e: Exception) {
e
}
}
view raw grpc.kt hosted with ❤ by GitHub

You need to create 2 stream observers one for response and request.

I have created a countdown latch to wait on the current thread.

So, click on Client Streaming and you will get the output.

The same way you have to do for Bi-directional Streaming

fun sendMessageBiDirectional(channel: ManagedChannel?): Any{
return try {
val stub = GreeterGrpc.newStub(channel)
var failed: Throwable? = null
val finishLatch = CountDownLatch(1)
val responseList = mutableListOf<HelloResponse>()
val requestObserver = stub.bidirectionalHello(object : StreamObserver<HelloResponse> {
override fun onNext(response: HelloResponse) {
responseList.add(response)
}
override fun onError(t: Throwable) {
failed = t
finishLatch.countDown()
}
override fun onCompleted() {
finishLatch.countDown()
}
})
try {
val requests = arrayOf(
newHelloResponse("TOM"),
newHelloResponse("ANDY"),
newHelloResponse("MANDY"),
newHelloResponse("John")
)
for (request in requests) {
requestObserver.onNext(request)
}
} catch (e: java.lang.RuntimeException) {
requestObserver.onError(e)
return e.message?:""
}
requestObserver.onCompleted()
if (!finishLatch.await(1, TimeUnit.MINUTES)) {
return "Timeout error"
}
if (failed != null) {
return failed?.message?:""
}
return responseList.map { it.message }
} catch (e: Exception) {
e
}
}
view raw grpc.kt hosted with ❤ by GitHub

 

Click on the Bi-directional button.

That’s it.

Now, I will explain a few methods used in the Node.js server.

var PROTO_PATH = __dirname+'/greeter.proto';
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var greeter_proto =
grpc.loadPackageDefinition(packageDefinition).greeter;
function lotsOfReplies(call) {
console.log("Request:"+call.request);
var name = call.request.name
for(let i=0;i<10;++i){
call.write({message: "Yo "+name+" "+i});
}
call.end();
}
function sayHello(call, callback) {
callback(null, {message: 'Yo ' + call.request.name});
}
function lotsOfRequests(call, callback) {
var input = [];
var index = 0;
call.on('data',function(request){
console.log("Request:"+request.name);
input.push("Hello "+request.name+" "+index+"\n");
index++;
});
call.on('end',function(){
callback(null, {message: input.toString()});
});
}
function bidirectionalHello(call) {
var input = [];
var index = 0;
call.on('data',function(request){
console.log("Request: "+request.name);
if(index < 2){
call.write({message: "Hello "+request.name+" "+(index*2)});
}
else{
call.write({message: "Yo "+request.name+" "+(index*3)});
}
input.push(request.name+"\n");
index++;
});
call.on('end',function(){
call.write({message: "\n"+input.toString()+" "+(index*index)});
call.end();
});
}
function main() {
var server = new grpc.Server();
server.addService(greeter_proto.Greeter.service, {
sayHello: sayHello,
lotsOfReplies: lotsOfReplies,
lotsOfRequests: lotsOfRequests,
bidirectionalHello: bidirectionalHello
});
server.bindAsync('0.0.0.0:50051', grpc.ServerCredentials.createInsecure(), () => {
server.start();
});
}
main();
view raw index.js hosted with ❤ by GitHub

Here in the main() method, we are starting a grpc server at IP 0.0.0.0 and port 50051.

and adding Greeter service.

I have added some logic in different RPC calls for demonstration purposes.

for streaming requests like in the case of client streaming and bidirectional streaming. we need to have callbacks such as call.on(‘data’,null) and call.on(‘end’,null).

In the case of getting requests from stream, call object implements Readable.

In the case of sending a response on a stream, call object implements Writable.

gRPC is a very high-performing way to communicate with servers.

explore more about it here

Thank You for Reading

 

This article was originally published on proandroiddev.com on December 26, 2022

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu