CRUD Operations & Validation

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

In the last segment, you implemented loading tasks from disk. The app now remembers tasks after restart. However, there’s still a problem: you can’t save changes.

The missing piece is to write operations utilizing CRUD (Create, Read, Update, Delete). In this segment, you’ll complete the persistence layer by:

  • Adding saveTasks() to write tasks to disk.
  • Implementing updateTask() and deleteTask() in TaskManager with validation.
  • Creating JNI exports to bridge Swift methods to Kotlin.

By the end, you’ll have a complete CRUD system where every change persists immediately to disk.

Understanding the CRUD Pattern

CRUD is a standard acronym in software development representing the four fundamental operations for managing data:

Why CRUD Matters

CRUD provides a standard vocabulary for data operations. When you say “implement CRUD,” other developers immediately understand you’re building Create, Read, Update, and Delete functions. This consistency makes codebases easier to understand and maintain.

Architecture Overview

The data flow for mutations follows this path:

Completing TaskStorage with saveTasks()

Earlier in the lesson, you implemented loadTasks() to read tasks from disk. Now you’ll complete TaskStorage by adding the write operation.

Implementing saveTasks()

Open taskmanager-lib/Sources/TaskManagerKit/TaskStorage.swift. Add this method after loadTasks():

Understanding Atomic Writes

The .atomic option deserves special attention because it’s crucial for data integrity:

Why Pretty Printed JSON?

During development, you’ll inspect tasks.json to debug issues. Compare these two formats:

Understanding TaskValidator

Before adding update and delete operations, you need validation logic. The Starter project includes a TaskValidator class that centralizes validation rules. While you could put validation directly in TaskManager, separating it into a dedicated TaskValidator class provides clearer responsibilities and makes testing easier.

Why Separate Validation?

Consider what happens without a separate validator:

Understanding TaskValidator.swift

The Starter project already includes TaskValidator.swift in Sources/TaskManagerKit/. This class was introduced in earlier lessons, so you don’t need to create it—just open it now to review how it works before using it in your CRUD operations:

Understanding Static Methods

Notice all methods are static. This means you call them on the class itself (TaskValidator.validateTitle()), not on instances (let validator = TaskValidator(); validator.validateTitle()).

Completing TaskManager.addTask()

Before implementing update and delete, you need to complete the existing addTask() method. Currently, it has a TODO comment for saving tasks to disk.

Addressing the TODO

Open taskmanager-lib/Sources/TaskManagerKit/TaskManager.swift and locate the addTask() method:

Adding Persistence

Replace the TODO comment with a call to saveTasks():

Updating Task Model for Photo Storage

Before implementing photo storage, you need to update the Task struct to reflect the proper semantics for how photos are stored.

Renaming the Field

Update the Task struct to use photoFilename:

Implementing PhotoStorage

Tasks can have associated photos. Now that your Task model uses photoFilename, you need a PhotoStorage class to handle photo file operations.

Why Photo Management Matters

Consider the lifecycle of task photos:

Understanding the Photos Directory

Photos are stored in a subdirectory of the app’s documents directory:

Creating PhotoStorage.swift

Create a new file taskmanager-lib/Sources/TaskManagerKit/PhotoStorage.swift:

Why Static Methods?

Notice all public methods are static. This design choice serves multiple purposes:

Implementing TaskManager.updateTask()

With TaskStorage.saveTasks() in place, you can now implement the Update operation. When a user edits a task, you need to find the task, validate the new data, update it in memory, and persist to disk.

Why Update Needs Validation

In Segment 02, you focused on loading tasks. The data was already validated when it was created. But for updates, you must validate again because:

Implementing the Method

Open taskmanager-lib/Sources/TaskManagerKit/TaskManager.swift. Add this method after addTask():

Understanding @discardableResult

The @discardableResult attribute allows callers to ignore the return value without compiler warnings:

Implementing TaskManager.deleteTask()

Deleting a task is simpler than updating, but it introduces a new concept: cascading deletes. When you delete a task, you must also delete the associated photo file (if it exists).

Implementing the Method

Add this method to TaskManager.swift after updateTask():

Understanding Cascading Deletes

The term cascading delete comes from database design. In SQL databases with foreign keys, you can configure ON DELETE CASCADE to automatically delete child records when you delete a parent record.

Understanding Idempotent Operations

Notice that deleting a non-existent task returns false, but deleting a photo that doesn’t exist is silently ignored (returns success). Both are examples of idempotent operations—you can call them multiple times with the same result.

Implementing getTask(by:) for Individual Access

You’ve implemented Create (addTask), Update (updateTask), and Delete (deleteTask). To complete the CRUD pattern, you need the Read operation for individual tasks. While getTasks() returns all tasks, you often need to fetch a single task by ID.

Completing CRUD

You’ve now implemented all four CRUD operations:

Exposing Methods to Kotlin with swift-java

Your Swift code is complete and ready to use.

How swift-java Works

The swift-java plugin is a build tool that scans your Swift code and generates JNI bindings automatically. When you build the Android project:

The public static Pattern

For swift-java to expose your methods to Kotlin, they must follow a specific pattern. Looking at your TaskManager methods:

Adding the getShared() Accessor

swift-java cannot directly expose Swift’s static let shared property because static properties don’t map cleanly to JNI. Instead, you provide a static accessor method.

Understanding Object Marshaling

When Kotlin calls manager.updateTask(task), how does the Kotlin Task object become a Swift Task object? swift-java generates marshaling code:

Key Takeaways

You’ve completed the Swift CRUD layer with validation, photo management, and automatic Kotlin bindings. Here’s what you learned:

See forum comments
Download course materials from Github
Previous: Persistent Load Workflow Next: Photo Storage & Kotlin Integration