NO SUS: a Flutter web + Android privacy app I built, and three Flutter things that bit me

Hi all, I’m Shubham, an engineering student in Mumbai. For the last few months I’ve been building NO SUS, a Flutter app (web plus Android) for sharing documents and notes you don’t want sticking around. I’m the only developer, it’s Alpha and free right now, and the client is MIT on GitHub. So this is a maker post, and I’m mostly here for feedback.

What’s shipped:

  • Burn Notes / Burn Files: encrypted in the browser with AES-256 (CTR for notes, CBC for files), no account on either side, files up to 25 MB, expiry 1h/24h/7d, and it opens once, then it’s gone. The key lives in the URL fragment.
  • Go: open your Saved items on a borrowed PC (cyber cafe, college lab) by scanning a QR with the phone app, matching a 2-digit code and approving with fingerprint or face. The PC never gets your Google password or token.
  • SecureSend: each viewer’s identity is watermarked on the document, with view limits, expiry, a view log and revocation.
  • On Android, screenshots and screen recording are blocked with OS flags. On web that isn’t possible, so it’s touch-to-reveal blur plus the watermark: deterrence and attribution, not prevention.

Honest limit: SecureSend and group files are not end-to-end encrypted. They rely on Supabase RLS policies.

Stack: Flutter, Riverpod 3, supabase_flutter (Postgres + RLS, Deno edge functions), encrypt/pointycastle for the crypto, local_auth for the biometric step, pdfrx for viewing.

Three Flutter-specific things I learned the hard way:

  1. Android App Links can’t match on the URL fragment. Every link the app mints looks like https://<host>/#/burn/..., so the autoVerify intent filter can’t be path-scoped. Once the app is installed it catches every link to that host. If the native side doesn’t recognise a link shape, the link just lands on the home screen. There’s no browser fallback, because Android already chose the app. I ended up with one routing function that has to know every link shape the app can create, mirroring the Uri.base handling on web, plus tests that pin the old formats so links already out there keep working.

  2. ref.watch(provider.select(...)) silently does nothing if your models don’t override ==/hashCode. Every list emission creates new instances, so the selected value never compares equal and you rebuild anyway. There’s no error, the optimisation just turns into a no-op. I only noticed because a group detail screen rebuilt whenever any group changed.

  3. Multi-file burn shares: each file gets its own key/IV, and the encrypt → upload → confirm pipeline for each file runs in parallel with Future.wait, with the AES work in compute(). On mobile that keeps the UI responsive. On web, compute() doesn’t get a separate isolate, so the heavy part still competes with the UI there.

Feedback I’d like:

  • Has anyone moved heavy crypto off the main thread in Flutter web cleanly (a Web Worker, wasm, anything)? That’s the part I’d most like ideas on.
  • Is there a better answer to the fragment + App Links problem than one big router that knows every link shape?
  • If you try a burn link on web, does the flow make sense the first time?

Site: https://nosus.foo
Code: GitHub - https-shubhamsahu/NON_SUS · GitHub

Dealing with files on web was always a tough thing due to the differences in how browser uses files and flutter via dart io which is not supported on the web…I would implement the file functionalities with web APIS and use dart js_interop to communicate with flutter, this way you can even offload the work to a web worker.

We all got burned by the equality part, objects cannot be compared properly if they dont override hashcode and equality, always do that for state objects