Every one of us has been here.
The designer opens Figma and changes the primary color from #6C63FF to #5B52E8. A small update (they think).
Our release cycle: create a branch, update the color, build, test, submit to App Store, wait for store review, for just a hex code.
This is the tax Flutter developers pay for a compile-time theming system. It's powerful and type-safe and beautiful — and completely frozen the moment your app ships.
flutter_skin
flutter_skin is a runtime skin engine. Instead of hardcoding your color values, you define them as named tokens. Those tokens are managed from a dashboard and delivered to your app.
When you publish a new skin, every connected device updates instantly.
How it works in 2 lines
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlutterSkin.init(apiKey: 'fsk_your_key_here');
runApp(const MyApp());
}
// In your MaterialApp:
theme: FlutterSkin.toThemeData()
That's the integration. FlutterSkin.init() fetches the active skin from the FSkin backend and opens a persistent SSE connection. From that point, any skin you publish reaches the app in about 2 seconds.
The live update architecture
Here's what happens end to end when you click Publish:
Dashboard: click Publish
↓
Skin marked active in PostgreSQL
↓
Supabase Realtime detects the UPDATE on skins table
↓
Node.js backend receives the Realtime event
↓
Backend writes SSE event to all connected Flutter clients
↓
flutter_skin package receives the flag
↓
Package re-fetches active skin from CDN
↓
A stream emits new SkinTokens
↓
setState() → MaterialApp rebuilds → app repaints
Total time: ~1–2 seconds. No polling. No App Store. No user action.
The full MaterialApp setup
To react to live updates, wrap your app in a StatefulWidget:
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
FlutterSkin.onSkinChanged.listen((_) {
if (mounted) setState(() {});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: FlutterSkin.toThemeData(),
home: const HomePage(),
);
}
}
What's available today
The alpha supports color tokens — full Material ColorScheme mapping:
-
primary,secondary,background,surface,error - All the
on*counterparts -
brightnessfor light/dark
The dashboard (app.fskin.dev) gives you:
- Project management
- Skin editor with JSON view
- Team collaboration with role-based permissions
- API key management (auto-generated per project)
- Publishing with confirmation + version history
A lot of cool features still coming
Links
- 📦 Package: pub.dev/packages/flutter_skin
- 🖥️ Dashboard: app.fskin.dev
- 📖 Docs: docs.fskin.dev
- 🐙 GitHub: github.com/koukibadr/flutter_skin
It's still on alpha track.
I'd love feedback from anyone who tries it — bug reports, feature requests.
don't forget to thumbs up our package on pub.dev 🥸 👀.
Top comments (2)
Cool project — SSE instead of polling makes sense here. I work a lot with offline-first apps, so my first question is the offline case: if the app is offline when a new skin drops, do you cache the last one, and how do you handle a stale cached skin meeting a newer one on reconnect? That's usually the tricky part.
Hello Ahmed, Indeed for offline apps there will be an offline handling by caching the last theme
for now with the current alpha version when the app goes offline it will show's your app default theme or fallback theme (I added the fallbackTheme attribute)
-> with the upcoming updates apps will handle cached versions of skins
for the conflict resolving between old stale skins and newer ones, we're implementing versionning already + currently the project already pushes the last skin as the one all apps should use