Crafting exceptional mobile experiences with Swift and Kotlin
Hello! I'm Yaron, a passionate mobile app developer specializing in creating robust and user-friendly applications for iOS and Android platforms. With over a decade of experience in the industry, I've developed a wide range of applications from concept to launch.
My expertise spans Swift and Objective-C for iOS development and Kotlin and Java for Android. I'm committed to writing clean, maintainable code and creating intuitive user experiences that delight users.
Years Experience
Client Satisfaction
I develop iOS apps with full Apple ecosystem integration, providing seamless experiences across all Apple devices.
Creating polished, high-performance native iOS applications with beautiful UIs and fluid animations.
Developing iPad-optimized apps with adaptive layouts and multitasking support for productivity.
Building Mac apps using Catalyst or native AppKit, optimized for desktop interactions.
Creating watchOS apps and complications with efficient interfaces for wearable experiences.
Developing tvOS apps with focus-based navigation and engaging large-screen experiences.
Implementing AirPlay, AirPods, and HomePod connectivity for immersive audio experiences.
2019 - Present
Lead developer for iOS and Android applications, managing a team of 5 developers. Implemented CI/CD pipelines and reduced app crash rates by 95%.
2016 - 2019
Developed and maintained multiple iOS and Android applications serving over 500,000 users. Implemented complex features and optimized app performance.
2013 - 2016
Specialized in iOS development for various clients across different industries. Built and deployed 15+ apps to the App Store.
Throughout my career, I've had the privilege of working with these outstanding companies, delivering mobile solutions across various industries.
As a master of both iOS and Android development, I'm fluent in both Swift and Kotlin. Here's how similar functionality looks in both languages:
// Network request in Swift
import Foundation
class SchoolSearchViewModel: ObservableObject {
@Published var schools: [SchoolLocation] = []
@Published var isLoading = false
@Published var errorMessage: String?
func fetchSchools() {
isLoading = true
errorMessage = nil
let url = "https://nominatim.openstreetmap.org/search"
let parameters: Parameters = [
"limit": "10",
"bounded": "1",
"viewbox": "-0.1366,51.5260,-0.1246,51.5170",
"amenity": "school",
"addressdetails": "1",
"format": "json"
]
AF.request(url, parameters: parameters)
.validate()
.responseDecodable(of: [SchoolLocation].self) { response in
DispatchQueue.main.async {
self.isLoading = false
switch response.result {
case .success(let data):
self.schools = data
case .failure(let error):
self.errorMessage = error.localizedDescription
}
}
}
}
}
class SchoolSearchViewModel : ViewModel() {
private val _schools = MutableLiveData>()
val schools: LiveData> = _schools
private val _isLoading = MutableLiveData(false)
val isLoading: LiveData = _isLoading
private val _errorMessage = MutableLiveData()
val errorMessage: LiveData = _errorMessage
fun fetchSchools() {
_isLoading.value = true
_errorMessage.value = null
val params = mapOf(
"limit" to "10",
"bounded" to "1",
"viewbox" to "-0.1366,51.5260,-0.1246,51.5170",
"amenity" to "school",
"addressdetails" to "1",
"format" to "json"
)
viewModelScope.launch {
try {
val result = RetrofitClient.api.searchSchools(params)
_schools.value = result
} catch (e: Exception) {
_errorMessage.value = e.localizedMessage
} finally {
_isLoading.value = false
}
}
}
}
import SwiftUI
struct LoginView: View {
@State private var username: String = ""
@State private var isLoggedIn: Bool = false
var body: some View {
VStack(spacing: 20) {
Text("Welcome")
.font(.largeTitle)
.fontWeight(.bold)
TextField("Username", text: $username)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Button(action: {
// Handle login
isLoggedIn = !username.isEmpty
}) {
Text("Login")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(8)
}
if isLoggedIn {
Text("Hello, \(username)!")
.font(.title)
.padding()
}
}
.padding()
}
}
#Preview {
SampleView()
.preferredColorScheme(.light)
}
package com.yaronj.myviewsample
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun LoginScreen() {
var username by remember { mutableStateOf("") }
var isLoggedIn by remember { mutableStateOf(false) }
Column(modifier = Modifier.padding(16.dp)) {
Text(text = "Welcome", style = MaterialTheme.typography.headlineLarge)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (username.isNotBlank()) {
isLoggedIn = true
} else {
isLoggedIn = false
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Login")
}
Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.Center) {
if (isLoggedIn && username.isNotBlank()) {
Text(text = "Welcome, $username!",
style = MaterialTheme.typography.headlineLarge)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun LoginScreenPreview() {
LoginScreen()}
// Contacts programming in Swift
import Foundation
struct User: Codable {
let id: Int
let name: String
}
import Foundation
import Contacts
import SwiftUI
enum ContactError: Error {
case accessDenied
case unknown
}
struct ContatctItem: Identifiable {
var id = UUID().uuidString
let name: String
let email: String?
let phone: String?
init(contact: CNContact) {
self.name = "\(contact.givenName) \(contact.familyName)"
self.email = contact.emailAddresses.first?.value as String?
self.phone = contact.phoneNumbers.first?.value.stringValue
}
}
@MainActor class ContactsViewModel: ObservableObject {
@Published var contacts: [ContatctItem] = []
private let contactStore = CNContactStore()
private var keysToFetch: [CNKeyDescriptor] {
let keys = [
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
CNContactEmailAddressesKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor,
CNContactImageDataKey as CNKeyDescriptor,
CNContactThumbnailImageDataKey as CNKeyDescriptor,
CNContactIdentifierKey as CNKeyDescriptor,
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactOrganizationNameKey as CNKeyDescriptor
]
return keys
}
init() {
Task {
do {
let localContacts = try await fetchContacts()
self.contacts = localContacts
} catch {
print("Error fetching contacts: \(error)")
}
}
}
func fetchContacts() async throws -> [ContatctItem] {
do {
// Request permission first
let authStatus = CNContactStore.authorizationStatus(for: .contacts)
switch authStatus {
case .notDetermined:
let granted = try await contactStore.requestAccess(for: .contacts)
guard granted else {
throw ContactError.accessDenied
}
case .restricted, .denied:
throw ContactError.accessDenied
case .authorized:
break
case .limited:
print("Limited access granted")
@unknown default:
throw ContactError.unknown
}
}
return try await Task.detached(priority: .userInitiated) { [keys = self.keysToFetch] in
let contactStore = CNContactStore()
var allContacts: [CNContact] = []
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
try contactStore.enumerateContacts(with: fetchRequest) { contact, stop in
if let completeContact = try? contactStore.unifiedContact(
withIdentifier: contact.identifier,
keysToFetch: keys
) {
allContacts.append(completeContact)
}
}
return allContacts.map { ContatctItem(contact: $0) }
}.value
}
}
// Contacts programming in Kotlin
package com.yaronj.contacts
import android.content.Context
import android.provider.ContactsContract
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
data class ContactItem(
val id: String = UUID.randomUUID().toString(),
val name: String,
val email: String?,
val phone: String?
)
class ContactsViewModel(private val context: Context) : ViewModel() {
private val _contacts = MutableStateFlow>(emptyList())
val contacts: StateFlow> = _contacts
init {
loadContacts()
}
private fun loadContacts() {
viewModelScope.launch {
_contacts.value = fetchContacts(context)
}
}
private suspend fun fetchContacts(context: Context): List {
return withContext(Dispatchers.IO) {
val contactList = mutableListOf()
val contentResolver = context.contentResolver
val cursor = contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
null, null, null, null
) ?: return@withContext emptyList()
while (cursor.moveToNext()) {
val contactId = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts._ID))
val name = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)) ?: ""
// Fetch phone number
var phone: String? = null
if (cursor.getInt(cursor.getColumnIndexOrThrow(ContactsContract.Contacts.HAS_PHONE_NUMBER)) > 0) {
val phoneCursor = contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
"${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?",
arrayOf(contactId),
null
)
phoneCursor?.use {
if (it.moveToFirst()) {
phone = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
}
}
}
// Fetch email
var email: String? = null
val emailCursor = contentResolver.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
"${ContactsContract.CommonDataKinds.Email.CONTACT_ID} = ?",
arrayOf(contactId),
null
)
emailCursor?.use {
if (it.moveToFirst()) {
email = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Email.ADDRESS))
}
}
contactList.add(ContactItem(name = name, email = email, phone = phone))
}
cursor.close()
return@withContext contactList
}
}
}
}
Have a project in mind? Get in touch and let's create something amazing together.