| name | refactor-swift-to-flutter |
| description | Convert existing Swift/iOS code to Flutter/Dart. Transforms SwiftUI views, Combine publishers, URLSession APIs, and Core Data models to equivalent Flutter patterns (Riverpod, GoRouter, Dio, Hive/SQLite). Use when user has Swift code they want to port to Flutter, mentions "porting iOS to Android", "migrate Swift to Dart", or asks to "convert this Swift code".
|
Refactor Swift to Flutter
Overview
Transform Swift/iOS codebases into Flutter equivalents with equivalent functionality, architecture, and patterns.
When to Use
- User wants to port an existing iOS app to Flutter
- User asks to "convert Swift to Flutter"
- User has Swift code and wants equivalent Dart
- User mentions "porting iOS to Android"
- User asks to "rewrite this in Flutter"
Swift to Dart Mapping
Data Types
| Swift | Dart |
|---|
String | String |
Int | int |
Double | double |
Bool | bool |
[Type] | List<Type> |
[Key: Value] | Map<Key, Value> |
Optional<Type>? | Type? |
enum | enum (same) |
struct | class (with final fields) |
class | class |
protocol | abstract class or mixin |
Common Conversions
struct User {
let id: UUID
var name: String
var email: String?
}
// Dart
class User {
final String id;
final String name;
final String? email;
User({required this.id, required this.name, this.email});
}
SwiftUI to Flutter Widgets
struct ContentView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
// Flutter
class ContentView extends StatefulWidget {
const ContentView({super.key});
@override
State<ContentView> createState() => _ContentViewState();
}
class _ContentViewState extends State<ContentView> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Count: $_count'),
ElevatedButton(
onPressed: () => setState(() => _count++),
child: const Text('Increment'),
),
],
);
}
}
Combine to Riverpod
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
func fetchUser() {
isLoading = true
networkService.fetchUser()
.receive(on: DispatchQueue.main)
.sink { user in
self.user = user
self.isLoading = false
}
}
}
// Dart Riverpod
class UserNotifier extends StateNotifier<AsyncValue<User?>> {
final NetworkService _networkService;
UserNotifier(this._networkService) : super(const AsyncValue.data(null));
Future<void> fetchUser() async {
state = const AsyncValue.loading();
try {
final user = await _networkService.fetchUser();
state = AsyncValue.data(user);
} catch (e, st) {
state = AsyncValue.error(e, st);
}
}
}
final userProvider = StateNotifierProvider<UserNotifier, AsyncValue<User?>>((ref) {
return UserNotifier(ref.read(networkServiceProvider));
});
URLSession to Dio
class NetworkService {
func fetch<T: Decodable>(_ type: T.Type, from endpoint: String) async throws -> T {
let url = URL(string: "https://api.example.com\(endpoint)")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode(T.self, from: data)
}
}
// Flutter Dio
class NetworkService {
final Dio _dio;
Future<T> fetch<T>(String endpoint) async {
final response = await _dio.get('https://api.example.com$endpoint');
return response.data as T;
}
}
final networkServiceProvider = Provider<NetworkService>((ref) {
return NetworkService(Dio());
});
Core Data to Hive/SQLite
@Entity
class UserEntity {
@Attribute(.unique) var id: UUID
var name: String
var email: String
}
// Dart Hive
@HiveType(typeId: 0)
class UserModel extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
@HiveField(2)
String? email;
}
Conversion Process
- Analyze Swift code - Identify architecture (MVVM, VIPER), patterns used
- Map dependencies - Find Swift-only libraries and find Flutter equivalents
- Convert data models - Transform Swift structs/classes to Dart
- Convert views - SwiftUI -> Flutter widgets
- Convert state management - Combine -> Riverpod
- Convert networking - URLSession -> Dio
- Convert persistence - Core Data -> Hive/SQLite
- Test equivalence - Ensure same behavior
Best Practices
- Preserve original logic and behavior
- Use Dart/Flutter idioms, don't just translate literally
- Use freezed for data classes
- Use Riverpod for state (not setState)
- Use GoRouter for navigation
- Handle async/await properly
- Preserve error handling patterns
- Test both implementations for parity