Skip to content

Commit 8868f6f

Browse files
committed
universal android application for sending requests
1 parent 9fd813d commit 8868f6f

File tree

7 files changed

+371
-51
lines changed

7 files changed

+371
-51
lines changed

android/android-app-curl-with-two-buttons/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ gradle --status
3939
./gradlew --stop
4040
./gradlew --status
4141

42+
# build/rebuild
4243
./gradlew clean :app:assembleDebug --no-build-cache
4344
ls -la app/build/outputs/apk/debug/app-debug.apk
4445
```
@@ -48,4 +49,17 @@ ls -la app/build/outputs/apk/debug/app-debug.apk
4849
adb devices
4950
# if no devices: https://github.com/cherkavi/cheat-sheet/blob/master/android-cheat-sheet.md#adb
5051
adb install -r app/build/outputs/apk/debug/app-debug.apk
52+
```
53+
54+
## [test application using http server](./http_test_server/server.py)
55+
56+
### start server
57+
```sh
58+
python3 http_test_server/server.py
59+
```
60+
61+
### test requests
62+
```sh
63+
curl -X POST http://localhost:8000/posts -H "Content-Type: application/json" -d '{"msg":"hello"}'
64+
curl -X POST http://localhost:8000/posts -d 'plain text body'
5165
```

android/android-app-curl-with-two-buttons/app/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ android {
1010
}
1111
}
1212

13-
dependencies {
13+
dependencies {
1414
implementation 'androidx.appcompat:appcompat:1.6.1'
1515
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
16+
implementation 'com.google.code.gson:gson:2.8.9'
1617
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[
2+
{
3+
"title": "button #1 create post",
4+
"request": "### Create a New Post"
5+
},
6+
{
7+
"title": "button #2 create another post",
8+
"request": "### Create another Post"
9+
}
10+
11+
]
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Variables
2+
@baseUrl = http://192.168.01.01:8080
3+
@authToken = your_token_here
4+
5+
### Create a New Post
6+
POST {{baseUrl}}/posts
7+
Content-Type: application/json
8+
Authorization: Bearer {{authToken}}
9+
Custom-Header: MyValue
10+
11+
{
12+
"title": "First post",
13+
"body": "this is first post",
14+
"userId": 123
15+
}
16+
17+
### Create another Post
18+
POST {{baseUrl}}/posts
19+
Content-Type: application/json
20+
Authorization: Bearer {{authToken}}
21+
Custom-Header: MyValue
22+
23+
{
24+
"title": "Second post",
25+
"body": "second post",
26+
"userId": 2222
27+
}
Lines changed: 214 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,236 @@
11
package com.example.buttonapp;
22

3-
import android.os.Bundle;
4-
import android.widget.Button;
5-
import android.widget.Toast;
6-
import androidx.appcompat.app.AppCompatActivity;
7-
import java.io.IOException;
8-
import okhttp3.Call;
9-
import okhttp3.Callback;
10-
import okhttp3.OkHttpClient;
11-
import okhttp3.Request;
3+
import android.os.Bundle;
4+
import android.view.ViewGroup;
5+
import android.widget.Button;
6+
import android.widget.LinearLayout;
7+
import androidx.appcompat.app.AlertDialog;
8+
import androidx.appcompat.app.AppCompatActivity;
9+
import com.google.gson.Gson;
10+
import com.google.gson.annotations.SerializedName;
11+
import java.io.BufferedReader;
12+
import java.io.IOException;
13+
import java.io.InputStream;
14+
import java.io.InputStreamReader;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
import okhttp3.Call;
19+
import okhttp3.Callback;
20+
import okhttp3.Headers;
21+
import okhttp3.MediaType;
22+
import okhttp3.OkHttpClient;
23+
import okhttp3.Request;
24+
import okhttp3.RequestBody;
1225
import okhttp3.Response;
1326

1427
public class MainActivity extends AppCompatActivity {
1528

1629
private final OkHttpClient client = new OkHttpClient();
17-
// 10.0.2.2 is how the Android Emulator sees your computer's localhost
18-
private final String SERVER_URL = "http://10.0.2.2:8080/";
30+
private LinearLayout buttonContainer;
31+
private final Gson gson = new Gson();
32+
33+
private static class ButtonSpec {
34+
@SerializedName("title")
35+
String title;
36+
@SerializedName("request")
37+
String request;
38+
}
1939

2040
@Override
2141
protected void onCreate(Bundle savedInstanceState) {
2242
super.onCreate(savedInstanceState);
2343
setContentView(R.layout.activity_main);
44+
buttonContainer = findViewById(R.id.buttonContainer);
45+
46+
String buttonsJson = readAssetToString("buttons.json");
47+
ButtonSpec[] specs = gson.fromJson(buttonsJson, ButtonSpec[].class);
48+
if (specs != null) {
49+
for (ButtonSpec s : specs) {
50+
Button b = new Button(this);
51+
b.setText(s.title);
52+
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
53+
ViewGroup.LayoutParams.MATCH_PARENT,
54+
ViewGroup.LayoutParams.WRAP_CONTENT);
55+
lp.setMargins(0, 16, 0, 0);
56+
b.setLayoutParams(lp);
57+
b.setOnClickListener(v -> onButtonClick(s.request));
58+
buttonContainer.addView(b);
59+
}
60+
}
61+
}
62+
63+
private String readAssetToString(String name) {
64+
try (InputStream is = getAssets().open(name);
65+
InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);
66+
BufferedReader br = new BufferedReader(isr)) {
67+
StringBuilder sb = new StringBuilder();
68+
String line;
69+
while ((line = br.readLine()) != null) {
70+
sb.append(line).append('\n');
71+
}
72+
return sb.toString();
73+
} catch (IOException e) {
74+
e.printStackTrace();
75+
return "";
76+
}
77+
}
78+
79+
private void onButtonClick(String requestHeading) {
80+
new Thread(() -> {
81+
String httpText = readAssetToString("requests.http");
82+
Map<String, String> vars = parseHttpFileVariables(httpText);
83+
String block = extractRequestBlock(httpText, requestHeading);
84+
if (block == null) {
85+
runOnUiThread(() -> showAlert("Error", "Request not found: " + requestHeading));
86+
return;
87+
}
88+
Request okReq = buildOkHttpRequestFromBlock(block, vars);
89+
if (okReq == null) {
90+
runOnUiThread(() -> showAlert("Error", "Failed to build request"));
91+
return;
92+
}
93+
client.newCall(okReq).enqueue(new Callback() {
94+
@Override
95+
public void onFailure(Call call, IOException e) {
96+
runOnUiThread(() -> showAlert("Network Error", e.getMessage()));
97+
}
98+
99+
@Override
100+
public void onResponse(Call call, Response response) throws IOException {
101+
final String body = response.body() != null ? response.body().string() : "";
102+
runOnUiThread(() -> showAlert("Response: " + response.code(), body));
103+
}
104+
});
105+
}).start();
106+
}
24107

25-
Button btnOne = findViewById(R.id.btn_one);
26-
Button btnTwo = findViewById(R.id.btn_two);
108+
private Map<String, String> parseHttpFileVariables(String text) {
109+
Map<String, String> vars = new HashMap<>();
110+
BufferedReader br = new BufferedReader(new InputStreamReader(
111+
new java.io.ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)),
112+
StandardCharsets.UTF_8));
113+
try {
114+
String line;
115+
while ((line = br.readLine()) != null) {
116+
line = line.trim();
117+
if (line.startsWith("@")) {
118+
int eq = line.indexOf('=');
119+
if (eq > 1) {
120+
String name = line.substring(1, eq).trim();
121+
String val = line.substring(eq + 1).trim();
122+
vars.put(name, val);
123+
}
124+
}
125+
}
126+
} catch (IOException ignored) { }
127+
return vars;
128+
}
27129

28-
btnOne.setOnClickListener(v -> makeRequest("button1"));
29-
btnTwo.setOnClickListener(v -> makeRequest("button2"));
130+
private String extractRequestBlock(String text, String heading) {
131+
String[] lines = text.split("\\r?\\n");
132+
StringBuilder sb = null;
133+
for (int i = 0; i < lines.length; i++) {
134+
if (lines[i].trim().equals(heading)) {
135+
sb = new StringBuilder();
136+
for (int j = i + 1; j < lines.length; j++) {
137+
if (lines[j].trim().startsWith("### ")) break;
138+
sb.append(lines[j]).append('\n');
139+
}
140+
break;
141+
}
142+
}
143+
return sb == null ? null : sb.toString().trim();
30144
}
31145

32-
private void makeRequest(String path) {
33-
Request request = new Request.Builder()
34-
.url(SERVER_URL + path)
35-
.build();
146+
private Request buildOkHttpRequestFromBlock(String block, Map<String, String> vars) {
147+
BufferedReader br = new BufferedReader(new InputStreamReader(
148+
new java.io.ByteArrayInputStream(block.getBytes(StandardCharsets.UTF_8)),
149+
StandardCharsets.UTF_8));
150+
try {
151+
String methodAndUrl;
152+
do {
153+
methodAndUrl = br.readLine();
154+
if (methodAndUrl == null) return null;
155+
} while (methodAndUrl.trim().isEmpty());
156+
157+
String[] parts = methodAndUrl.trim().split("\\s+", 2);
158+
String method = parts[0].toUpperCase();
159+
String url = parts.length > 1 ? substituteVariables(parts[1].trim(), vars) : "";
160+
161+
Headers.Builder headersBuilder = new Headers.Builder();
162+
String line;
163+
StringBuilder bodySb = new StringBuilder();
164+
boolean inBody = false;
165+
while ((line = br.readLine()) != null) {
166+
if (!inBody) {
167+
if (line.trim().isEmpty()) {
168+
inBody = true;
169+
continue;
170+
}
171+
int colon = line.indexOf(':');
172+
if (colon > 0) {
173+
String name = line.substring(0, colon).trim();
174+
String val = line.substring(colon + 1).trim();
175+
val = substituteVariables(val, vars);
176+
headersBuilder.add(name, val);
177+
}
178+
} else {
179+
bodySb.append(line).append('\n');
180+
}
181+
}
36182

37-
client.newCall(request).enqueue(new Callback() {
38-
@Override
39-
public void onFailure(Call call, IOException e) {
40-
runOnUiThread(() ->
41-
Toast.makeText(MainActivity.this, "Network Error: " + e.getMessage(), Toast.LENGTH_LONG).show());
183+
RequestBody requestBody = null;
184+
if (!method.equals("GET") && !method.equals("HEAD")) {
185+
String bodyStr = bodySb.toString().trim();
186+
if (!bodyStr.isEmpty()) {
187+
String contentType = headersBuilder.build().get("Content-Type");
188+
MediaType mt = contentType != null ? MediaType.parse(contentType) : MediaType.parse("application/json; charset=utf-8");
189+
requestBody = RequestBody.create(bodyStr, mt);
190+
} else {
191+
requestBody = RequestBody.create(new byte[0], null);
192+
}
42193
}
43194

44-
@Override
45-
public void onResponse(Call call, Response response) throws IOException {
46-
final String responseData = response.isSuccessful() ? "Success!" : "Server Error: " + response.code();
47-
runOnUiThread(() ->
48-
Toast.makeText(MainActivity.this, responseData, Toast.LENGTH_SHORT).show());
195+
Request.Builder reqB = new Request.Builder().url(url).headers(headersBuilder.build());
196+
switch (method) {
197+
case "GET": reqB.get(); break;
198+
case "POST": reqB.post(requestBody); break;
199+
case "PUT": reqB.put(requestBody); break;
200+
case "DELETE":
201+
if (requestBody != null) reqB.delete(requestBody);
202+
else reqB.delete();
203+
break;
204+
case "PATCH": reqB.patch(requestBody); break;
205+
default: return null;
49206
}
50-
});
207+
return reqB.build();
208+
} catch (IOException e) {
209+
e.printStackTrace();
210+
return null;
211+
}
212+
}
213+
214+
private String substituteVariables(String s, Map<String, String> vars) {
215+
if (s == null) return "";
216+
java.util.regex.Pattern p = java.util.regex.Pattern.compile("\\{\\{([^}]+)\\}\\}");
217+
java.util.regex.Matcher m = p.matcher(s);
218+
StringBuffer sb = new StringBuffer();
219+
while (m.find()) {
220+
String name = m.group(1).trim();
221+
String val = vars.getOrDefault(name, "");
222+
m.appendReplacement(sb, java.util.regex.Matcher.quoteReplacement(val));
223+
}
224+
m.appendTail(sb);
225+
return sb.toString();
51226
}
227+
228+
private void showAlert(String title, String message) {
229+
new AlertDialog.Builder(this)
230+
.setTitle(title)
231+
.setMessage(message)
232+
.setPositiveButton("OK", null)
233+
.show();
234+
}
235+
52236
}
Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
<?xml version="1.0" encoding="utf-8"?>
2-
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3-
android:layout_width="match_parent"
4-
android:layout_height="match_parent"
5-
android:orientation="vertical"
6-
android:gravity="center"
2+
<ScrollView
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:layout_width="match_parent"
5+
android:layout_height="match_parent"
76
android:padding="20dp">
87

9-
<Button
10-
android:id="@+id/btn_one"
11-
android:layout_width="match_parent"
12-
android:layout_height="60dp"
13-
android:text="Send to /button1"
14-
android:backgroundTint="#2196F3"/>
15-
16-
<Button
17-
android:id="@+id/btn_two"
18-
android:layout_width="match_parent"
19-
android:layout_height="60dp"
20-
android:layout_marginTop="20dp"
21-
android:text="Send to /button2"
22-
android:backgroundTint="#4CAF50"/>
8+
<LinearLayout
9+
android:id="@+id/buttonContainer"
10+
android:layout_width="match_parent"
11+
android:layout_height="wrap_content"
12+
android:orientation="vertical"
13+
android:gravity="center" />
2314

24-
</LinearLayout>
15+
</ScrollView>

0 commit comments

Comments
 (0)