DEV Community

Cover image for Flutwire 1.0: a network inspector for Flutter that opens from the notification shade
AS
AS

Posted on

Flutwire 1.0: a network inspector for Flutter that opens from the notification shade

When an app misbehaves on a phone, the call that explains it is usually out of reach. The console shows only what a logger was told to print. A proxy needs a certificate on the device and a changed Wi-Fi setting. Every HTTP client in the app has to be wired by hand before it says anything, and when the app is killed the evidence goes with it.

Flutwire is my answer to that. It records the calls an app makes, keeps them on the device and lists the latest ones in the notification shade. Tapping the notification opens the inspector, and each call opens in full: URL, timings, headers, cookies, and the request and response bodies as a JSON tree, indented text, form fields, an image or a hex dump. Version 1.0 is out.

flutter pub add flutwire
Enter fullscreen mode Exit fullscreen mode
Future<void> main() async {
  await Flutwire.init();
  runApp(const MyApp());
}
Enter fullscreen mode Exit fullscreen mode

That is the whole integration. From then on every call that goes through dart:io is recorded, including the ones made by packages I did not write: package:http, Dio's default adapter, Chopper, Image.network. A debug build needs that one line, and a release build records nothing.

The notification in the shade, the list of calls, a JSON body as a tree and the search across headers and bodies

What it does once it has the calls

The history is written to disk as soon as a call ends, so it survives the app being killed. Tap the notification afterwards and the inspector opens on what was read back. Search looks at URLs, status codes, GraphQL operation names, headers and the text of bodies, so finding where an id came from is one query.

Version 0.2 went beyond recording. A call can be edited and sent again from the same screen. A WebSocket or an event stream lists its messages as they arrive. Two calls can be set side by side with each difference marked where it happens. The statistics give the error rate, the median and the 95th percentile, the slowest calls and a timeline. The inspector can dock beside the app on a wide screen and under it on a phone, so the app and its traffic are in view together. Calls made in other isolates reach the history too, through Flutwire.run and Flutwire.compute, and Protobuf and MessagePack bodies are readable.

Two calls side by side, the statistics, the inspector docked under the app, and the marks that mocks and breakpoints leave on the list

Mocks and breakpoints are the one place where Flutwire changes traffic, so they stay off until you turn them on with FlutwireConfig(interception: true). A rule matches an address. A mock answers with a stored response and sends nothing. A breakpoint holds the request while the app waits, and you can send it on, change it first, or stop it.

Everything else only reads. Flutwire does not proxy other apps, and it cannot see what never passes through Dart, such as the native Firestore SDK. When a call may have gone unseen, because a client does not use dart:io or because something replaced the capture, the inspector says so above the list and says what to change. Authorization and cookie values are masked when a call is recorded, so they never reach memory, disk, a notification or an export.

For the places dart:io does not reach there are three small packages: flutwire_dio, flutwire_http and flutwire_cloud_functions, which records Firebase callable functions. On the web, Windows and Linux, where there is no notification, a small floating button opens the inspector.

What 1.0 means

The version number is a promise now, and I wrote it down in STABILITY.md. The four packages are released together. A breaking change needs a new major version, and CI compares each package with its latest release on pub.dev and fails when the API breaks without one. A deprecated member stays for at least two minor releases. The look and wording of the inspector are not part of the promise, and the file says so.

Two things in that file came out of checking the release rather than writing it. The history on disk had no version, and a summary that could not be read was deleted, so an older release opening the history of a newer one would have wiped it. The folder now records a layout number, and a layout this release does not know is left exactly as it is.

The other came from reading Protobuf the way protoc writes it. My tests built their descriptors by hand, so a misreading of the format could have sat on both sides. I generated a descriptor set and messages with protoc and its Python runtime and ran the decoder over them. It read nearly everything correctly. A negative enum value showed as a number, because the descriptor writes it as ten bytes and my reader dropped it, and a float showed 0.10000000149011612 for 0.1. Both are fixed, with tests that fail without the fixes.

I also ran the new screens on an Android emulator, an iOS simulator, macOS and in a browser, and that turned up a few layout problems on narrow screens, which are fixed as well.

What comes next

gRPC capture. gRPC in Dart opens its own sockets, so the HttpClient wrapper cannot see it, and it needs an interceptor for the grpc package.

Flutwire is on pub.dev and the source is on GitHub, under Apache 2.0. If it does not show you a call you expected, or shows it wrongly, an issue with the client you use is the most useful thing you can send.

Top comments (2)

Collapse
 
stefan_07d23f966225156b50 profile image
Stefan •

The protoc-generated fixtures were a good catch. Handwritten test data can agree perfectly with a broken decoder when both make the same assumption. I'd enjoy a follow-up on that part: what passed your original tests, and what the real messages exposed.

Collapse
 
tonytonycoder11 profile image
AS •

Thanks, Stefan. My hand-built descriptor had no negative enum value, so the reader never met the ten bytes protoc writes for one, and my float test used 0.5, which prints the same either way (0.1 came out as 0.10000000149011612). The fixtures and their generator are here: github.com/NaCode-Studios/Flutwire.... I'd like to write the follow-up.