iOS Roadmap for Android Devs: Basics

Rohit Kumar
5 min readAug 19, 2024

--

As Kotlin Multiplatform Mobile (KMM) gains traction, the demand for developers who can code for both Android and iOS is increasing. This guide will cover the similarities and differences between developing for these platforms. We’ll provide an overview of each topic, with more detailed parts to follow.

System Specific Design

Android apps are developed using a partitioning approach, breaking the app into fragments and activities. An activity represents a single screen in an app, and a project with multiple screens requires managing numerous activities. Each activity contains fragments, which are parts of the user interface used to navigate between activities, input values, or open new screens.

iOS application architecture is based on view controllers. Different types of view controllers, such as page view, tab, and split view controllers, are used in app development. A view controller can manage an entire screen or a portion of it.

OOPS

Both Kotlin and Swift are modern, statically typed programming languages that support the OOP paradigm, including classes, objects, inheritance, polymorphism, encapsulation, and abstraction.

Kotlin

// Classes & Objects
class Person(val name: String)
val person = Person("John")

// Inheritance
open class Animal
class Dog : Animal()

// Polymorphism
open class Animal {
open fun makeSound() {
println("Animal sound")
}
}
class Dog : Animal() {
override fun makeSound() {
println("Bark")
}
}

Swift

// Classes & Objects
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person = Person(name: "John")

// Inheritance
class Animal {}
class Dog: Animal {}

// Polymorphism
class Animal {
func makeSound() {
print("Animal sound")
}
}
class Dog: Animal {
override func makeSound() {
print("Bark")
}
}

Access Modifiers

Notes:

  • Swift lacks a direct equivalent of Kotlin’s protected modifier.
  • Kotlin doesn’t have a direct equivalent of Swift’s file-private; however, the internal level is used within single-file modules for similar behavior.

Null Safety

// KOTLIN
var name: String? = null // nullable type

//SWIFT
var name: String? = nil // optional type
  • Kotlin has built-in null safety to prevent null pointer exceptions, requiring explicit nullability with ?
  • Swift uses optionals to handle nullability, with ? and ! to indicate optional and implicitly unwrapped optional types.

Extensions:

//KOTLIN
fun String.addExclamation() = this + "!"

//SWIFT
extension String {
func addExclamation() -> String {
return self + "!"
}
}

Both languages support extensions to add functionality to existing classes. Syntax is similar but with different keywords.

Views

// SWIFTUI
struct ContentView: View {
@State private var count = 0

var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}

// Jetpack Compose
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }

Column {
Text("Count: $count")
Button(onClick = { count += 1 }) {
Text("Increment")
}
}
}

Both SwiftUI and Jetpack Compose use a declarative syntax to describe the UI, reflecting its state. They offer mechanisms to manage state and update the UI when the state changes.

State Management

State management is a crucial aspect of building responsive and interactive UIs. Both Jetpack Compose (Android) and SwiftUI (iOS) provide mechanisms for managing state within their respective frameworks. Below, we will compare and contrast the state management approaches in both platforms.

In Android, the ViewModel persists through configuration changes like screen rotations. However, in iOS with SwiftUI, there is no direct equivalent to Android’s ViewModel for persisting state through configuration changes. State persistence can be achieved using @StateObject, @ObservedObject, @EnvironmentObject.

Navigation

iOS Navigation: The Coordinator Pattern is commonly used for navigation in multi-module iOS projects, providing a clean separation of concerns by delegating navigation responsibilities to coordinators.

// Feature A Coordinator
class FeatureACoordinator: ObservableObject, Coordinator {
@Published var navigationPath = NavigationPath()
func start() -> some View {
NavigationStack(path: $navigationPath) {
FeatureAView()
.environmentObject(self)
}
}

func navigateToFeatureB() {
navigationPath.append(FeatureBView())
}
}
  • Setup Navigation in Views: Use SwiftUI views and bind them with the coordinators.
struct MainView: View {
@EnvironmentObject var coordinator: MainCoordinator

var body: some View {
VStack {
Text("Main View")
Button("Go to Feature A") {
coordinator.navigateToFeatureA()
}
}
.navigationTitle("Main")
}
}

We can also use Flow Stacks with the Coordinator Pattern in iOS for a multi-module project is an advanced navigation architecture that provides better organization and separation of concerns.

Android Navigation: In Android, navigation is typically handled using the Navigation Component, which provides a framework for handling all navigational interactions in an app, including fragment transactions, deep linking, and more.

// Navigation setup in nav_graph.xml
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
app:startDestination="@id/mainFragment">

<fragment
android:id="@+id/mainFragment"
android:name="com.example.MainFragment"
android:label="Main"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_mainFragment_to_featureAFragment"
app:destination="@id/featureAFragment" />
</fragment>

<fragment
android:id="@+id/featureAFragment"
android:name="com.example.FeatureAFragment"
android:label="Feature A"
tools:layout="@layout/fragment_feature_a" />
</navigation>

// MainFragment.kt
class MainFragment : Fragment(R.layout.fragment_main) {
private val navController by lazy { findNavController() }

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.buttonNavigateToFeatureA).setOnClickListener {
navController.navigate(R.id.action_mainFragment_to_featureAFragment)
}
}
}

// FeatureAFragment.kt
class FeatureAFragment : Fragment(R.layout.fragment_feature_a)

Android Launch Types:

  • Standard: New instance added to the back stack.
  • SingleTop: Reuses top instance if it exists.
  • SingleTask: Brings existing instance to the front, clears others.
  • SingleInstance: Activity runs alone in its task.

These launch types help manage the back stack behavior and ensure the proper navigation flow in complex applications.

Multi Module

  • As Android uses Gradle to manage multi-module projects.
// Project Structure
MyApplication/
├── app/
├── network/
├── featureA/
├── featureB/
└── settings.gradle

// settings.gradle
include ':app', ':network', ':featureA', ':featureB'

// app/build.gradle
dependencies {
implementation project(':network')
implementation project(':featureA')
}
  • iOS uses Xcode for project management and can leverage Swift Package Manager (SPM) for dependencies.
//Project Structure
MyApplication/
├── MyApp/
├── NetworkFramework/
├── FeatureAFramework/
└── Package.swift

// Package.swift
import PackageDescription

let package = Package(
name: "MyApp",
dependencies: [
.package(path: "./NetworkFramework"),
.package(path: "./FeatureAFramework")
],
targets: [
.target(
name: "MyApp",
dependencies: ["NetworkFramework", "FeatureAFramework"]),
]
)
  • CocoaPods and Carthage are also popular dependency managers for iOS, besides SPM.
// Podfile
target 'MyApp' do
pod 'Alamofire', '~> 5.4'
end

Testing Types

Both Android and iOS offer various testing methods to ensure app reliability and performance. Below is a comparison of the key testing types used for each platform:

This is a brief overview of starting your journey towards iOS development. Future parts will cover each topic in detail.

Thank you 🚀

--

--

Responses (4)