Reading time: 4 minutes
TL;DR: After almost two decades in software development, I’ve worked with Java, Kotlin, and Swift — and for nearly five years now, Flutter. While no tool is perfect, today’s modern frameworks have reignited my joy for building apps thanks to their clean and concise development experience — even if that sometimes means tradeoffs like limited market adoption or native integrations workarounds.
The Evolution of Mobile UI
Legacy (2010s) | Modern Native | Flutter | Kotlin Multiplatform | React Native | |
---|---|---|---|---|---|
Android | RecyclerView, Adapter, XML | Jetpack Compose + Composables | Widgets | Jetpack Compose | React Components |
iOS | UITableView, Delegates, Storyboards/XIBs | SwiftUI + Views | Widgets | SwiftUI / UIKit | React Components |
Paradigm | Imperative, Boilerplate-heavy | Declarative, Reactive | Declarative, Reactive | Shared logic + native UI | Declarative, JS runtime |
Language | Java / Objective-C | Kotlin / Swift | Dart | Kotlin | JavaScript / TypeScript |
UI Units | ViewHolders / UITableViewCells | Composables / Views | Widgets | Native Views | JSX Components |
Setup | Verbose, lifecycle-bound | Concise, reactive composition | Single codebase | Native UI + shared logic | Metro + Bridge + React |
Each ecosystem has matured significantly. But for many devs, Flutter still hits a unique sweet spot: fast iteration, multi-platform, and a beautiful developer experience:
- Modern Native (Jetpack Compose / SwiftUI): Best for platform-specific UX and performance, but still means maintaining two codebases. Continually improving with better tooling, tighter platform integration, and expanding declarative APIs.
- Flutter: Fast, expressive, and multi-platform, but Dart is less familiar to many devs and native integration can be tricky. Rapidly advancing with improvements like Flutter Web, desktop support, better performance through the Impeller rendering engine, and enhanced platform API integration.
- Kotlin Multiplatform: Shares business logic in Kotlin with native UI on each platform; great for Kotlin-heavy teams, but UI needs to be written twice. Growing ecosystem with better tooling and expanded platform targets.
- React Native: Uses JavaScript and a massive ecosystem, but performance and native UI fidelity may fall short for complex apps. React Native’s architecture is evolving with new improvements (Fabric, TurboModules, JSI) beyond the classic Metro + Bridge.
Introduction
I started my journey in Android development over a decade ago. Back then, Java ruled the Android world, and building even a simple UI meant wading through boilerplate code, clunky XML layouts, and the dreaded Adapter pattern.
// Classic RecyclerView Adapter in Android (Java, before Jetpack Compose)
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> data;
public MyAdapter(List<String> data) {
this.data = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.textView.setText(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
}
Then in 2017, Kotlin came along. It was a breath of fresh air — concise, expressive, and modern. I embraced it fully and enjoyed the improvements it brought to Android development.
Around that time, I also started building native iOS apps with Swift. Each platform had its own quirks, strengths, and mental models. While Kotlin and Swift modernized the mobile dev experience, I still found myself context-switching constantly and duplicating logic across platforms.
// Classic UITableView in Swift (before SwiftUI)
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
let items = ["Item 1", "Item 2", "Item 3"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
view.addSubview(tableView)
tableView.frame = view.bounds
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
Real-World Flutter: Simplicity in Action
Fast forward to today, and I’ve been working with Flutter — it as been a game changer for my productivity and enjoyment.
Flutter lets me focus on what truly matters: delivering features and crafting great user experiences — all from a single codebase. While tools like Jetpack Compose, SwiftUI, React Native, and Kotlin Multiplatform have each made strides in improving development, Flutter goes further by eliminating most of the platform-specific overhead and unifying the development model across iOS, Android, web, and beyond.
Here’s a simple example that hit me recently while playing with our app, Sagwee. I was updating a part of the UI where we display a list of items, switching between a ListView
and a Wrap
layout depending on the user’s preference. The entire setup responds dynamically to user choices, powered by our backend and state management logic. Just a few widgets — declarative, intuitive, and responsive by design — made it all work seamlessly.

Sagwee, a Flutter app: https://sagwee.com
Here’s how that plays out in simplified Flutter code:
// Vertical scrolling list.
return ListView.separated(
itemCount: list.length,
itemBuilder: (_, index) => _showItem(ref, list[index]),
separatorBuilder: (_, __) => const Divider(),
);
// Dynamic wrapping layout.
return Wrap(
spacing: defaultSpacing,
runSpacing: defaultSpacing,
children: list.map((item) => _showItem(context, item)).toList(),
);
A Grateful Pause
I’m not here to say Flutter is perfect (no tool is) — I still work with native too. But after more than a decade in mobile development, I’ve come to appreciate tools that get out of the way and let me build.
I’ve realized I’m genuinely happy for Flutter: for its flexibility, the beauty of Dart, and for how it unifies the development experience across platforms.
Reflection
If you’ve wrangled with Android Adapters or juggled multiple native stacks, you probably know what I mean. And if you’re considering trying Flutter, I say: give it a shot. While some might point out that there are currently fewer job openings for Flutter, it might still reignite your love for building.
Bonus: Modern UI Declarations at a Glance
If you’re curious how list UIs are declared in today’s modern solutions, here’s a quick glimpse into some of the leading approaches — all embracing declarative UI, reactive state, and composable views, making development smoother and more enjoyable.
Jetpack Compose (Android)
@Composable
fun ItemList(items: List<String>) {
LazyColumn {
items(items) { item ->
Text(
text = item,
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
)
Divider()
}
}
}
Highlights:
- LazyColumn is Compose’s answer to RecyclerView — optimized and scrollable.
- No more Adapter, ViewHolder, or XML layouts.
- Pure Kotlin, type-safe, and fully declarative.
SwiftUI (iOS)
struct ItemList: View {
let items: [String]
var body: some View {
List(items, id: \.self) { item in
Text(item)
}
}
}
Highlights:
- Declarative and concise by default.
- Clear, composable structure.
- Tight integration with Swift and async workflows.
React Native
import { FlatList, Text, View } from 'react-native';
const ItemList = ({ items }) => (
<FlatList
data={items}
keyExtractor={(item) => item}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Text>{item}</Text>
</View>
)}
ItemSeparatorComponent={() => <View style={{ height: 1, backgroundColor: '#ccc' }} />}
/>
);
Highlights:
- FlatList is optimized for rendering large lists.
- Familiar JSX + JavaScript logic.
- Reusable components and styles.