Start with the uncomfortable part, because everything else follows from it. Any string compiled into your app is public. Not “hard to find” — public. An APK is a zip file; strings on the extracted binary takes seconds. --dart-define values, constants, obfuscated names, base64-encoded blobs: all of it is recoverable by anyone motivated enough to download your app once. This is not a Flutter weakness. It is true of every client application on every platform. What it changes is where you draw the line between “the app knows this” and “the app can ask for this”. The line Kind of secret Where it belongs Third-party API key with billing attached Server only. The app calls your backend, your backend calls them Public/publishable keys (Stripe publishable, Firebase config, Maps key) In the app — they are designed for it, and restricted server-side User session token Device secure storage, short-lived, refreshable Refresh token Device secure storage, revoc...
The notification arrives. Sometimes the app shows it, sometimes the system does, sometimes tapping it opens the right screen and sometimes it dumps the user on the home page. The behaviour feels random until you see the structure underneath: the same message takes four different paths depending on what the app was doing. App state Who displays it Which handler runs Foreground Nobody, by default onMessage Background The OS onBackgroundMessage (separate isolate) Terminated The OS onBackgroundMessage (separate isolate) Opened by tapping — onMessageOpenedApp , or getInitialMessage if it was terminated Every notification bug I have debugged was one of those rows being unhandled. Wire all four and most of the mystery disappears. Notification messages versus data messages Before the code, the distinction that determines everything: an FCM payload can contain a notification block, a data block, or both. notification present — the OS displays it automa...