1 — A request and a response


GET in Swagger, then receive the same kind of text in Java

Lesson map · Swagger Petstore · עברית

Predict, then explore in Swagger

Open Swagger Petstore and expand pet → GET /pet/findByStatus. Select Try it out, choose available, choose application/json for the response format/Accept selector, and press Execute.

Inspect Request URL, the generated curl, Server response → Code, and Response body. Use the executed server response, not the static “Example Value” shown in the documentation. The request is equivalent to:

GET /api/v3/pet/findByStatus?status=available HTTP/1.1
Host: petstore3.swagger.io
Accept: application/json

GET requests data. The path identifies the resource; the query asks for a status. Accept tells the server which response format we want. 200 is a successful HTTP status; the body is the returned text. Other status codes are also responses. A connection failure may produce no HTTP response at all.

Predict: will receiving the text create any Java animal objects? No: our first checkpoint receives a String only. The text happens to be JSON; we do not interpret it yet.

Petstore is a public, shared sample server. Its animals can change, so no lesson depends on a fixed pet ID or row count. GET and POST worked without credentials when checked on 19 October 2026, even though the API definition advertises security schemes. If behavior changes, inspect the actual Swagger response; do not invent a secret key or hide the failure. The downloadable sample in checkpoint 2 keeps JSON exercises usable offline.

Implement the first runnable checkpoint

Use Android Studio’s Android view. Gradle changes below are additions to the existing module configuration; keep every template dependency. Sync after the dependency and View Binding changes, then continue with the XML and Java edits. Generated ActivityMainBinding becomes available once the layout is processed by the build.

The root ConstraintLayout remains. Its Hello World child is replaced by a scrolling lab area. The response has its own 160dp scrolling area so a long body cannot push all later controls far down the screen. Monospace and selectable text make the body easy to inspect and copy.

Gradle Scripts > build.gradle.kts (Module :app)

Apply these additions and removals; keep the surrounding template code. Lines beginning with + are added and lines beginning with - are removed. Do not type the markers.

             }
         }
     }
+    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

Apply these additions and removals; keep the surrounding template code. Lines beginning with + are added and lines beginning with - are removed. Do not type the markers.

 <?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

Apply these additions and removals; keep the surrounding template code. Lines beginning with + are added and lines beginning with - are removed. Do not type the markers.

     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

Apply these additions and removals; keep the surrounding template code. Lines beginning with + are added and lines beginning with - are removed. Do not type the markers.

First change these two existing references. The field and initialization needed by binding are added in the structural diff below.

Before

setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
    Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());

After

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

  1. Does writing text -> { ... } execute that code immediately?
    No. It supplies a callback. Volley calls it later when a successful result is ready.
  2. Where does text get its value?
    Our parseNetworkResponse creates a String from the response bytes. Volley delivers that String as the success callback’s argument.
  3. Why write this::showError instead of showError()?
    We are giving Volley a method to call later. Volley will supply the error argument when it calls that method.
  4. Are getHeaders and parseNetworkResponse inside the success callback?
    No. The success callback ends before this::showError. The methods belong to the anonymous subclass that starts after the constructor’s closing ).
  5. Have we deserialized any animals yet?
    No. new String(...) converts bytes into text. A later gson.fromJson(...) converts that JSON text into animal objects.

Trace the Java code

StringRequest asks Volley to decode the response bytes into text. It does not deserialize animals. queue.add(request) starts asynchronous work; the method returns while the network runs. The success callback receives text later, on the main thread, so it can update the Views. this::showError is Java shorthand for error -> showError(error); it passes failures to our existing error-display method. The code never blocks the UI waiting for the server.

The two overrides expose the JSON Accept header and HTTP status. parseNetworkResponse records the status and decodes the body bytes explicitly as UTF-8. JSON exchanged over the network uses UTF-8, including Hebrew names and emoji; a missing charset header must not make the body display as Latin-1. Response.success(text, null) passes that text to the success callback without a cache entry; caching is disabled for this lab. showError preserves HTTP error bodies; it distinguishes a received error response from a transport failure with no response body.

The cache is disabled so GET demonstrates a network request. Requests are tagged with this Activity; starting a new one or leaving the screen cancels delivery of the previous request. httpStatus therefore belongs to the current request. This small lab does not preserve pending work or results through rotation; press GET again after returning. State ownership and repositories belong in the later HTTP lab.

Build and observe

Build and run on an emulator. Tap GET available pets. Expect GET HTTP 200 and a JSON array in the response area. Scroll inside it, select text, and compare its field names with Swagger. The exact animals may differ because other users share the server.

Turn the emulator’s network off and retry. Expect a visible network failure; restore networking afterwards. A 404, 401, or 5xx is an HTTP response, not proof of no internet. Keep the body visible when diagnosing it.

Download checkpoint source

Next: JSON becomes objects