Icon
Icon
2
Last Updates
Icon
New Project
Rebranding Swift Sports with bold visuals and hyper speed interactions.
Mar 12
New Project
Showcasing our latest work for Noire, a brand that breathes fresh life.
Mar 12
New Post
Exploring pixel art aesthetics and code for modern digital experiences.
Mar 12
New Project
A look behind the scenes of the photo shoot that defined a luxury scent.
Mar 12
New Post
Read how our team stays productive and inspired anywhere on the globe.
Mar 12
New Post
Pushing furniture design outside its natural habitat for a unique view.
Mar 12
View all
View all
Icon
Explore our pages
Icon
Home
[01]
About
[02]
Cases
[03]
News
[04]
Contact
[05]
Contact
[05]
Office
149 Zelena St

Lviv, Lviv Oblast 79035
Ukraine

Social
Instagram
LinkedIn
Clutch
Medium

Return to Native Shores: From Flutter and Dart Back to Android and Kotlin

Category:
Articles
Date:
May 13, 2025

First of all, a few words about my experience.

‍

«I’ve been working in software development for more than ten years, and for most of that time I’ve worked as a native Android developer. I started working with Android 4.x versions and Java, and witnessed (and encouraged) transition from Java to Kotlin.»

Artem Kleinschmidt

‍

I like Kotlin. In fact, I absolutely love it.

‍

But one day, as a naturally curious person, I started learning Flutter.

‍

There were some things I liked, and some things I found weird. But to fully appreciate a technology’s strengths and gain a better understanding of its weaknesses, you have to work with it full-time on a real project.

‍

So, over time, I received an offer to work on a Flutter project, which I gratefully accepted. And then, after a year and a half, the project was mostly put on hold, so I switched back to native Android development.

‍

In this article, I’ll share my thoughts on moving away from native development and then returning to it.

‍

Part 1: Getting to Know a Stranger.

‍

Dart

‍

Learning Flutter was a great mental exercise for me.

‍

First of all, the Dart language felt awkward.

‍

After familiarizing myself with the syntax, my thought was “Wow, this looks like a weird cousin of Java and Kotlin”.

‍

With my background in Java and Kotlin, it was really easy to pick up Dart. At the same time, some features felt new and unique.

‍

However, many aspects felt off.

‍

Semicolons? Seriously — are we back in 2010?

‍

Also, the absence of an equivalent to Kotlin’s data classes felt awful. Just look at how you create the simplest data class in Kotlin versus in Dart!

‍

data class BiometricCredentials(
    val username: String,
    val password: String,
    val encryptTimestamp: Long,
    val isExpired: Boolean = false,
)

‍

class BiometricCredentials {
  final String username;
  final String password;
  final int encryptTimestamp;
  final bool isExpired;

  const BiometricCredentials({
    required this.username,
    required this.password,
    required this.encryptTimestamp,
    this.isExpired = false,
  });

  BiometricCredentials copyWith({
    String? username,
    String? password,
    int? encryptTimestamp,
    bool? isExpired,
  }) {
    return BiometricCredentials(
      username: username ?? this.username,
      password: password ?? this.password,
      encryptTimestamp: encryptTimestamp ?? this.encryptTimestamp,
      isExpired: isExpired ?? this.isExpired,
    );
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;

    return other is BiometricCredentials &&
      other.username == username &&
      other.password == password &&
      other.encryptTimestamp == encryptTimestamp &&
      other.isExpired == isExpired;
  }

  @override
  int get hashCode {
    return username.hashCode ^
           password.hashCode ^
           encryptTimestamp.hashCode ^
           isExpired.hashCode;
  }

  @override
  String toString() {
    return 'BiometricCredentials(username: $username, password: $password, encryptTimestamp: $encryptTimestamp, isExpired: $isExpired)';
  }
}

‍

‍

This felt like a major downgrade compared to Kotlin and reminded me of the good old Java 7/8.

‍

There are other things that are just different.

‍

For instance, Mixins are an interesting concept, and they’re used extensively in the Flutter framework.

‍

What bothered me most was the absence of very basic class modifiers.

‍

Prior to Dart 3, you had Classes, Mixins, and mysterious implicit interfaces.

‍

But with Dart 3, we get most of the goodies from Kotlin: abstract classes, interfaces, final classes, and sealed classes!

‍

This is a decent set, but the protected modifier is still missing. That’s a bummer, but I can live with it.

‍

Another interesting thing is the way we work with asynchronous code execution. There are two tools: Futures and Isolates.

‍

Futures are asynchronous, but run on the same thread, while Isolates concurrently on a separate threads.

‍

Okay, this is straightforward. I’ve also found them a bit easier to use compared to Kotlin coroutines, and much, much easier than vanilla Java multithreading.

‍

Then, there are also improvements compared to Java (though not to Kotlin).

‍

Modern Dart supports null safety! That’s really nice. I’m spoiled by Kotlin, and going back to code without null safety would be a nightmare.

‍

But here, the implementation is very similar to Kotlin’s, so that’s a win.

Flutter

‍

Then we have the Flutter framework.

‍

This was completely new and mind-blowing to my XML-infected brain.

‍

Sure, Compose has been around for a long time, and I played with it a bit back then. Most of my experience, however, was with the good old XML-based UI.

‍

Of course, UI toolkits have changed a lot over the years.

‍

First, we had the ConstraintLayout component and data binding. Then the Android team realized that data binding might be overkill, so view binding was introduced.

‍

And now we have Compose, but at the time it was not widely adopted.

‍

So as an Android developer, I knew several ways to build UI, but they all had XML at their core.

‍

Google actively pushed the Layout Editor to work with ConstraintLayout, but I never liked it — it was easier to edit the XML directly.

‍

So learning to build UI the Flutter way was truly mind-blowing for me.

‍

In Flutter, almost everything is a Widget. A screen is a Widget. A list is a Widget. Adding padding also means wrapping your widget in a padding widget.

‍

To make anything clickable, you need, you guessed it, a widget.

‍

There are stateless and stateful widgets, and stateful widgets have their own life cycle, which has nothing to do with your activity/fragment lifecycle.

‍

It was fascinating to learn because the approach is completely different compared to what I knew.

‍

Although it took some time to relearn how to implement simple things, it paid off.

‍

Once you know the basics, building UI becomes really easy.

‍

Another thing I like about Flutter is its animations framework. It provides straightforward tools for basic animations and more sophisticated APIs for complex effects.

‍

Before Flutter, I hated animations and avoided them at all costs. But with Flutter, I finally learned to love them.

Building an App

‍

Much like native Android apps, Flutter apps rely heavily on code generation.

‍

To generate code, you use a tool called build runner.

‍

Here’s another difference compared to native development:

‍

You must use a lot of console commands to build your app.

‍

«With native development, I’ve become accustomed to a fully automated app build process (when it works). You click one button/shortcut and all the compilation steps run one by one, and the app launches.»

Artem Kleinschmidt

‍

With Flutter, you run console commands to synchronize dependencies, generate code, and finally build the app.

‍

A normal way to build and run an app might look like this:

‍

flutter pub get
flutter pub run build_runner build
flutter run

‍

You can run the build_runner watch command to automate code generation, but it has its own flaws.

‍

Overall, this build flow felt like a major downgrade to me.

‍

Hot Reload and Previews

‍

There are no widget previews in Flutter, unfortunately.

‍

But the hot reload feature actually works, so you can easily see your changes on an emulator.

‍

Communicating with the Platform

‍

Flutter provides the ability to communicate with native code.

‍

It’s not rocket science, but if you do it, you have to write code in at least three languages: Dart, Kotlin, and Swift.

‍

That’s definitely a hassle.

‍

In Summary

‍

These are my initial impressions of Flutter: many aspects felt off, and many felt just plain weird.

‍

But now, let’s look at how my thoughts on Flutter have evolved over time.

Part 2: Becoming Friends with Flutter

‍

Dart

‍

Well, Dart 3 and up is a solid programming language.

‍

It provides tools to write clean code. You can solve any problem with it.

‍

I still don’t like the fact that there’s no protected modifier. On one hand, there’s a @protected annotation, but it’s not the same as a built-in keyword. On the other, it makes you apply the composition over inheritance principle more often, which can be beneficial.

‍

But come on — give me the option, and I’ll decide when to use a protected modifier and when not to.

‍

Another concern I had is performance.

‍

You often write asynchronous code in Dart using Futures, which can give the false impression of parallel execution. In fact, they run on the same thread as UI updates, which could be problematic.

‍

But in reality, this rarely causes issues. Dart and Flutter are both well optimized, so in most cases using Futures won’t impact performance.

‍

If you need to run truly heavy work, you can launch an Isolate, which runs on a separate thread under the hood.

‍

Then there are semicolons — the simplest thing to get used to. In fact, when I returned to Kotlin, I had to relearn not to use them.

‍

The absence of the data classes is a bummer. But you can work around this by using libraries such as freezed, or a combination of equatable and copy_with_extension. Also, the addition of data classes is one of the most requested features, and the Dart team is aware of it, so it may be added in the future.

‍

Flutter

‍

I’ve fallen in love with it! Once you get used to it and learn its features and quirks, it becomes a powerful tool.

‍

Writing a class or two (for stateful widgets) is still more verbose than writing composable functions. However, I got used to it, and Android Studio offers handy shortcuts to generate both stateless and stateful widgets.

‍

You can also try this library that lets you define widgets as functions instead of classes.

‍

«Also, as I mentioned earlier, there are many tools for creating animations of varying complexity, and developers often enjoy using them — sometimes proactively suggesting animations to managers or clients!»

Artem Kleinschmidt

‍

Hot Reload and Previews

‍

The absence of previews felt strange only at first.

‍

I quickly learned to use hot reload to verify UI changes, and this workflow is as good as having previews — and sometimes even better, since you can test changes immediately with real data.

‍

Communicating with the Platform

‍

It can be fine. It can be bad.

‍

Passing simple parameters from native code to Flutter is straightforward.

‍

But, if you frequently communicate with native-side code, you should ask yourself, if you need a cross-platform app at all.

‍

However, we encountered a nasty issue when embedding native views into Flutter.

‍

We added videos using a library that leveraged native views under the hood.

‍

It worked fine on static screens, but when we implemented video previews in a news-feed–style screen, performance was atrocious.

‍

So we had to put the feature on hold until we figured out a workaround. Other libs might have worked better, but the fact we faced such a problem was already pretty bad.

‍

Ecosystem

‍

Flutter has a very strong and active community.

‍

pub.dev is a great portal for discovering Dart and Flutter libraries.

‍

Some things there are really impressive — like this chat library that lets you build chat UI super easily.

‍

In Summary

‍

Flutter is great: it has its pros and cons, but overall it delivers on its promises. It allows you to build an app for multiple platforms using a single codebase.

‍

Of course, it’s not all sunshine and rainbows. Sometimes you’ll need to implement Flutter method channels to communicate with the native code. Sometimes you’ll face weird platform-specific bugs. And you’ll always need to test the app separately on each supported platform.

‍

One important consideration is the business side of things. Flutter enables you to deliver an app for multiple platforms with a smaller team. It also provides less obvious organizational advantages — more on that in the next part.

‍

Part 3. Returning to the Native Shores.

‍

All good things come to an end, so now I’m back to native Android development.

‍

So, how does it feel to be a native Android developer after an amazing Flutter journey? Well, I can look at it with somewhat fresh eyes now. So, let’s see how Android development has evolved over the past few years.

‍

Kotlin

‍

Kotlin is a mature language, so there aren’t many changes compared to what I used before. It’s still a great, modern language.

‍

A few things I’m noticing now:

‍

  • It’s still less verbose than Dart.
  • Absence of semicolons is nice, but not a big deal.
  • Built‑in delegation support is neat (class delegation, property delegation).
  • Data classes are great!
  • Functions such as apply, let, and also are handy, but they can make code unreadable if overused.
  • Why is there no simple ternary operator? Yes, you can use if/else instead, but the classic syntax is a bit more concise.

‍

Android SDK

‍

Compose has broader adoption now, which is great.

‍

I’m still undecided about which I like more — Flutter or Compose — but at their core they are quite similar.

‍

Both utilize declarative UI, although their implementations differ. I can’t say one is better than the other; both are modern, effective tools for building UI.

‍

Somewhere in Between

‍

Apps are stuck in an in‑between state again.

‍

There was a long transitional period when apps were migrating from Java to Kotlin. It might have been easy to migrate small apps or build new ones using Kotlin. But with large, established projects, it was hard — you were writing new code in Kotlin while the old codebase was still in Java.

‍

Over time, you refactored the old code from Java to Kotlin. But then there was legacy code no one wanted to touch, and it could have remained in Java for years.

‍

Over time, Kotlin was adopted as the standard, so now most apps are Kotlin‑only.

‍

So we should be out of that transitional period, right? Not exactly.

‍

Now we’re in a second transitional period. We’re transitioning from vanilla XML to Compose. It’s easier to do while maintaining existing Fragment-based navigation, so this is the approach developers are tempted to use.

‍

Compose Navigation exists, of course, but I believe it will take years to retire XML, phase out Fragments, and fully transition to Compose.

‍

Building an App

‍

Clean Build → Invalidate Caches and Restart.

‍

This is a mantra every Android developer knows. And it’s still part of the routine!

‍

With Flutter, there are also situations where you need to clean the build to clear a random error, but in my experience this doesn’t happen as often. Especially when it comes to Android Studio caches.

‍

KSP

‍

Ah, finally, my current project no longer uses kapt. All the major libraries have made the necessary changes, so we can rely solely on KSP.

‍

The new tool is expected to be better — faster and more stable.

‍

And it may indeed be faster. But I’m not so sure about its stability.

‍

Every now and then, I have to disable KSP incremental compilation to eliminate build errors, which is far from ideal.

‍

Hot Reload and Previews

‍

Hot Reload (a.k.a. “Apply Changes”) doesn’t work in my current project. In fact, ever since it was introduced, it has always been problematic and buggy. It still is.

‍

Compose does offer a handy @Preview annotation for your composables, so that’s something.

‍

Still, rebuilding and restarting the app every time I make a change is far from perfect.

‍

Organizational Challenges

‍

This is big. This is much bigger than I thought back when I had no Flutter production experience.

‍

Of course, questions like “Why does it work differently compared to the iOS app?” are annoying, but I hadn’t realized how much time and effort goes into synchronizing the development of two separate apps.

‍

Clients, owners, and managers usually want iOS and Android apps to be as aligned as possible. But with native mobile development, it’s its own challenge.

‍

There are two development teams. Separate developers work on the same features for different platforms, either simultaneously or at different times.

‍

Each clarifies requirements with managers or business analysts. Each communicates with QA engineers. Each produces and then fixes bugs.

‍

«Sometimes the behavior differs simply because it was implemented in the most straightforward way for that platform. And that simplest way may differ between Android and iOS. Sometimes it’s just a bug on one of the platforms.»

Artem Kleinschmidt

‍

So your QA team has noticed this inconsistency. What’s next?

‍

First, a clarification is needed. You need to set up a call or create a thread in Slack to discuss the problem.

‍

Then there are two ways to address the inconsistency.

‍

First, is to change behavior on one platform to match another one. Sometimes it’s easy to do. Sometimes it’s hard. In any case, it’s an additional task.

‍

The second option is to leave it as is. Sometimes that’s easier than changing the logic just to align with the other app. But then you need to document the behavioral difference, and QA engineers must take it into account every time they test. Not perfect either.

‍

With Flutter, similar issues can still occur. In most cases, though, it’s limited to UI differences caused by platform specifics. Because you have a single codebase, the logic is reused on both platforms, so behavior diverges far less often.

‍

I’m talking about Flutter, but the same applies to other cross‑platform tools (React Native, I’m looking at you).

‍

Having a cross‑platform team means the code is largely reused across platforms. It also means you can work with a smaller team.

‍

With cross‑platform development, you have one developer working on a single implementation of a feature. There is one implementation that can be adjusted for different platforms if needed. When bugs, clarifications, or behavior changes arise, you only have to discuss and implement them once — not twice.

‍

‍

Part 4: Summary

‍

Android SDK and Flutter both have pros and cons. Both have active communities. Both let you build a modern mobile app.

‍

Which one is better depends on the specific app. If the app relies heavily on platform‑specific APIs, it makes little sense to use a cross‑platform framework.

‍

For most apps, though, Flutter (or another cross‑platform framework) is the way to go. Let’s be honest — the global economy is struggling, and it’s unclear when things will improve. There are a lot of layoffs in the industry. The world is changing, and the ability to save time and money with a single team working on a cross‑platform app is a benefit that’s hard to ignore.

Found this valuable? Share it forward

Related News

Newsoft at London Tech Week 2026
Events
June 10, 2026
Newsoft at FIBO 2026
Events
April 19, 2026

All news

Subscribe
to our newsletter

You've successfully subscribed
Oops! Something went wrong while submitting the form.
Stay tuned for updates and news coming soon...
PAGES
Home
Home
About us
About us
Our Cases
Our Cases
News
News
Contact
Contact
cases
MedPal AI
MedPal AI
LYMA
LYMA
Underdog
Underdog
Virgin Active
Virgin Active
Socials
Instagram
Instagram
LinkedIn
LinkedIn
Clutch
Clutch
Medium
Medium
NEWSOFT, INC 19 - 26©

We collaborate with ambitious brands and people.

Privacy Policy

Logo
All News