Today’s work on codefrog in flutter for windows
Sign up for the mailing list to be notified when the Windows version drops.
https://codefrog.app

Today’s work on codefrog in flutter for windows
Sign up for the mailing list to be notified when the Windows version drops.
https://codefrog.app

My year of development in 2025. I had a job up until May. Currently working on codefrog.app for Windows and searching for work and people to develop stuff with.
I created a lot of sites and apps using AI (Cursor, CodeRabbit, AugmentCode, Antigravity, Amp)
march
3dtankbattle.com
3dwebgames.com
may
longevity.greenrobot.com
mentalhealthlawyers.greenrobot.com
remotedevjobs.greenrobot.com
june
robots.greenrobot.com
september
gunstopperdrone.com
game.gunstopperdrone.com
december
codefrog.app
github.com/greenrobotllc/bio-neighbor
Notable accomplishment: Getting CodeFrog.app approved on the App Store and being able to use open-source programs installed on the computer within the app while still sandboxed by utilizing SSH on the localhost. Thank you, Apple, for approving it! I am so grateful!
Unfortunate setback: My loving, kind, good watchdog, Albert, got cancer, and we have an appointment with radiation upcoming in January.
As a developer who has built a released application in Flutter, I’ve recently started exploring Kotlin Multiplatform (KMP) for a new project. After building codefrog.app – a macOS developer tool in Flutter – I’ve been evaluating KMP for similar functionality. Even in the early stages, the differences in how these frameworks handle asynchronous programming are immediately apparent. This article explores why KMP’s async programming model appears superior, especially for native platform integration.
Flutter uses Isolates for concurrent execution. Isolates are separate memory spaces that communicate via message passing – essentially separate processes that can’t share memory.
// Flutter isolate example
import 'dart:isolate';
void isolateFunction(SendPort sendPort) {
// Heavy computation
sendPort.send(result);
}
void main() async {
ReceivePort receivePort = ReceivePort();
await Isolate.spawn(isolateFunction, receivePort.sendPort);
var result = await receivePort.first;
}
Key Limitations:
KMP uses Kotlin Coroutines – lightweight threads that can suspend and resume without blocking.
// KMP coroutine example
suspend fun fetchData(): Data {
return withContext(Dispatchers.IO) {
// Network call - suspends, doesn't block
api.getData()
}
}
// Usage
viewModelScope.launch {
val data = fetchData() // Seamless async/await
updateUI(data)
}
Key Advantages:
While building codefrog.app, I discovered that isolates cannot access secure storage APIs on macOS. This became a significant issue when porting to Windows, where the app would hang – a problem that doesn’t occur on macOS. I’m still working through the multithreading issues on Windows, which has proven to be very time-consuming.
// This DOESN'T work in an isolate
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
void isolateFunction() {
final storage = FlutterSecureStorage();
// ❌ CRASHES: Secure storage requires main isolate
await storage.write(key: 'token', value: 'secret');
}
Why This Happens:
I discocered the macOS app had no problems doing everything on the main thread not using isoaltes, but when trying to port to Windows it hung, meaning I would have to use isolates on Windows in order to release a Windows Version using Flutter.
In KMP, coroutines can access secure storage directly because they share the same memory space and thread context.
// This WORKS perfectly in a coroutine
suspend fun saveCredentials(token: String) {
withContext(Dispatchers.Main) {
// Direct Keychain access - no restrictions
keychain.save("token", token)
}
}
// Can be called from any coroutine
viewModelScope.launch {
saveCredentials("secret") // Works everywhere!
}
Why This Works:
Dispatchers.Main gives access to UI and platform APIsmacOS sandboxed apps have strict security requirements. Flutter isolates face additional challenges:
// Sandboxed app trying to access file system from isolate
void isolateFunction() async {
// ❌ May fail: Isolates don't inherit sandbox entitlements properly
final file = File('/path/to/file');
await file.writeAsString('data');
}
Issues:
KMP coroutines work seamlessly with macOS sandboxing:
// Direct platform API access with proper sandbox support
suspend fun saveFile(data: String) {
withContext(Dispatchers.Main) {
// Full sandbox support - uses app's entitlements
val panel = NSSavePanel()
if (panel.runModal() == .OK) {
data.writeToFile(panel.url.path)
}
}
}
Advantages:
One of the most painful experiences building codefrog.app was implementing File → New Window. Flutter’s architecture requires a separate engine instance for each window.
// Flutter: Creating a new window
void createNewWindow() {
// ❌ Must create entire new Flutter engine
final engine = FlutterEngine();
await engine.run(); // Expensive initialization
// ❌ Black screen while engine initializes (1-2 seconds)
final window = await engine.createWindow();
// ❌ Each window has separate memory footprint
// ❌ No shared state between windows
}
Problems:
Real Experience from codefrog.app:
In KMP with SwiftUI, window creation is trivial and instant:
// KMP/SwiftUI: Creating a new window
func openNewWindow() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 800, height: 600),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.contentView = NSHostingView(rootView: ContentView())
window.makeKeyAndOrderFront(nil)
// ✅ Instant - no engine initialization
// ✅ No black screen
// ✅ Shared framework instance
}
Advantages:
Real Experience:
Flutter uses Dart’s Future and Stream for async operations:
// Flutter async
Future<List<Data>> fetchData() async {
final response = await http.get(url);
return parseData(response.body);
}
// Stream for reactive updates
Stream<Progress> generateProgress() async* {
yield Progress.started;
await Future.delayed(Duration(seconds: 1));
yield Progress.processing(50);
yield Progress.completed;
}
Issues:
KMP uses Kotlin Coroutines and Flow:
// KMP async
suspend fun fetchData(): List<Data> {
return withContext(Dispatchers.IO) {
val response = client.get(url)
parseData(response.body)
}
}
// Flow for reactive updates
fun generateProgress(): Flow<Progress> = flow {
emit(Progress.Started)
delay(1000)
emit(Progress.Processing(50))
emit(Progress.Completed)
}
Advantages:
Flutter:
KMP:
Flutter:
KMP:
Flutter:
KMP:
Building codefrog.app in Flutter revealed several pain points:
While just getting started with KMP, the initial experience shows promise:
Based on my experience building production apps in Flutter and early exploration of KMP, KMP’s async programming model appears significantly better for native platform integration:
Choose Flutter if:
Choose KMP if:
As someone who has shipped production apps in Flutter and is now exploring KMP, KMP’s async programming model appears to be the clear winner for desktop applications requiring native platform integration. The coroutine model provides better performance, simpler code, and full platform access – things that Flutter’s isolate model simply cannot match.
For codefrog.app and similar developer tools, KMP’s advantages in window management, secure storage access, and overall performance make it an attractive choice. Based on early exploration, it seems the days of waiting 1-2 seconds for new windows and working around isolate limitations could be over.
I’m still in the early stages of my KMP journey, but the initial signs are very promising. The async programming model alone makes it worth serious consideration for any desktop application requiring deep platform integration.
This article is based on real-world experience building codefrog.app in Flutter and early exploration of Kotlin Multiplatform. Performance numbers and observations are from actual Flutter development and initial KMP testing.
This article explores how to use Kotlin’s asynchronous programming features in Kotlin Multiplatform (KMP) applications. We’ll cover Coroutines, Flow, async/await, and how they bridge to Swift and other platforms. This guide is based on a real-world implementation in a slideshow generation app.
Coroutines are Kotlin’s way of writing asynchronous, non-blocking code. Think of them as lightweight threads that can be suspended and resumed.
Key Concepts:
import kotlinx.coroutines.*
// Launch a coroutine
fun main() = runBlocking {
launch {
delay(1000L) // Suspend for 1 second
println("World!")
}
println("Hello,")
}
// Output: Hello, World!
launch: Fire-and-forget, returns a Jobasync: Returns a Deferred (like a Future)runBlocking: Blocks the current thread (use sparingly)coroutineScope: Creates a scope for structured concurrencysuspend fun fetchData() = coroutineScope {
val data1 = async { fetchFromSource1() }
val data2 = async { fetchFromSource2() }
// Both run concurrently, await when needed
val result1 = data1.await()
val result2 = data2.await()
combineResults(result1, result2)
}
A suspend function can be paused and resumed. It can only be called from a coroutine or another suspend function.
suspend fun fetchUser(id: String): User {
delay(1000) // Simulate network call
return User(id, "John Doe")
}
Kotlin suspend functions automatically bridge to Swift async functions:
Kotlin:
suspend fun createSlideshow(topic: String): Slideshow {
// ... implementation
}
Swift:
let slideshow = try await kmpBridge.createSlideshow(topic: "Travel Tips")
The Kotlin compiler generates the necessary bridging code automatically!
Flow is Kotlin’s answer to reactive streams (like RxJava’s Observable). It’s a cold stream that emits values over time.
Key Characteristics:
| Feature | Flow | RxJava Observable | Swift AsyncSequence |
|---|---|---|---|
| Hot/Cold | Cold by default | Hot by default | Cold |
| Threading | Coroutine-based | Thread-based | Async/await |
| Backpressure | Automatic | Manual | Automatic |
| Cancellation | Coroutine cancellation | Subscription | Task cancellation |
import kotlinx.coroutines.flow.*
fun numbersFlow(): Flow<Int> = flow {
for (i in 1..5) {
delay(100)
emit(i) // Emit a value
}
}
suspend fun collectNumbers() {
numbersFlow().collect { number ->
println(number) // Prints 1, 2, 3, 4, 5
}
}
Flow provides many operators similar to RxJava:
flowOf(1, 2, 3, 4, 5)
.map { it * 2 } // Transform
.filter { it > 5 } // Filter
.take(2) // Take first 2
.collect { println(it) } // Collect: 6, 8
val stateFlow = MutableStateFlow(0)
// Collect updates
stateFlow.collect { value ->
println("Current value: $value")
}
// Update value
stateFlow.value = 1 // Triggers collection
Kotlin suspend functions bridge seamlessly to Swift:
Kotlin:
suspend fun searchImages(query: String): List<ImageResult>
Swift:
let images = try await kmpBridge.searchImages(query: "nature")
Flow doesn’t bridge directly, but Flow.collect is a suspend function:
Kotlin:
fun generateProgress(): Flow<Progress> = flow {
emit(Progress.Started)
delay(1000)
emit(Progress.Processing(50))
delay(1000)
emit(Progress.Completed)
}
Swift Bridge:
func generateProgress() -> AsyncThrowingStream<ProgressModel, Error> {
return AsyncThrowingStream { continuation in
Task {
do {
let flow = appModule.useCase.generateProgress()
try await flow.collect { progress in
let model = progress.toSwiftModel()
continuation.yield(model)
if progress is Progress.Completed {
continuation.finish()
}
}
} catch {
continuation.finish(throwing: error)
}
}
}
}
Swift Usage:
for try await progress in kmpBridge.generateProgress() {
updateUI(with: progress)
}
Let’s see how we use these concepts in our slideshow app:
// Repository layer
interface ImageRepository {
suspend fun searchImages(query: String): List<ImageResult>
}
// Use case layer
class CreateSlideshowUseCase(
private val imageRepository: ImageRepository
) {
suspend fun createFromTopic(topic: String): Slideshow {
// Sequential execution
val images = imageRepository.searchImages(topic)
val music = musicRepository.suggestMusic(topic)
return Slideshow(
title = topic,
slides = images.map { createSlide(it) },
music = music
)
}
}
suspend fun createFromTopic(topic: String): Slideshow = coroutineScope {
// Launch multiple operations concurrently
val imagesDeferred = async { imageRepository.searchImages(topic) }
val musicDeferred = async { musicRepository.suggestMusic(topic) }
val slideContentDeferred = async { aiRepository.generateSlides(topic) }
// Await all results
val images = imagesDeferred.await()
val music = musicDeferred.await()
val slideContent = slideContentDeferred.await()
// Combine results
Slideshow(
title = topic,
slides = combineSlides(images, slideContent),
music = music
)
}
fun createFromTopicWithProgress(topic: String): Flow<GenerationProgress> = flow {
// Emit progress updates as work progresses
emit(GenerationProgress.GeneratingSlides(0, 5))
val slideContents = aiRepository.generateStructuredSlides(topic, 5)
emit(GenerationProgress.GeneratingSlides(5, 5))
slideContents.forEachIndexed { index, content ->
emit(GenerationProgress.GeneratingImages(index + 1, 5, content.title))
val image = imageRepository.generateImage(content.imageKeywords.first())
// ... create slide
// Emit progress for each slide
}
emit(GenerationProgress.Completed(slideshow))
}
class SlideshowViewModel: ObservableObject {
@Published var generationProgress: GenerationProgressModel?
func createSlideshow(topic: String) async {
let progressStream = kmpBridge.createSlideshowWithProgress(
topic: topic,
numberOfSlides: 5
)
// Iterate over the stream reactively
for try await progress in progressStream {
await MainActor.run {
self.generationProgress = progress
if case .completed(let slideshow) = progress {
self.currentSlideshow = slideshow
}
}
}
}
}
struct ProgressView: View {
@ObservedObject var viewModel: SlideshowViewModel
var body: some View {
if let progress = viewModel.generationProgress {
ProgressView(value: progress.progress) {
Text(progress.message)
}
}
}
}
Use Kotlin Flow when:
Use RxJava when:
Use Swift AsyncSequence when:
| Aspect | Flow | RxJava | AsyncSequence |
|---|---|---|---|
| Platform | Kotlin Multiplatform | JVM/Android | Swift |
| Concurrency Model | Coroutines | Threads | async/await |
| Hot/Cold | Cold | Hot (default) | Cold |
| Backpressure | Automatic | Manual | Automatic |
| Cancellation | Coroutine scope | Subscription | Task |
| Learning Curve | Medium | Steep | Easy (if you know async/await) |
Kotlin Flow:
flowOf(1, 2, 3)
.map { it * 2 }
.collect { println(it) }
RxJava:
Observable.just(1, 2, 3)
.map { it * 2 }
.subscribe { println(it) }
Swift AsyncSequence:
let sequence = AsyncStream { continuation in
for i in 1...3 {
continuation.yield(i * 2)
}
continuation.finish()
}
for await value in sequence {
print(value)
}
✅ Good:
suspend fun processData() = coroutineScope {
val result1 = async { fetchData1() }
val result2 = async { fetchData2() }
combineResults(result1.await(), result2.await())
}
❌ Bad:
suspend fun processData() {
GlobalScope.launch { /* ... */ } // Don't use GlobalScope!
}
fun dataFlow(): Flow<Result<Data>> = flow {
try {
emit(Result.success(fetchData()))
} catch (e: Exception) {
emit(Result.failure(e))
}
}
✅ Use Flow:
fun progressUpdates(): Flow<Progress> = flow {
emit(Progress.Started)
emit(Progress.Processing(50))
emit(Progress.Completed)
}
❌ Don’t use suspend function:
suspend fun getProgress(): Progress // Can only return one value
// ✅ Good: Use AsyncThrowingStream
func getProgress() -> AsyncThrowingStream<ProgressModel, Error> {
return AsyncThrowingStream { continuation in
Task {
try await flow.collect { progress in
continuation.yield(progress.toSwiftModel())
if progress is Completed {
continuation.finish()
}
}
}
}
}
// ❌ Bad: Don't try to use Flow directly in Swift
// Flow doesn't bridge - use collect instead
for try await progress in progressStream {
await MainActor.run {
// Update UI properties here
self.progress = progress
}
}
// In a ViewModel or similar
private var generationJob: Job? = null
fun startGeneration() {
generationJob = viewModelScope.launch {
flow.collect { /* ... */ }
}
}
fun cancelGeneration() {
generationJob?.cancel()
}
In our slideshow app:
This creates a reactive, non-blocking user experience where the UI updates in real-time as work progresses.
This article was written based on implementation in a real Kotlin Multiplatform slideshow generation application.
I am reviewing my project’s code warning errors coming from CodeRabbit GitHub review comments in CodeFrog, an app I developed to speed up development and improve software correctness with comprehensive security, accessibility, seo, and more types of testing. It helps to have a single text file containing all issues to quickly scan for problematic issues or incorrect/ambiguous comments that need manual intervention.
CodeFrog generates this file of PR comment AI summaries for you with a few clicks using the GitHub API, pulling from CodeRabbit’s comments on your code. After all complex issues are resolved you can import the simple ones into Cursor by copying and pasting the contents of the file into Cursor or Antigravity or whatever AI coder you use.
View more about CodeFrog, my upcoming developer tool at https://codefrog.app.

A business idea I had was to build a more secure Mailchimp:
What do you think? I thought it’d be cool to make a company around secure tools for the web. Next is a blog, WordPress alt that is secure by default and has an ‘A’ rating from CodeFrog.app mega report on accessibility, security, seo, URL preview, meta tags validation, html validation. My WordPress blog, by default, has a D rating. An opportunity to create something new that doesn’t have the WordPress baggage, because some of the items in the security test (security headers) that are failing are very hard to fix by installing additional plugins and stuff, and I still couldn’t get it to work. Also, accessibility and SEO, and loading times should be fixed by default by generating static files that are correct.

Interested in helping me build this or invest in this? Contact me at andy@greenrobot.com
Having a lot of fun with macos flutter development. I’ve created two modules so far for making macos specific stuff work. One for drag and drop and one for doing native menu state enabling and disabling stuff dependent on what window and screen is active, like sometimes save is greyed out if not viewing a file that can be saved. It ends up being a pretty complicated system and after awhile debugging I decided to make it into a library. Finally got it working. I think AI works better when appropriately using and creating modules instead of a 3k line appdelegate file. Once I get CodeFrog into the Apple store I plan to open source these modules I created so it takes less time for other devs who want to have mac native features in Flutter like drag and drop and dynamic menus.
Do you have a need for a Flutter macOS expert? I’m open to work!
Happy Thanksgiving from Maryland.

A B Rating From CodeFrog
While building CodeFrog’s landing page, we integrated Mailchimp for newsletter signups. However, our security scans revealed a critical issue that prevents us from achieving an A rating: Mailchimp’s validation script violates Content Security Policy (CSP) requirements.
Mailchimp’s mc-validate.js script injects inline styles dynamically, which requires the 'unsafe-inline' directive in our CSP style-src policy. This is a security anti-pattern because:
'unsafe-inline' allows any inline styles, defeating the purpose of CSP protection against XSS attacksWhen we removed 'unsafe-inline' to improve security, the browser console shows:
Applying inline style violates the following Content Security Policy directive:
`style-src 'self' 'nonce-...'`. The action has been blocked.
This error originates from mc-validate.js:164, confirming that Mailchimp’s script requires unsafe inline styles to function.
Content Security Policy is a critical security feature that helps prevent:
By requiring 'unsafe-inline', Mailchimp forces us to weaken our security posture, which is unacceptable for a security-focused tool like CodeFrog.
We’re looking for a newsletter service that:
✅ Free tier (or very affordable) for up to 500 subscribers
✅ CSP-compliant – doesn’t require 'unsafe-inline'
✅ Secure by default – supports nonces or external stylesheets
✅ Easy integration – simple embed or API
✅ Reliable – good deliverability and uptime
We’re reaching out to the developer community for recommendations. If you know of a newsletter service that:
Please share your suggestions! We’re particularly interested in:
Good news! We found a solution that doesn’t require 'unsafe-inline'. When we removed 'unsafe-inline' from our CSP, the browser console error message actually provided the answer:
Applying inline style violates the following Content Security Policy directive:
'style-src 'self' 'nonce-...''. Either the 'unsafe-inline' keyword, a hash
('sha256-iIHQ0a6ntSSQhfMwBwjvXvp+zrKJldURld+iiblnEKo='), or a nonce
('nonce-...') is required to enable inline execution.
The browser helpfully suggested using a hash for the specific inline style instead of allowing all inline styles. This is a much better security approach!
Instead of using 'unsafe-inline' in our CSP style-src directive, we now use:
style-src 'self' 'nonce-{style_nonce}' 'sha256-iIHQ0a6ntSSQhfMwBwjvXvp+zrKJldURld+iiblnEKo='
This hash is specific to the inline style that Mailchimp’s mc-validate.js script injects. By using the hash, we:
✅ Allow only that specific style – not arbitrary inline styles
✅ Maintain strict CSP – no 'unsafe-inline' directive
✅ Pass security scans – scanners don’t flag specific hashes
✅ Keep Mailchimp working – the validation script functions correctly
Using a hash is more secure than 'unsafe-inline' because:
While we’ve solved the CSP style-src issue, there’s still one security concern:
⚠️ SRI (Subresource Integrity) Missing: Mailchimp’s script is loaded from their S3 bucket without an integrity attribute. This means we can’t verify the script hasn’t been tampered with. However, this is a known limitation because:
crossorigin="anonymous" would break script loading due to CORS issuesThis is a MEDIUM severity issue that prevents a perfect A+ rating, but it’s an acceptable trade-off given the constraints.
As a security-focused developer tool, CodeFrog needs to maintain the highest security standards. We’ve successfully resolved the CSP 'unsafe-inline' issue by using a hash-based approach, which is more secure and CSP-compliant.
The remaining SRI issue is a known limitation with third-party scripts that don’t support CORS, and we’ve documented it appropriately. We’re now much closer to that A security rating!
Update: We solved the CSP issue using a hash-based approach! The browser’s error message provided the exact hash we needed. Mailchimp now works with strict CSP without requiring 'unsafe-inline'.
Update by Editor: Reviewing this post I found it said it was maintainable to have a hash value that gets updated if MailChimp changes their scripts. This doesn’t seem very maintainable to me. I have to manually check for errors, or build an automated test that checks and then changes(?) the hash. I would still want to review it I think. I may take off the hash value and reduce the codefrog.app mega report rating since it seems dumb to have to keep it updated. Another option is to build an automated test that does it. Or wait for some ideas from someone else? Maybe there’s a way to use a REST api for Mailchimp which would be better. It would be nicer if it was just secure by default though.
Accessibility is not a feature—it’s a fundamental requirement for modern web and mobile applications. Over 1 billion people worldwide live with disabilities, and many rely on assistive technologies to navigate digital products. Beyond the moral imperative, accessible applications reach broader audiences, improve SEO rankings, and help organizations comply with legal standards like WCAG 2.1 and the Americans with Disabilities Act (ADA).
However, accessibility testing remains challenging for many development teams. Manual testing is time-consuming and error-prone, while developers often lack the expertise to identify subtle accessibility violations. This is where automated testing tools become invaluable.
Developers frequently encounter accessibility barriers that go unnoticed during standard testing:
These issues compound, creating frustrating experiences for users with disabilities while exposing organizations to legal liability.
CodeFrog integrates axe-core, the industry-leading automated accessibility testing engine, directly into your development workflow. This powerful integration enables developers to catch accessibility violations early—during development, not after deployment.
With CodeFrog’s Web Testing feature, you can:
Whether you’re testing a development server running on localhost:3000, a staging environment, a production URL, or an entire site via sitemap, CodeFrog brings accessibility testing directly into your workflow.
axe-core performs comprehensive automated testing across multiple accessibility standards:
alt attributes on imagesfor attributes on labelsShift-Left Testing: Catch accessibility issues before code review, reducing remediation costs and timeline pressure.
Developer Education: Detailed violation reports help your team understand why issues matter and how to fix them, building accessibility expertise across the organization.
Continuous Improvement: Integrate accessibility testing into your CI/CD pipeline to prevent regressions.
Compliance Confidence: Demonstrate accessibility commitment to stakeholders, customers, and regulators.
Inclusive Products: Build applications that work for everyone, expanding your user base and market reach.
CodeFrog makes accessibility testing accessible to developers of all experience levels. Whether you’re building a new feature or auditing an existing application, the Web Testing feature provides actionable insights to improve your digital products.
Start testing today and join the movement toward truly inclusive web development.
CodeFrog: Empowering developers to build accessible, inclusive applications.