Skip to content

Commit ef5c808

Browse files
authored
Add Android example (#137)
1 parent 5bb4b1e commit ef5c808

File tree

19 files changed

+743
-0
lines changed

19 files changed

+743
-0
lines changed

examples-android/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.gradle/
2+
.idea/
3+
build/
4+
*.iml
5+
/local.properties
6+
/gradle.properties

examples-android/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: server/build/install/server/bin/server

examples-android/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# gRPC Kotlin Android Sample
2+
3+
## Run Server Locally:
4+
5+
Run the server:
6+
```
7+
./gradlew -t :server:run
8+
```
9+
10+
## Deploy and Run Server on Cloud Run:
11+
12+
[![Run on Google Cloud](https://deploy.cloud.run/button.png)](https://deploy.cloud.run/?cloudshell_context=cloudrun-gbp)
13+
14+
## Run the Client:
15+
16+
1. [Download Android Command Line Tools](https://developer.android.com/studio)
17+
18+
1. Install the SDK:
19+
```
20+
mkdir android-sdk
21+
cd android-sdk
22+
unzip PATH_TO_SDK_ZIP/sdk-tools-linux-VERSION.zip
23+
tools/bin/sdkmanager --update
24+
tools/bin/sdkmanager "platforms;android-29" "build-tools;29.0.3" "extras;google;m2repository" "extras;android;m2repository"
25+
tools/bin/sdkmanager --licenses
26+
```
27+
28+
1. Set an env var pointing to the `android-sdk`
29+
```
30+
export ANDROID_HOME=PATH_TO_SDK/android-sdk
31+
```
32+
33+
1. Run the build from this project's dir:
34+
```
35+
./gradlew :android:build
36+
```
37+
38+
1. You can either run on an emulator or a physical device and you can either connect to the server running on your local machine, or connect to a server you deployed on the cloud.
39+
40+
* Emulator + Local Server:
41+
* From the command line:
42+
```
43+
./gradlew :android:installDebug
44+
```
45+
* From Android Studio / IntelliJ, navigate to `android/src/main/kotlin/io/grpc/examples/helloworld` and right-click on `MainActivity` and select `Run`.
46+
47+
* Physical Device + Local Server:
48+
* From the command line:
49+
1. [Setup adb](https://developer.android.com/studio/run/device)
50+
1. `./gradlew :android:installDebug -PserverUrl=http://YOUR_MACHINE_IP:50051/`
51+
* From Android Studio / IntelliJ:
52+
1. Create a `gradle.properties` file in your root project directory containing:
53+
```
54+
serverUrl=http://YOUR_MACHINE_IP:50051/
55+
```
56+
1. Navigate to `android/src/main/kotlin/io/grpc/examples/helloworld` and right-click on `MainActivity` and select `Run`.
57+
58+
* Emulator or Physical Device + Cloud:
59+
* From the command line:
60+
1. [setup adb](https://developer.android.com/studio/run/device)
61+
1. `./gradlew :android:installDebug -PserverUrl=https://YOUR_SERVER/`
62+
* From Android Studio / IntelliJ:
63+
1. Create a `gradle.properties` file in your root project directory containing:
64+
```
65+
serverUrl=https://YOUR_SERVER/
66+
```
67+
1. Navigate to `android/src/main/kotlin/io/grpc/examples/helloworld` and right-click on `MainActivity` and select `Run`.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
plugins {
2+
id("com.android.application")
3+
kotlin("android")
4+
}
5+
6+
dependencies {
7+
implementation(kotlin("stdlib"))
8+
implementation("androidx.appcompat:appcompat:1.1.0")
9+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7")
10+
implementation(project(":common"))
11+
runtimeOnly("io.grpc:grpc-okhttp:${rootProject.ext["grpcVersion"]}")
12+
}
13+
14+
android {
15+
compileSdkVersion(29)
16+
buildToolsVersion = "29.0.3"
17+
18+
defaultConfig {
19+
applicationId = "io.grpc.examples.hello"
20+
minSdkVersion(23)
21+
targetSdkVersion(29)
22+
versionCode = 1
23+
versionName = "1.0"
24+
25+
val serverUrl: String? by project
26+
if (serverUrl != null) {
27+
resValue("string", "server_url", serverUrl!!)
28+
} else {
29+
resValue("string", "server_url", "http://10.0.2.2:50051/")
30+
}
31+
}
32+
33+
sourceSets["main"].java.srcDir("src/main/kotlin")
34+
35+
compileOptions {
36+
sourceCompatibility = JavaVersion.VERSION_1_7
37+
targetCompatibility = JavaVersion.VERSION_1_7
38+
}
39+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
android.useAndroidX=true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.grpc.examples.helloworld">
3+
4+
<uses-permission android:name="android.permission.INTERNET"/>
5+
6+
<application
7+
android:allowBackup="true"
8+
android:fullBackupContent="true"
9+
android:icon="@android:drawable/btn_star"
10+
android:label="@string/app_label">
11+
<activity
12+
android:theme="@style/Theme.AppCompat.NoActionBar"
13+
android:name="io.grpc.examples.helloworld.MainActivity">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN"/>
16+
<category android:name="android.intent.category.LAUNCHER"/>
17+
</intent-filter>
18+
</activity>
19+
</application>
20+
21+
</manifest>
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package io.grpc.examples.helloworld
2+
3+
import android.os.Bundle
4+
import android.text.Editable
5+
import android.text.TextWatcher
6+
import android.view.KeyEvent
7+
import android.widget.Button
8+
import android.widget.EditText
9+
import android.widget.TextView
10+
import androidx.appcompat.app.AppCompatActivity
11+
import io.grpc.ManagedChannel
12+
import io.grpc.ManagedChannelBuilder
13+
import java.net.URL
14+
import java.util.logging.Logger
15+
import kotlinx.coroutines.Dispatchers
16+
import kotlinx.coroutines.asExecutor
17+
import kotlinx.coroutines.runBlocking
18+
19+
// todo: suspend funs
20+
class MainActivity : AppCompatActivity() {
21+
private val logger = Logger.getLogger(this.javaClass.name)
22+
23+
private fun channel(): ManagedChannel {
24+
val url = URL(resources.getString(R.string.server_url))
25+
val port = if (url.port == -1) url.defaultPort else url.port
26+
27+
logger.info("Connecting to ${url.host}:$port")
28+
29+
val builder = ManagedChannelBuilder.forAddress(url.host, port)
30+
if (url.protocol == "https") {
31+
builder.useTransportSecurity()
32+
} else {
33+
builder.usePlaintext()
34+
}
35+
36+
return builder.executor(Dispatchers.Default.asExecutor()).build()
37+
}
38+
39+
// lazy otherwise resources is null
40+
private val greeter by lazy { GreeterGrpcKt.GreeterCoroutineStub(channel()) }
41+
42+
override fun onCreate(savedInstanceState: Bundle?) {
43+
super.onCreate(savedInstanceState)
44+
setContentView(R.layout.activity_main)
45+
46+
val button = findViewById<Button>(R.id.button)
47+
48+
val nameText = findViewById<EditText>(R.id.name)
49+
nameText.addTextChangedListener(object : TextWatcher {
50+
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
51+
button.isEnabled = s.isNotEmpty()
52+
}
53+
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
54+
override fun afterTextChanged(s: Editable) { }
55+
})
56+
57+
val responseText = findViewById<TextView>(R.id.response)
58+
59+
fun sendReq() = runBlocking {
60+
try {
61+
val request = HelloRequest.newBuilder().setName(nameText.text.toString()).build()
62+
val response = greeter.sayHello(request)
63+
responseText.text = "Server says: " + response.getMessage()
64+
} catch (e: Exception) {
65+
responseText.text = "Server error: " + e.message
66+
e.printStackTrace()
67+
}
68+
}
69+
70+
button.setOnClickListener {
71+
sendReq()
72+
}
73+
74+
nameText.setOnKeyListener { _, keyCode, event ->
75+
if ((event.action == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
76+
sendReq()
77+
true
78+
} else {
79+
false
80+
}
81+
}
82+
}
83+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<LinearLayout
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent"
7+
android:orientation="vertical"
8+
android:gravity="center_horizontal"
9+
tools:context="io.grpc.examples.helloworld.MainActivity">
10+
11+
<EditText android:id="@+id/name"
12+
android:layout_width="match_parent"
13+
android:layout_height="wrap_content"
14+
android:inputType="text"
15+
android:hint="@string/name_hint"
16+
android:autofillHints="name"/>
17+
18+
<Button android:id="@+id/button"
19+
android:layout_width="wrap_content"
20+
android:layout_height="wrap_content"
21+
android:text="@string/say_hello"
22+
android:enabled="false"/>
23+
24+
<TextView android:id="@+id/response"
25+
android:layout_width="wrap_content"
26+
android:layout_height="wrap_content"/>
27+
28+
</LinearLayout>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="name_hint">name</string>
4+
<string name="say_hello">say hello</string>
5+
<string name="app_label">gRPC Kotlin Android</string>
6+
</resources>

examples-android/build.gradle.kts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
buildscript {
2+
repositories {
3+
gradlePluginPortal()
4+
google()
5+
}
6+
dependencies {
7+
classpath("com.android.tools.build:gradle:3.6.3")
8+
}
9+
}
10+
11+
plugins {
12+
kotlin("jvm") version "1.3.72"
13+
id("org.jlleitschuh.gradle.ktlint") version "9.2.1"
14+
}
15+
16+
// todo: move to subprojects, but how?
17+
ext["grpcVersion"] = "1.30.0"
18+
ext["grpcKotlinVersion"] = "0.1.3"
19+
ext["protobufVersion"] = "3.12.2"
20+
21+
allprojects {
22+
repositories {
23+
mavenLocal()
24+
mavenCentral()
25+
jcenter()
26+
google()
27+
}
28+
29+
apply(plugin = "org.jlleitschuh.gradle.ktlint")
30+
}
31+
32+
tasks.replace("assemble").dependsOn(":server:installDist")
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import com.google.protobuf.gradle.generateProtoTasks
2+
import com.google.protobuf.gradle.id
3+
import com.google.protobuf.gradle.ofSourceSet
4+
import com.google.protobuf.gradle.plugins
5+
import com.google.protobuf.gradle.protobuf
6+
import com.google.protobuf.gradle.protoc
7+
8+
plugins {
9+
kotlin("jvm")
10+
id("com.google.protobuf") version "0.8.12"
11+
}
12+
13+
dependencies {
14+
implementation(kotlin("stdlib"))
15+
implementation("javax.annotation:javax.annotation-api:1.3.2")
16+
api("io.grpc:grpc-protobuf-lite:${rootProject.ext["grpcVersion"]}")
17+
api("io.grpc:grpc-stub:${rootProject.ext["grpcVersion"]}")
18+
api("io.grpc:grpc-kotlin-stub:${rootProject.ext["grpcKotlinVersion"]}") {
19+
exclude("io.grpc", "grpc-protobuf")
20+
}
21+
}
22+
23+
java {
24+
sourceCompatibility = JavaVersion.VERSION_1_7
25+
}
26+
27+
protobuf {
28+
protoc {
29+
artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}"
30+
}
31+
plugins {
32+
id("grpc") {
33+
artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}"
34+
}
35+
id("grpckt") {
36+
artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}"
37+
}
38+
}
39+
generateProtoTasks {
40+
ofSourceSet("main").forEach {
41+
it.builtins {
42+
named("java") {
43+
option("lite")
44+
}
45+
}
46+
it.plugins {
47+
id("grpc") {
48+
option("lite")
49+
}
50+
id("grpckt") {
51+
option("lite")
52+
}
53+
}
54+
}
55+
}
56+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Copyright 2015 gRPC authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
syntax = "proto3";
15+
16+
option java_multiple_files = true;
17+
option java_package = "io.grpc.examples.helloworld";
18+
option java_outer_classname = "HelloWorldProto";
19+
option objc_class_prefix = "HLW";
20+
21+
package helloworld;
22+
23+
// The greeting service definition.
24+
service Greeter {
25+
// Sends a greeting
26+
rpc SayHello (HelloRequest) returns (HelloReply) {}
27+
}
28+
29+
// The request message containing the user's name.
30+
message HelloRequest {
31+
string name = 1;
32+
}
33+
34+
// The response message containing the greetings
35+
message HelloReply {
36+
string message = 1;
37+
}
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
distributionBase=GRADLE_USER_HOME
2+
distributionPath=wrapper/dists
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
4+
zipStoreBase=GRADLE_USER_HOME
5+
zipStorePath=wrapper/dists

0 commit comments

Comments
 (0)