מפת השיעור · Swagger Petstore · English version
קודם מנבאים, ואז מנסים ב־Swagger
פתחו את Swagger Petstore והרחיבו את הפעולה GET /pet/findByStatus בקבוצת pet. לחצו Try it out, בחרו available, בחרו application/json בפורמט התשובה או בבורר Accept, ולחצו Execute.
בדקו את Request URL, את פקודת curl שנוצרה, את Code תחת Server response, ואת Response body. קראו את תשובת השרת לאחר ההרצה. האזור “Example Value” הוא דוגמה קבועה בתיעוד. הבקשה ששלחנו שקולה לזו:
GET /api/v3/pet/findByStatus?status=available HTTP/1.1
Host: petstore3.swagger.io
Accept: application/json
GET מבקשת נתונים. הנתיב מזהה את המשאב, ופרמטר השאילתה מבקש מצב מסוים. הכותרת Accept מציינת באיזה פורמט נרצה לקבל תשובה. 200 הוא קוד הצלחה של HTTP; גוף התשובה מכיל את הטקסט שהוחזר. גם קודי מצב אחרים הם תשובות. בכשל חיבור ייתכן שלא תתקבל תשובת HTTP כלל.
נבאו: האם קבלת הטקסט תיצור אובייקטים של בעלי חיים ב־Java? לא: בשלב הראשון נקבל רק String. הטקסט כתוב בפורמט JSON, ועדיין לא נפרש אותו.
Petstore הוא שרת דוגמה ציבורי ומשותף. בעלי החיים בו עשויים להשתנות, ולכן התרגיל אינו תלוי במזהה קבוע או במספר שורות קבוע. בבדיקה ב־19 באוקטובר 2026, GET ו־POST פעלו בלי פרטי הזדהות, אף שהגדרת ה־API מציגה מנגנוני הרשאה. אם ההתנהגות משתנה, בדקו את התשובה בפועל ב־Swagger. דוגמת ה־JSON להורדה בשלב 2 מאפשרת להמשיך לתרגל גם בלי רשת.
מממשים את נקודת הבדיקה הראשונה
עבדו בתצוגת Android של Android Studio. השינויים ב־Gradle מתווספים להגדרות המודול הקיימות; משאירים את כל תלויות התבנית. אחרי הוספת הספרייה והפעלת View Binding בצעו Sync, ואז המשיכו לעריכות XML ו־Java. המחלקה ActivityMainBinding נוצרת כאשר תהליך הבנייה מעבד את קובץ הפריסה.
משאירים את ConstraintLayout הראשי. מחליפים את רכיב Hello World שבתוכו באזור ניסוי שאפשר לגלול. לתשובה יש אזור גלילה משלה בגובה 160dp, כך שתשובה ארוכה לא תדחוף את כל הפקדים שבהמשך הרחק למטה. גופן ברוחב קבוע וטקסט שניתן לבחור מקלים על הקריאה וההעתקה.
Gradle Scripts > build.gradle.kts (Module :app)
בצעו את ההוספות וההסרות האלה והשאירו את קוד התבנית שסביבן. שורות שמתחילות ב־+ מתווספות ושורות שמתחילות ב־- מוסרות. את סימני השינוי עצמם אין להקליד.
}
}
}
+ buildFeatures {
+ viewBinding = true
+ }
+
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
+ implementation("com.android.volley:volley:1.2.1")
implementation(libs.activity.ktx)
implementation(libs.appcompat)
implementation(libs.constraintlayout)
app > manifests > AndroidManifest.xml
בצעו את ההוספות וההסרות האלה והשאירו את קוד התבנית שסביבן. שורות שמתחילות ב־+ מתווספות ושורות שמתחילות ב־- מוסרות. את סימני השינוי עצמם אין להקליד.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
+
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
app > res > layout > activity_main.xml
בצעו את ההוספות וההסרות האלה והשאירו את קוד התבנית שסביבן. שורות שמתחילות ב־+ מתווספות ושורות שמתחילות ב־- מוסרות. את סימני השינוי עצמם אין להקליד.
android:layout_height="match_parent"
tools:context=".MainActivity">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Hello World!"
+ <androidx.core.widget.NestedScrollView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:fillViewport="true"
+ app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
+ app:layout_constraintEnd_toEndOf="parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="16dp">
+ <Button
+ android:id="@+id/getPets"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="GET available pets" />
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:accessibilityLiveRegion="polite"
+ android:text="Ready — send a request" />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="12dp"
+ android:text="Last HTTP response body — unchanged text"
+ android:textStyle="bold" />
+ <androidx.core.widget.NestedScrollView
+ android:layout_width="match_parent"
+ android:layout_height="160dp">
+ <TextView
+ android:id="@+id/responseJson"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:fontFamily="monospace"
+ android:textIsSelectable="true" />
+ </androidx.core.widget.NestedScrollView>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
app > kotlin+java > com.example.topic > MainActivity
בצעו את ההוספות וההסרות האלה והשאירו את קוד התבנית שסביבן. שורות שמתחילות ב־+ מתווספות ושורות שמתחילות ב־- מוסרות. את סימני השינוי עצמם אין להקליד.
תחילה שנו את שתי ההפניות הקיימות האלה. השדה binding והאתחול שלו מתווספים בקטע השינויים המבניים שמתחת להשוואה.
לפני
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());אחרי
setContentView(binding.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import com.android.volley.NetworkResponse;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+import com.example.topic.databinding.ActivityMainBinding;
+
+import java.util.Collections;
+import java.util.Map;
+
public class MainActivity extends AppCompatActivity {
+ private static final String BASE_URL = "https://petstore3.swagger.io/api/v3";
+ private ActivityMainBinding binding;
+ private RequestQueue queue;
+ private int httpStatus;
+ /** Creates the screen and connects its network action. */
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
+ binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
ViewCompat.setOnApplyWindowInsetsListener(binding.main, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
+ queue = Volley.newRequestQueue(getApplicationContext());
+ binding.getPets.setOnClickListener(v -> getPets());
+ }
+
+ /** Fetches JSON as text; no Java Pet objects are created here. */
+ private void getPets() {
+ queue.cancelAll(this);
+ binding.status.setText("Sending GET...");
+ StringRequest request = new StringRequest(Request.Method.GET,
+ BASE_URL + "/pet/findByStatus?status=available", text -> {
+ binding.responseJson.setText(text);
+ binding.status.setText("GET HTTP " + httpStatus);
+ }, this::showError) {
+ @Override
+ public Map<String, String> getHeaders() {
+ return Collections.singletonMap("Accept", "application/json");
+ }
+
+ @Override
+ protected Response<String> parseNetworkResponse(NetworkResponse response) {
+ httpStatus = response.statusCode;
+ return Response.success(
+ new String(response.data, java.nio.charset.StandardCharsets.UTF_8), null);
+ }
+ };
+ request.setShouldCache(false);
+ request.setTag(this);
+ queue.add(request);
+ }
+
+ /** Keeps HTTP error bodies visible and distinguishes them from connection failures. */
+ private void showError(VolleyError error) {
+ NetworkResponse response = error.networkResponse;
+ binding.status.setText(response == null
+ ? "Network failure: " + error.getClass().getSimpleName()
+ : "HTTP " + response.statusCode);
+ binding.responseJson.setText(response == null ? "No HTTP response body received."
+ : new String(response.data, java.nio.charset.StandardCharsets.UTF_8));
+ }
+
+ /** Cancels delivery to this screen when it is no longer visible. */
+ @Override
+ protected void onStop() {
+ queue.cancelAll(this);
+ binding.status.setText("Ready — requests cancelled when leaving the screen");
+ super.onStop();
}
}
Callback walkthrough: who calls our code, and when?
Runnable checkpoint: the code edits above are complete. Build and run the app, press GET available pets, and check that the response text appears. Now use this walkthrough to explain the code you have just run. The annotated excerpt below is a reading aid for that existing code; keep it in its existing method locations.
This chunk contains three callbacks and two overridden methods. A callback means:
Here is code to run when something happens. I am giving you that code now; you will call it later.
There are two different Java mechanisms here:
| Code | How we supply it | Who calls it? |
|---|---|---|
v -> getPets() |
Button-click callback | Android, when the button is clicked |
text -> { ... } |
Success callback passed to the constructor | Volley, when the result is ready |
this::showError |
Error callback passed to the constructor | Volley, when the request fails |
getHeaders() |
Overridden method in an anonymous subclass | Volley, while preparing the request |
parseNetworkResponse(...) |
Overridden method in that same subclass | Volley, while processing a successful response |
The overridden methods are also hooks through which Volley calls our code. They are defined differently from the two constructor callback arguments.
The complete flow
flowchart TD
subgraph SETUP["1. Screen setup — main thread"]
A["Create the Volley request queue"]
B["Register the button callback<br/>v → getPets()"]
C["Wait for a click"]
A --> B --> C
end
subgraph CLICK["2. Button clicked — main thread"]
D["Android calls the click callback"]
E["getPets() runs"]
F["Cancel older requests tagged with this Activity<br/>Display: Sending GET..."]
G["Construct a StringRequest object<br/>Supply GET, URL, success callback, error callback<br/>Override getHeaders and parseNetworkResponse"]
H["Disable caching<br/>Attach the Activity as a tag"]
I["queue.add(request)<br/>Submit the request for background work"]
J["getPets() returns<br/>The screen remains responsive"]
D --> E --> F --> G --> H --> I --> J
end
subgraph NETWORK["3. Volley does the network work — worker thread"]
K["Volley calls our getHeaders()<br/>Accept: application/json"]
L["Volley sends the HTTP request"]
M{"Successful response?"}
N["Volley calls our parseNetworkResponse(response)<br/>Save HTTP status<br/>Decode bytes into a UTF-8 String"]
O["Return Response.success(text, null)<br/>The text is ready for delivery"]
P["Volley prepares an error result"]
K --> L --> M
M -->|Yes| N --> O
M -->|HTTP error or network failure| P
end
subgraph DELIVERY["4. Volley delivers the result — main thread"]
Q["Volley calls our success callback<br/>text → display JSON text and status"]
R["Volley calls our error callback<br/>showError(error)"]
end
C -->|Student clicks| D
I -->|Background processing| K
O -->|If the request was not cancelled| Q
P -->|If the request was not cancelled| R
classDef setup fill:#e8f0fe,stroke:#4285f4,color:#111;
classDef worker fill:#fff3cd,stroke:#c49000,color:#111;
classDef success fill:#d9ead3,stroke:#38761d,color:#111;
classDef failure fill:#f4cccc,stroke:#a61c00,color:#111;
class A,B,C,D,E,F,G,H,I,J setup;
class K,L,M,N,O worker;
class Q success;
class P,R failure;
The first and last sections use the same main thread. Network work and response parsing happen on a worker thread; Volley delivers the callbacks on the main thread so they can update the screen. See Volley’s execution model.
The same code, heavily commented
The comments explain both the syntax and the timing. The first two statements belong at the end of the existing onCreate method.
// Create a queue that manages network work in the background.
// Creating the queue does not send our GET request yet.
queue = Volley.newRequestQueue(getApplicationContext());
// Give Android a callback to remember.
// Android calls it when the button is clicked.
// v is the clicked View; this callback does not need to use it.
binding.getPets.setOnClickListener(v -> getPets());
} // End of the existing onCreate method.
/**
* Creates and submits a GET request.
*
* The response arrives later through a callback.
* This method does not wait for the server or return its response.
*/
private void getPets() {
// Cancel older requests carrying this Activity's tag.
// Their response callbacks should no longer update this screen.
queue.cancelAll(this);
// This runs immediately, before the response arrives.
binding.status.setText("Sending GET...");
// Construct a request object.
// The constructor receives FOUR arguments:
StringRequest request = new StringRequest(
// Argument 1: which HTTP operation?
Request.Method.GET,
// Argument 2: where should the request go?
BASE_URL + "/pet/findByStatus?status=available",
// Argument 3: SUCCESS CALLBACK.
// We give Volley this code now.
// Later, Volley supplies the String named text and calls it.
text -> {
// text contains the decoded HTTP response body.
// It is still JSON TEXT, not Pet objects.
binding.responseJson.setText(text);
binding.status.setText("GET HTTP " + httpStatus);
},
// Argument 4: ERROR CALLBACK.
// Pass a reference to this Activity's showError method.
// Equivalent to: error -> showError(error)
this::showError
) {
// The constructor arguments have ended at the ")" above.
//
// These braces define an ANONYMOUS SUBCLASS of StringRequest:
// a subclass created here without giving it a separate class name.
//
// Its methods below are NOT additional constructor arguments.
// They replace inherited behavior for this request object.
/**
* Volley calls this while preparing the HTTP request.
*
* @return request headers asking for a JSON response
*/
@Override
public Map<String, String> getHeaders() {
// A Map containing one header name/value pair.
return Collections.singletonMap("Accept", "application/json");
}
/**
* Volley calls this on a worker thread after receiving
* a successful network response.
*
* @param response HTTP status, headers, and raw body bytes
* @return a successful Volley result containing decoded text
*/
@Override
protected Response<String> parseNetworkResponse(
NetworkResponse response) {
// Save the HTTP status for the success callback to display.
httpStatus = response.statusCode;
// response.data is a byte array.
// Decode those bytes using UTF-8 to create a Java String.
//
// Response.success wraps that String as a Volley result.
// null means there is no cache entry attached to this result.
//
// Returning this result does NOT directly execute our callback.
// Volley schedules its delivery on the main thread.
return Response.success(
new String(
response.data,
java.nio.charset.StandardCharsets.UTF_8),
null);
}
};
// Finish configuring the request before submitting it.
request.setShouldCache(false);
// Tag it with this Activity so cancelAll(this) can identify it.
request.setTag(this);
// Submit the request. Volley can now process it asynchronously.
queue.add(request);
// The method ends without waiting for the response.
// Volley will call the appropriate callback later,
// unless the request has been cancelled.
}
Volley’s source confirms that parseNetworkResponse runs during background processing, and that the returned result is subsequently delivered through its response-delivery mechanism: network processing, callback delivery.
Small Q&A
- Does writing
text -> { ... }execute that code immediately?
No. It supplies a callback. Volley calls it later when a successful result is ready. - Where does
textget its value?
OurparseNetworkResponsecreates a String from the response bytes. Volley delivers that String as the success callback’s argument. - Why write
this::showErrorinstead ofshowError()?
We are giving Volley a method to call later. Volley will supply the error argument when it calls that method. - Are
getHeadersandparseNetworkResponseinside the success callback?
No. The success callback ends beforethis::showError. The methods belong to the anonymous subclass that starts after the constructor’s closing). - Have we deserialized any animals yet?
No.new String(...)converts bytes into text. A latergson.fromJson(...)converts that JSON text into animal objects.
עוקבים אחרי קוד Java
StringRequest מבקשת מ־Volley לפענח את הבתים של התשובה לטקסט. עדיין לא נוצרים אובייקטי בעלי חיים. queue.add(request) מתחילה עבודה אסינכרונית: המתודה מסתיימת בזמן שהתקשורת ממשיכה ברקע. פונקציית התגובה להצלחה מקבלת את text מאוחר יותר, ב־main thread, ולכן יכולה לעדכן את רכיבי המסך. הביטוי this::showError הוא קיצור ב־Java ל־error -> showError(error); הוא מעביר כישלון למתודת הצגת השגיאה. המסך ממשיך לפעול בזמן ההמתנה לשרת.
שתי המתודות שאנו דורסים חושפות את כותרת Accept ואת קוד המצב. parseNetworkResponse שומרת את קוד המצב ומפענחת במפורש את הבתים לפי UTF-8. זהו הקידוד של JSON המועבר ברשת, גם כשיש שמות בעברית או אימוג׳י. הפענוח המפורש חשוב גם כשהשרת אינו מציין charset. הקריאה Response.success(text, null) מעבירה את הטקסט לפונקציית ההצלחה בלי רשומת מטמון; במעבדה המטמון כבוי. showError משאירה את גופי תשובות השגיאה גלויים ומבדילה בין תשובת HTTP שהתקבלה לבין כשל העברה ללא גוף תשובה.
המטמון כבוי כדי שהניסוי ב־GET ידגים בקשת רשת. הבקשות מתויגות באמצעות ה־Activity: התחלת בקשה חדשה או יציאה מהמסך מבטלות את מסירת התשובה של הבקשה הקודמת. המשתנה httpStatus משמש להצגת קוד המצב של הבקשה בניסוי. המעבדה הקטנה אינה משמרת עבודה ממתינה או תוצאות בזמן סיבוב המסך; לחצו GET שוב אחרי החזרה. ניהול בעלות על מצב ו־Repository נלמדים במעבדת HTTP המתקדמת.
בונים ומסתכלים על התוצאה
בצעו Build והריצו באמולטור. לחצו GET available pets. בתשובה מוצלחת תראו GET HTTP 200 ומערך JSON באזור התשובה. גללו בתוכו, בחרו טקסט והשוו את שמות השדות ל־Swagger. בעלי החיים עצמם עשויים להיות שונים, כי משתמשים נוספים עובדים עם אותו שרת.
כבו את חיבור הרשת באמולטור ונסו שוב. אמורה להופיע הודעת כשל רשת; הפעילו שוב את הרשת בסיום הניסוי. קוד 404, 401 או 5xx מציין תשובת HTTP שהתקבלה. היעזרו בגוף התשובה הגלוי כדי לאבחן אותה.