The hard Flutter API bugs rarely look like “networking is broken.” They look like this: macOS works, Android Emulator times out, a physical device never reaches your local API, Dio sends an expired token, staging returns a different JSON shape than production, and checkout only fails after a retry. Logs help, but logs are still your app’s opinion. A proxy shows what actually crossed the boundary.
This guide is a case-by-case debugging workbook for the Rockxy Flutter sample. It starts with one safe HTTP request and gradually moves into localhost routing, HTTPS inspection, filtering, Allow List, Map Remote, Modify Header, Breakpoints, Map Local, Block List, Network Conditions, Replay, Compose, and scripting. It is deliberately practical: each case has a problem, a sample scenario, a Rockxy action, expected evidence, and a failure interpretation.
The debugging assignment
Imagine a Flutter storefront with this production-like incident:
- Startup works on macOS, but Android Emulator users see timeouts.
- A developer points the app at
localhost, but the request reaches the wrong runtime. - HTTPS rows appear in the proxy, but headers and bodies are not decrypted.
- The profile API returns 401 after a refresh should have happened.
- The app is accidentally calling a production-marked environment during staging QA.
- Checkout fails because the POST body total does not match the backend calculation.
- A product endpoint returns a schema the Flutter UI does not expect.
- An optional analytics endpoint fails and should not break the user flow.
- The team needs to retry edited requests without tapping through the whole app again.
The goal is not to “prove Rockxy can capture something.” The goal is to build a repeatable workflow a Flutter engineer can follow when a real app has the same class of API, HTTPS, proxy, environment, auth, timeout, and contract bugs.
The sample emits safe correlation headers on every request:
X-Rockxy-Lab: flutter-sample
X-Rockxy-Lab-Run-Id: run-...
X-Rockxy-Scenario-Id: auth-recovery
X-Rockxy-Step-Id: expired-token
X-Rockxy-Client: dio-5
X-Rockxy-Runtime: local-apple-runtime
X-Request-ID: run-...-auth-recovery-expired-token
Those headers are the backbone of the workflow. In Rockxy, filter by the run ID first, then drill down by scenario and step ID. That keeps the capture readable when your Mac is also producing browser, package manager, simulator, analytics, and background app traffic.
What success looks like
By the end of the workbook, you should have a capture where each row can answer one of these questions:
- Did the Flutter runtime actually use Rockxy as its HTTP proxy?
- Did the request go to the intended API host, path, scheme, and environment?
- Can Rockxy decrypt the HTTPS request and response body for the target host?
- Which header, token, query parameter, or body value caused the failure?
- Can a rule, fixture, replay, or script isolate the problem without changing app code?
- Is the failure in the Flutter client, the proxy setup, the backend contract, or the current Rockxy UX?
Prepare the lab
Before debugging HTTPS, auth, mapping, or replay, prove that one plain HTTP request reaches Rockxy. Start the sample API and app:
git clone https://github.com/RockxyApp/Rockxy-Flutter-Sample-Guidance.git
cd Rockxy-Flutter-Sample-Guidance
fvm flutter pub get
fvm dart run tool/rockxy_demo_api.dart --port 43210
# In another terminal
fvm flutter run -d macos
In Rockxy, start capture and copy the active proxy port. In the sample app, paste that port into Rockxy port. Keep Demo API base URL separate from the proxy target. For the first pass on macOS, use:
| Sample field | Value for the first pass |
|---|---|
| Runtime | iOS Simulator / macOS desktop |
| Rockxy port | The active port from Rockxy, not the demo API port |
| Demo API base URL | http://127.0.0.1:43210 |
| Client | Dart HttpClient first, then repeat with package:http and Dio |
| Proxy through Rockxy | Enabled |
Proxy host and API host are different
A lot of Flutter localhost API debugging gets derailed because two different addresses are mixed together: the proxy address and the API address.
| Runtime | Rockxy proxy host | Local API host |
|---|---|---|
| macOS Flutter | 127.0.0.1:<Rockxy port> | 127.0.0.1:43210 |
| iOS Simulator | 127.0.0.1:<Rockxy port> | 127.0.0.1:43210 |
| Android Emulator | 10.0.2.2:<Rockxy port> | 10.0.2.2:43210 when the app connects directly to the Mac API |
| Physical device | <Mac LAN IP>:<Rockxy port> | <Mac LAN IP>:43210 if the demo API is bound to a reachable address |
When the Flutter app explicitly routes through Rockxy, Rockxy opens the upstream connection. That means the API URL can be Mac-local for the desktop path. When you compare direct behavior from an emulator or device, the API host has to be reachable from that runtime too.
Case 0: capture a known-good HTTP request
Problem. You cannot debug advanced failures until you know the Flutter client is actually sending traffic through Rockxy.
Run in the sample. Select Known-good app startup and run the selected scenario. Start with the bootstrap step.
Use in Rockxy.
- Start capture before pressing Run Scenario in the Flutter sample.
- Filter the traffic table by
/rockxy-demo/bootstrapor by the run ID shown in the sample. - Open the captured row and keep the inspector on the request overview first.
- Check the method, full URL, query string, status code, response time, request headers, response headers, and response body.
- Switch between the request and response body views so you know Rockxy is capturing both sides of the exchange, not only the URL.
Expected evidence.
- The table has one GET row for
http://127.0.0.1:43210/rockxy-demo/bootstrapwhen you run the macOS path. - The request headers include
X-Rockxy-Lab: flutter-sample,X-Rockxy-Scenario-Id: app-startup,X-Rockxy-Step-Id: bootstrap, and a uniqueX-Rockxy-Lab-Run-Id. - The response status is 200 and the response body is JSON from the local demo API, not an HTML error page, proxy error, or empty body.
- The timing is small and stable on localhost; if it is already slow here, fix the baseline before moving to latency or timeout cases.
If it fails. If no row appears, do not debug backend code yet. Check that Rockxy capture is running, the active proxy port was copied correctly, the sample proxy toggle is enabled, and the selected runtime matches where Flutter is running.
Case 1: debug localhost and runtime confusion
Problem. localhost is not universal. On macOS it means the Mac. Inside Android Emulator, the route back to the Mac is usually 10.0.2.2. On a physical device, the Mac must be reachable by LAN IP.
Run in the sample. In Known-good app startup, run the runtime-diagnostic step. Then repeat the scenario on another runtime if you have one available.
Use in Rockxy.
- Filter by
X-Rockxy-Step-Id: runtime-diagnosticor search for/rockxy-demo/runtime-diagnostic. - Open the row and inspect the request URL host, port, and query string.
- Open request headers and find
X-Rockxy-Runtime; compare it with the runtime selected in the sample UI. - Compare the captured destination with the proxy host displayed by the sample summary. Do not mix up the upstream API host with the Rockxy proxy host.
- Repeat the same case after changing the runtime selector only when the Flutter app is actually running in that runtime.
Expected evidence.
- On macOS desktop and iOS Simulator, the runtime header should be
local-apple-runtimeand the local API path should resolve through127.0.0.1:43210. - On Android Emulator, the runtime header should be
android-emulator, and the proxy route back to the Mac should use10.0.2.2:<Rockxy port>. - On a physical device, the runtime header should be
physical-device, and both the proxy host and any directly reached local API host must use the Mac LAN IP, not127.0.0.1. - If Rockxy captures the row, the request reached Rockxy. That proves proxy routing, but it does not by itself prove which Flutter target emitted it; the correlation headers provide that clue.
If it fails. If Android Emulator times out while macOS works, suspect runtime routing first. If a physical device cannot connect, check Wi-Fi proxy settings, Mac firewall, same-network access, and whether the demo API is bound to a reachable interface.
Case 2: reduce noise with filters and Allow List
Problem. A real macOS capture quickly fills with browser tabs, package managers, simulator traffic, update checks, and app telemetry. If you inspect everything, you debug nothing.
Run in the sample. Run Run All once and copy the active run ID from the sample UI.
Use in Rockxy.
- Run all sample scenarios once and copy the run ID shown in the sample result area.
- Paste the run ID into Rockxy search or create a header filter for
X-Rockxy-Lab-Run-Id. - Add a second filter for
X-Rockxy-Scenario-Idwhen you want one scenario, such asauth-recoveryorcheckout-contract. - Use status filters when you want to isolate failures such as 401, 409, and 500.
- Open Allow List and add the demo API host or URL pattern when background traffic makes the capture noisy.
- Disable or relax the Allow List again before you test third-party HTTPS or device traffic, otherwise you may hide traffic you are trying to prove.
Expected evidence.
- Filtering by run ID should leave only requests from that sample run, across all scenario groups.
- Filtering by scenario ID should reduce the view to a small, ordered set of rows that matches the sample timeline.
- Status filtering should immediately reveal the intentional failures: 401 for expired token, 409 for checkout mismatch, and 500 for server error.
- Allow List should reduce captured/displayed noise; it should not be described as a blocking mechanism. Use Block List later when the goal is to fail traffic intentionally.
If it fails. If Allow List hides more than expected, verify the host or pattern. Also remember the current model: Allow List focuses what Rockxy captures and displays; Block List is the feature for intentionally blocking or dropping traffic.
Case 3: inspect HTTPS traffic
For Flutter HTTPS debugging, two things have to be true. The runtime must trust Rockxy’s certificate path, and the target host must be inside Rockxy’s SSL Proxying scope. Global SSL Proxying by itself is not the whole story: include rules, exclude rules, bypass domains, certificate rejection, and app-level certificate pinning can all keep traffic tunneled.
Run in the sample. Use the custom probe field with an HTTPS URL you are allowed to inspect, or point the demo base URL at your own HTTPS dev server. Keep the sample’s debug certificate bypass enabled only for learning. For real debugging, prefer platform certificate trust.
Use in Rockxy.
- Open the certificate setup flow and install Rockxy’s CA for the runtime you are testing.
- On iOS Simulator or a physical iOS device, complete the trust step in system settings. On Android debug builds, make sure the app trusts user CAs through debug-only network security configuration.
- Open SSL Proxying and add an include rule for the exact HTTPS host you want to inspect.
- Confirm the host is not excluded or bypassed by another SSL Proxying rule.
- Run the sample custom HTTPS probe or your own HTTPS development endpoint.
- Open the captured row and check whether Rockxy shows decrypted request and response content rather than only a CONNECT tunnel.
Expected evidence.
- The table should show the HTTPS request as an inspectable HTTP transaction for the included host.
- The inspector should show request headers beyond the CONNECT metadata, including the sample correlation headers when the request comes from the Flutter sample.
- The response headers and body should be readable when the content type is text or JSON and the server sends a body.
- If you see only a tunnel, certificate warnings, or an empty decrypted view, the evidence points to certificate trust, SSL include/exclude scope, bypass rules, or app-level pinning.
If it fails. Check certificate trust, SSL Proxying include/exclude rules, bypass domains, app-level certificate pinning, Android debug CA trust, and whether the request is actually going through Rockxy. Do not treat debug certificate bypass code as production guidance.
Case 4: catch the wrong backend environment
Problem. The app says it is in staging, but the client is calling a production-marked endpoint or host.
Run in the sample. Select Wrong backend environment and run production-config. The sample sends X-App-Environment: production while asking the API to compare it with the expected staging environment.
Use in Rockxy.
- Run the scenario once without rules and open the
production-configrow. - Inspect the request URL, query parameter
expected, and headerX-App-Environment. - Create a Map Remote rule that matches the wrong host, scheme, port, path, or query pattern used by the client.
- Set the target to the intended local, staging, or debug endpoint. Preserve the Host header only if your backend requires it.
- Run the scenario again and compare the before-rule and after-rule rows side by side.
Expected evidence.
- Before mapping, the request should clearly expose the wrong environment marker, such as
X-App-Environment: production. - After mapping, Rockxy should show that the same Flutter request was routed to the intended upstream without editing Flutter code.
- The response body should no longer report the original environment mismatch if the target endpoint is correct.
- If the rule does not fire, the mismatch is usually in the match pattern, host casing, path prefix, query matching, or rule ordering.
If it fails. Check rule matching order and host/path matching. Use Map Remote for host and environment rewrites. Use scripting later for body/header mutation or dynamic mocks, not as the primary cross-host routing feature.
Case 5: diagnose an auth-token problem
Problem. The profile screen returns 401, but app logs only say “unauthorized.” You need to know whether the token was missing, expired, malformed, or not refreshed.
Run in the sample. Select Expired token and retry. It runs three steps: expired-token, refresh-token, and profile-retry.
Use in Rockxy.
- Filter by
X-Rockxy-Scenario-Id: auth-recovery. - Open the
expired-tokenrequest and inspect the auth-related request headers. - Open the 401 response body and note the backend’s error code or message.
- Open the
refresh-tokenPOST and verify the request body and response token shape. - Open
profile-retryand compare its auth header with the first profile call. - If you want to test a fix without app changes, add a Modify Header rule or use a Breakpoint to edit the live request before it reaches the backend.
Expected evidence.
- The first profile row should have status 401 and a request header carrying the sample’s expired-token marker.
- The refresh row should be POST, should include the fake refresh body, and should return a token-shaped JSON response without real secrets.
- The retry row should have status 200 and the valid-token marker in the request headers.
- If a Rockxy header edit makes the request succeed, the evidence points at Flutter auth state or interceptor logic, not a backend outage.
If it fails. If the refresh succeeds but the retry still sends the old token, the Flutter client or Dio interceptor likely has a state/update bug. If Rockxy’s header modification proves the request succeeds, the backend is probably not the first suspect.
Case 6: inspect and replay a bad checkout body
Problem. Checkout fails with a backend mismatch. The app sent a POST body, but the UI does not make it obvious which field is wrong.
Run in the sample. Select Checkout body and response contract and run the checkout-mismatch step. The sample intentionally sends expectedTotal: 59.50 while the backend expects a different value.
Use in Rockxy.
- Filter by
X-Rockxy-Step-Id: checkout-mismatch. - Open the POST request and inspect the request body. Confirm
cartId,paymentMethod, andexpectedTotal. - Open the response body and read the mismatch details returned with status 409.
- Use Replay, Edit-and-Repeat, or Compose to duplicate the request.
- Edit only the bad value first, such as changing
expectedTotalto the backend’s expected amount. - Send the edited request and compare the replay result with the original captured row.
Expected evidence.
- The original captured request should be POST and should include a JSON body, not an empty body or form-encoded surprise.
- The response should be 409 with fields that explain the mismatch, such as expected amount, received amount, cart ID, or validation reason.
- The edited replay should change only the request body field you intended to test. If many fields change, you no longer have a clean experiment.
- If the edited request succeeds, the strongest evidence points to Flutter-side total calculation or request serialization. If it still fails, inspect backend validation or contract assumptions.
If it fails. If the edited replay succeeds, the Flutter calculation or serialized body is suspect. If the edited replay still fails, inspect backend contract assumptions, currency precision, idempotency, and server-side validation.
Current behavior to know. Rockxy sends replay and compose requests directly from the app and bypasses Rockxy’s own proxy to avoid loops. Treat the replay result panel as the source of truth for that retry rather than expecting it to automatically become another ordinary live capture row.
Case 7: replace a bad backend response with Map Local
Problem. The product endpoint returns a shape the Flutter UI does not expect. You need to know whether the UI can render the documented contract before you wait for a backend fix.
Run in the sample. In Checkout body and response contract, run the shape-mismatch step. It returns a deliberately malformed product payload.
Use in Rockxy.
- Run
shape-mismatchonce with no mapping and inspect the original response body. - Create a Map Local rule that matches
/rockxy-demo/schema-mismatch. - Choose a deterministic fixture from the sample, for example
fixtures/rockxy_demo/products.json. - Run the same sample step again with the Map Local rule enabled.
- Compare the response body before and after the rule, then check the Flutter UI or sample result timeline.
Expected evidence.
- The before-rule response should contain the deliberately malformed product shape from the demo server.
- The after-rule response should match the selected fixture file byte-for-byte or structurally, depending on how the inspector formats JSON.
- The request URL and correlation headers should stay the same; only the response source should change.
- If the fixture fixes the app behavior, the backend response shape is the likely issue. If the app still fails on the fixture, investigate Flutter parsing and UI rendering.
If it fails. If the fixture is not returned, check the rule pattern and whether a previous rule consumed the request. If the fixture returns and the UI still breaks, the Flutter parsing or rendering path needs investigation.
Case 8: block optional traffic safely
Problem. Optional analytics, experimentation, or logging calls should not break checkout. You need to test that failure path without taking a real service down.
Run in the sample. Select Slow and failed network calls and run the analytics step.
Use in Rockxy.
- Run
analyticsonce without blocking and confirm it returns 202. - Create a Block List rule for
/rockxy-demo/analyticsor the demo API host plus analytics path. - Choose 403 when you want a normal HTTP failure response.
- Choose drop when you want to test connection failure, timeout, or transport error handling.
- Run the analytics step again and watch both the Rockxy row and the Flutter sample result.
Expected evidence.
- The before-rule request should return 202, proving the endpoint works before you inject failure.
- The blocked run should show the rule effect clearly: either a 403 response path or a dropped/failed connection path.
- The sample should surface the analytics failure as optional. In a real app, checkout or the primary screen should continue.
- If the primary flow fails because analytics failed, the evidence points to a Flutter fallback bug or an accidental hard dependency.
If it fails. If checkout or the primary UI fails because analytics failed, the app has an error-boundary or optional-dependency bug. That is exactly the kind of behavior this case is designed to expose.
Case 9: test timeout behavior with latency
Problem. A screen works on fast Wi-Fi but fails or becomes confusing on slow networks.
Run in the sample. In Slow and failed network calls, run slow-checkout, then repeat it with Network Conditions enabled for the matching host or path.
Use in Rockxy.
- Run
slow-checkoutonce without Network Conditions and note the baseline duration in Rockxy. - Create or enable a Network Conditions rule that matches
/rockxy-demo/delay/2or the demo API host. - Set a latency value that is large enough to be obvious but still below or near the sample timeout.
- Run the step again and compare Rockxy timing with the sample result duration.
- Increase latency past the app timeout when you want to validate timeout UI and retry behavior.
Expected evidence.
- The baseline row should already include the demo server’s intentional delay.
- The latency-enabled row should take longer by roughly the configured latency amount, allowing for normal scheduling overhead.
- The Flutter sample should either complete successfully inside its timeout or fail with a controlled timeout error rather than hanging forever.
- If Rockxy timing increases but the UI does not reflect loading, retry, or timeout state correctly, the issue is likely in app UX state handling.
Current limitation. Network Conditions currently adds latency only. It is not a full offline, packet loss, bandwidth, DNS failure, or radio-condition simulator.
Case 10: filter and triage server failures
Problem. A backend returns 500 and the app only shows a generic failure message.
Run in the sample. In Slow and failed network calls, run server-error.
Use in Rockxy.
- Run
server-errorand filter the table by status code 500. - If the table has other 500s, add
X-Rockxy-Step-Id: server-erroror search for/rockxy-demo/status/500. - Open the row and inspect response headers, especially
Content-Typeand any request ID or trace headers. - Inspect the response body and copy the backend error code or message into your bug report.
- Compare timing with successful nearby calls to see whether the failure is fast validation, slow backend work, or transport delay.
Expected evidence.
- The row should show HTTP 500, not a client-side timeout, proxy connection error, or certificate failure.
- The response body should contain the demo API’s structured error payload.
- The request should still include the run ID, scenario ID, step ID, client kind, runtime, and request ID headers.
- A good bug report from this case includes the exact URL, status, response body, request ID, and timing shown in Rockxy.
If it fails. If the app labels every non-200 response the same way, the issue may be user-facing error handling rather than transport. If Rockxy shows an empty body where the server claims JSON, inspect the server response contract.
Case 11: use scripting for a dynamic mock
Problem. Static mapping is not enough. You want to mutate an experiment response based on request headers and compare app behavior without changing the backend.
Run in the sample. Select Scripted response mutation and run pricing-experiment. It sends X-Experiment-Bucket: control.
Use in Rockxy.
- Run
pricing-experimentonce without scripting and inspect the original response body. - Create a script that matches
/rockxy-demo/pricing-experiment. - Enable response execution for the script. Keep request execution off unless you are also testing request mutation.
- Add a guard for
X-Rockxy-Scenario-Id: scripted-mockso the script does not mutate unrelated traffic. - Mutate the response body and add a console log or visible marker while validating the script.
- Run the same sample step again and compare the Flutter result with the original response.
Rockxy’s current script template uses this shape:
function onResponse(context, url, request, response) {
if (request.headers["X-Rockxy-Scenario-Id"] !== "scripted-mock") {
return response;
}
var body = response.body;
body.experiment.bucket = "treatment";
body.experiment.paywall = "discount";
body.experiment.discountPercent = 20;
response.headers["Content-Type"] = "application/json";
response.body = body;
return response;
}
Expected evidence.
- Without the script, the response should show the control experiment bucket from the demo API.
- With the script enabled, the Flutter client should receive the treatment bucket, discount paywall, and modified discount value.
- The script should only affect rows whose scenario header is
scripted-mock. - The scripting console or visible response marker should make it clear that the mutation came from Rockxy, not from a backend code change.
- If the response inspector display differs from what the client receives, treat that as a product behavior to verify before documenting it more strongly.
If it fails. Keep the script isolated from Map Local, Block List, and Map Remote rules that target the same request. If another rule already handles the request, the script may not be the part of the pipeline you are testing.
Case 12: repeat the workflow with Dio
Problem. Many Flutter production apps use Dio interceptors for auth, retry, headers, logging, and error mapping. A workflow that only works with HttpClient is not enough.
Run in the sample. Change the client selector from Dart HttpClient to Dio 5. Re-run Expired token and retry, then Checkout body and response contract.
Use in Rockxy.
- Run the same scenario once with Dart
HttpClientand once with Dio 5. - Filter both runs by scenario ID, then compare rows with the same step ID.
- Inspect
X-Rockxy-Clientto confirm which client produced each row. - Compare request headers, body serialization, timeout behavior, retry behavior, and response handling.
- Use Replay or Compose only after you have identified a specific difference to test.
Expected evidence.
- The same scenario and step IDs should appear in both runs, giving you a clean client-to-client comparison.
- The Dio run should include
X-Rockxy-Client: dio-5. - If Dio adds, removes, or formats headers differently, Rockxy should show that difference directly in the request inspector.
- If the Dio path fails while
HttpClientworks, the strongest suspects are Dio adapter setup, proxy configuration, interceptor ordering, timeout values, or certificate handling.
If it fails. Suspect client adapter setup, proxy configuration, interceptor ordering, timeout configuration, or certificate handling. This is especially important for Flutter Dio proxy debugging because the app’s network stack may not inherit the system proxy automatically.
Decision table: what the evidence means
| Evidence in Rockxy | Most likely area to inspect |
|---|---|
| No row appears | Flutter proxy setup, runtime host, Rockxy port, capture state, network reachability |
| Row appears but HTTPS body is not readable | Certificate trust, SSL Proxying include rules, bypass domains, app certificate pinning |
| Wrong host, scheme, path, or environment | Flutter config, environment loader, Map Remote rule |
| 401 with stale token | Auth interceptor, token refresh state, request header mutation |
| 409 after POST body mismatch | Flutter calculation, JSON serialization, backend validation contract |
| Map Local fixture fixes the UI | Backend response shape or environment data mismatch |
| Block List breaks a noncritical flow | Flutter fallback handling and optional dependency boundaries |
| Replay succeeds after editing one value | Client-side request construction or retry path |
Common mistakes while following the workbook
- Putting the demo API port into the Rockxy proxy port field.
- Using
10.0.2.2while the app is running on macOS instead of inside Android Emulator. - Installing a certificate but forgetting the SSL Proxying include rule for the HTTPS host.
- Trusting a user CA in Android settings while the app’s debug network-security-config still rejects user CAs.
- Assuming Allow List blocks traffic. It focuses capture; Block List blocks.
- Assuming latency presets also simulate packet loss, bandwidth, DNS failure, or offline mode.
- Shipping debug certificate bypass callbacks in release builds.