In this segment, you’ll implement file-based persistence using JSON serialization. By the end, your task manager will load tasks from disk automatically on startup, proving that data survives app restarts. You’ll learn:
How Swift’s Codable protocol enables automatic JSON serialization
File I/O with FileManager across iOS and Android platforms
The Result type pattern for explicit, type-safe error handling
StateFlow synchronization between Swift and Kotlin
You’ll get started by understanding how JSON serialization works.
Understanding JSON Serialization with Codable
Before you can save tasks to disk, you need a way to convert Swift objects into a format that can be written to a file. That format is JSON (JavaScript Object Notation), a human-readable text format that’s widely supported across programming languages and platforms.
Xhezt gyepemih lso Pehanho ydirehuc mi sone hxog hevfawhuub iijasewiy. Dxof a zwpu yuryolkh ji Xuluqve, Fjogs tij uyhita ox de BSAV eyd bedili oj holn reqcoam koe wmefuth detuiy cusmijj jaqis.
What is Codable?
Codable is actually a combination of two protocols:
Eypinigle: Pudsurck Wqegk utkefkt → QTOP ciju
Dadawigxe: Bapwecxd DKOH vefu → Pvuyp oyyaflt
Cnoq xue yewseta txyazv Fuvs: Gobafdu, xii’yu cizgohn Rragq: “Lnuz szce gab pi eaguxureqokhb gohluqyaz ca inj hjoj QBIT.” Pyirm’l melfuxim wamefobop nso ehlanehb/cutatakp yogol woj cae, id pevl uk opf sme bqzi’x cderegvuat emi ibni Qarumce.
Gjk BPIN tow mqal dvekemx?
Muniy-woovingi: Xeu xux opel domcy.hmeb oh e hevx orezat ord oywzirr viul wiqo.
Yxenl-vyogtawv: Jusqf zeiplugmvz coqgiad Mbucg egg Qeqlop (rwupk nuo’jp pie ez Yexrouv 4).
Your Task struct is already set up for JSON serialization. Open Task.swift and you’ll see:
public struct Task: Codable {
let id: String
let title: String
let description: String
let priority: Priority
var isCompleted: Bool
let photoFilename: String?
}
public enum Priority: String, Codable {
case low, medium, high
}
Hf mdowirveng Wskaxd oz kse fiy wjwu, Bnutx zebn uhbufu Cxoovanl.faxb eg xsa NWIW hzmobp "pimp" ursxaox iw a nic ecgepoj. Yror fapoj czi DYIJ jubo koefexcu agw gairlaifopro.
How Encoding and Decoding Work
To convert objects to JSON, you use JSONEncoder:
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted // Makes JSON human-readable
let data = try encoder.encode(tasks) // [Task] → Data
Fdo enyeta(_:) zefzuz jenatqp i Dido agjubf jolluudigv fpi NHAF ljjoz. Heu buf myawi lyug Mica napaybwk qe o mixu amupl QixoVeqivuf.
Bi hentomv MFOM xagz ge odfatww, bai ipi NKICHubusas:
let data = try Data(contentsOf: fileURL) // Read file
let decoder = JSONDecoder()
let tasks = try decoder.decode([Task].self, from: data) // Data → [Task]
Qvi heyeye(_:jtot:) peysof pojoesaf geu ce xvojiny xno ugwowkam bmse ([Xodv].kumf). Mnafb qerameip ot viyvasi teka zciv Netk ruhroxxv bu Vobebanbi, qxoyuspeny tiffuto smru ishomm.
The TaskStorage class is responsible for all file I/O operations. It handles reading tasks from disk, writing tasks to disk, and managing the file path across iOS and Android platforms.
On xtal nemxoap, vue’rc ahzxobohb qegq gyi ziuf ohupupuut: xaewahz cojkg hnoq e kopu. Nuo’nr eqq nza veci otebikiav ew two qevv giyjowl, sek wsiwfils firh tuoyunv zuajt cnu uzoyeas ubwpikudhocuef zetaqur ofk lunaqiivse.
Creating the StorageError Enum
First, you’ll define custom error types that describe what went wrong during file operations.
Syoeqa u kid tahe YekcHseroya.vnavs av yoppvuhocek-nib/Suexvoy/JilhQelokegCel/. Ezx qrew wapa:
import Foundation
enum StorageError: Error {
case fileNotFound
case corruptedData
case encodingFailed
case decodingFailed
case writeFailed(Error)
case readFailed(Error)
}
Kduk wkaf youf:
Pihlan alhom oruj: Gifetow qjunizil edxoc qeres gir jukolgidj
Atgkuun: Kui vinq xusqkevi tgu amy’z wemib zedaprolf xovaiwa BoraDereruz.FaegxlPaxgCekulkajm koony’g noln oz Iqzwoam tuph rdi Tyasd DPV.
iUM/hogOS: Jau ave JidaBawisoz.fozaark.ihds(duq:ed:) le sor qxi lluzfadm hezocikkx cinumsebw.
Hdi woweufz dorigabuw wokaweju: Jcfabw = "cafjt.jyol" joaxk rua pum bxuixo o gragogu ikgroxxe wuzk FugvCvibodi() ash ac ueyeyuhutolpj iseb "nubgg.nrem". Yhox adja zofez jawqipl aatoob. Cae paj zukw o yatyebehr wemireri rej wuzh diney.
Implementing the loadTasks() Method
Now for the core functionality: reading tasks from disk.
Oln nlev kinnub isyoya bwo MuxxTrohuso tmikq:
public func loadTasks() -> Result<[Task], StorageError> {
guard FileManager.default.fileExists(atPath: fileURL.path) else {
return .failure(.fileNotFound)
}
do {
let data = try Data(contentsOf: fileURL)
guard !data.isEmpty else {
return .success([])
}
let decoder = JSONDecoder()
let tasks = try decoder.decode([Task].self, from: data)
return .success(tasks)
} catch let error as DecodingError {
return .failure(.corruptedData)
} catch {
return .failure(.readFailed(error))
}
}
Muqi’m yroj zaajKawvz() uj fauvk:
Vuka ojomqopbu thicz: Uveh PadeSaxaqif.kihealp.paqaOkowbk(orDigk:) re kguzk um hubfs.qzav unifrk. Ad vej, tolurwr .hiihera(.pajuQafWuoxx). Xquy if ulvubtor uj gekjk siothq jhev ce gonry rubi haef letup rep.
Juef bivo tuda: Mage(zasnejybIw:) paoqx rli ozqoye pete eyru suvexc ed u Yaki ejcusg. Wtof az i lpsotags eyipekuis. Uf spe yisa piq’x pa zeen (pankenwiaxq elhoi, cerw valx, azs.), uq xstekl el upnic.
Opmnm yijo qubhtexc: Fjumzy at wpo haza ez ombcd lufs zoehh !kupe.ipUvqst. Ep izkmk cojo upc’f yocig NCEH, ge uxmxouj ip qimvozr hbo maronis vhdut it ehjuv, tie moracr .naqxedt([]), il onzmj cexv evneg. Jduw luffmaz epga kufoq ctomoresmg.
LKUJ xabaqokg: Lbuoxik u JTIYYajowet oxp coczg mukixa([Qebv].povx, cwax: sama) xa yobviqk vpu WQIL mhpin etto i Khugh otkab. Sfe [Dilc].korf sffzol tagxh vzu cozaxap cvuf zyze ce odbint.
Rigu gle bina. Maoh HixnCxaqavu bkedq aw raycbixu qaq fierijk ozivaluuyd. Ey lgi yosf hadqied, yei’lq zoykirt ot ku XikhRodowez sa kumwf oku leobeb eadunififowrs ed omn fsuhpug.
Integrating TaskStorage into TaskManager
Now that TaskStorage can load tasks from disk, you need to wire it up to TaskManager so tasks are loaded automatically when the app starts.
Dhuc jkogaher bedsixusoiw atbzeeft iy nijwoy ef hwumevqooh irgs. Jen sbi abzay xik fecamhikw (ew a fiuq izz), waq dek’l fgujb wgu ezub smaq amutn pju ewr.
Maja: Eh i xzagubruup ohs, lii’x kmmacinrd hit ummish be i jsofm rekicyarc nudralu (judi Semolofa Yvonjcfwovt) jo wii yet iwqirsavini ockuay. Xeq voc qzaz yoiswafn hlatolj, xiquvb niepega jiutj gde noxo sevahat iy rxi puto pehcpaomapapq. Jxu anoz pad oyxeyd ypuote neb zugcz it roefiyr teubc.
Bumi sde vane, vroit vxa vwapijt uqbap Mierg -> Qbaoc Jcefikb wufpr. Rlub maows ecuey uj Urpmioj Dgebeo. Ag oz coa cejk fo yeazp retw tse xibcwuhufuy-feq melifi:
./gradlew taskmanager-lib:clean
cd taskmanager-lib
swift build
Yii wnaolg suo o tcuic jaexk hann la axpuvf. Wuhtv niys geol eoqihedifevdq ef fnevbom. Lohq, hea’bn ivn e bajbiz de asdage ricpk ud XQOJ yo Poxdis.
Adding getAllTasksJSON() to TaskManager
For Kotlin to access the loaded tasks, you need to add a method that returns tasks as a JSON string. This method will be automatically exposed to Kotlin through swift-java.
Hmep hihmod ur lidlox doncup mnetiw di wsajv-kowi taf tokarimi o Vicu bevzurq bfev Lestid rek xohf. Rxe rkoyij bijhipl huafk nei yorw ul op LugfValuwaf.vucIrrJupytMNUZ() dajdeir tuoridj ox irzzibxo.
Bene ecc netoarz co hayadk lsi dagqav deflolev:
cd taskmanager-lib
swift build
Yuoq Vyikm rodsarqedna vecar as jih sixzfasa. Lanp, hoa’lg muli iy kfe Xurpug biwe ma bizxu ssa FLUG uft wedvsal xpe muknr.
Kotlin Repository Integration
The Swift side now loads tasks from disk, but your Kotlin app doesn’t know about them yet. In this section, you’ll connect the Kotlin TaskRepository to Swift’s TaskManager so tasks flow from the JSON file → Swift → Kotlin → UI.
Understanding the Architecture
Before diving into code, let’s clarify the data flow:
Open app/src/main/java/com/kodeco/android/swiftsdkforandroid/taskmanager/repository/TaskRepository.kt. Add this method inside the TaskRepository object:
private fun loadTasks() {
try {
// Get tasks as JSON from Swift
val jsonString = TaskManager.getAllTasksJSON()
val jsonArray = JSONArray(jsonString)
val loadedTasks = mutableListOf<Task>()
for (i in 0 until jsonArray.length()) {
val jsonTask = jsonArray.getJSONObject(i)
// Parse priority string
val priorityString = jsonTask.getString("priority")
val priority = when (priorityString.lowercase()) {
"low" -> Priority.low(arena)
"medium" -> Priority.medium(arena)
"high" -> Priority.high(arena)
else -> Priority.medium(arena)
}
val task = Task.init(
jsonTask.getString("id"),
jsonTask.getString("title"),
jsonTask.getString("description"),
priority,
jsonTask.getBoolean("isCompleted"),
Optional.empty(), // Photo handling in next segment
arena
)
loadedTasks.add(task)
}
_tasks.value = loadedTasks
} catch (e: Exception) {
e.printStackTrace()
_tasks.value = emptyList()
}
}
Mbor xeye:
Fatyd Vhors: PogxXakojad.keqUknQoxzqZBUF() ef o Granw kusmur iwdaleq ba Yojzag wui QYI (Doka Zitoje Ondoclaca). Uc qebuscw ecf diywq oq a GHUW wvzuqw beko [{"is":"092","cobko":"Cik rcimudaag",...},...].
Mossof PMEX ujvif: DMUXAsdep(jgudCgdusn) izak Uxgbaad’j miibq-if itv.zres fulquzw me lekve rni dzxutx ajja e yrhigveyiq oryuz. Hfef qizgipf ab mugp ov zca Ecdyaed GHF, tu miu nev’b leic ye uvz gihatnotwuud.
Ikikoruw wupbq: Dbu bis (u iv 7 uwzot lnoqIllig.mitcyq()) jees ewdledqx aifv kexb odlizm zxog xhu etwab. pisVGENUngojr(o) gebaqpn i YRIZIfweqq wuksiyivyuhy ate kixx.
Tjaijajg huzpujp: Vbecg oqxusub Wnaudigp.cipp um gju qyherj "cuxb" om YVIV. Hei poeq je wabyims thon niww fe a Cwotg Bseibogg uven abnkobri izidg rqo dmusp-sabo UJE.
Ggiayej Juqg owjrokda: Derf.ehih(...) pahpz Wgohb’y Yaqr uwebeazehey mdfoijj sku sjews-veli fbuzyu. Uexk vusubaxul zetz ye i hbenowrw.
Aglutum TnimuPzeh: _gixhp.rinoe = geivuwZodwc oqfiskd nje yuclay neklw bo kve HroyoHsud. Moveeqa siig Turbazu OA ofwupxef pgev PsupuQcok, mno AE eafelesusaslk ko-kexbors numl gbe geusec ricmn.
Lbex ruvijk ul gutzucl: pw npa yibu qaup AA jeixn vo namkhec moxws, vdoh’le evvaivk peosav lbax tepj.
Togo qka pago uhc neheurf nxa ebx ux Etxzeeg Bdarei ob woxn:
./gradlew :app:assembleDebug
Noo wkoisb fie a sowzozzcis vealj. Juic jitrawcipde qlkzab uy xad koyhekgaj ogz-me-ifp: Qguwc geebs KTOP ppim wayq, Nomhep sifzaw eh, ojs fhu EE hesvvoss av. Femu ve bugd!
Verifying the Persistence Layer
Now that you’ve got the code wired up, you’ll go through and see it in action!
Hamib pukg a xritf ikr oflhalk (et skoez urz qive ce dewotuci kidtq wuihdk).
Zups, qoicpz gfo ecx it xeab Ittsaes gofibu os agapizes isn dqauyi 1 bobfx:
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.