Reputation: 29
Hi guys i have a specific problem with updating realm object. I have an object ToDo which have properties and ToDoDAO:
public struct ToDo: LocalStorable {
public let id: String
public let name: String
public var symbol: Icon
public let image: Data?
public let executedDate: Date
public let executedTime: Date
public let remindTime: Date?
public let reminderRepetition: Repetition
public let tag: String
public var subtasks: [SubToDo]
public let isArchived: Bool
public var isDone: Bool
public init(
id: String = UUID().uuidString,
name: String,
symbol: Icon,
image: Data?,
executedDate: Date,
executedTime: Date,
remindTime: Date?,
reminderRepetition: Repetition,
tag: String,
subtasks: [SubToDo],
isArchived: Bool = false,
isDone: Bool = false
) {
self.id = id
self.name = name
self.symbol = symbol
self.image = image
self.executedDate = executedDate
self.executedTime = executedTime
self.remindTime = remindTime
self.reminderRepetition = reminderRepetition
self.tag = tag
self.subtasks = subtasks
self.isArchived = isArchived
self.isDone = isDone
}
public init(task: ToDo, isDone: Bool) {
self.id = task.id
self.name = task.name
self.symbol = task.symbol
self.image = task.image
self.executedDate = task.executedDate
self.executedTime = task.executedTime
self.remindTime = task.remindTime
self.reminderRepetition = task.reminderRepetition
self.tag = task.tag
self.subtasks = task.subtasks
self.isArchived = task.isArchived
self.isDone = isDone
}
public init(from dao: ToDoDAO) {
self.id = dao.id
self.name = dao.name
if let iconDAO = dao.symbol {
print("DEBUG - ToDo iconDAO:", iconDAO)
self.symbol = Icon(from: iconDAO)
} else {
print("DEBUG - ToDo fail")
self.symbol = .clipboardIcon
}
self.image = dao.image
self.executedDate = dao.executedDate
self.executedTime = dao.executedTime
self.remindTime = dao.remindTime
self.reminderRepetition = Repetition(rawValue: dao.reminderRepetition ?? "") ?? .daily
self.tag = dao.tag
self.subtasks = dao.subtasks.map { SubToDo(from: $0) }
self.isArchived = dao.isArchived
self.isDone = dao.isDone
}
public enum CodingKeys: String, CodingKey {
case id
case name
case symbol
case image
case executedDate
case executedTime
case remindTime
case reminderRepetition
case tag
case subtasks
case isArchived
case isDone
}
}
This is ToDoDAO:
public final class ToDoDAO: RealmSwift.Object, LocalDAOInterface {
@Persisted(primaryKey: true) public var id: String
@Persisted public var name: String
@Persisted public var symbol: IconDAO?
@Persisted public var image: Data?
@Persisted public var executedDate: Date
@Persisted public var executedTime: Date
@Persisted public var remindTime: Date?
@Persisted public var reminderRepetition: String
@Persisted public var tag: String
@Persisted public var subtasks: RealmSwift.List<SubToDoDAO>
@Persisted public var isArchived: Bool
@Persisted public var isDone: Bool
override public init() {
super.init()
self.name = ""
self.symbol = IconDAO()
self.image = Data()
self.executedDate = Date()
self.executedTime = Date()
self.remindTime = nil
self.reminderRepetition = ""
self.tag = Tag.otherEvent.rawValue
self.subtasks = RealmSwift.List<SubToDoDAO>()
self.isArchived = false
self.isDone = false
}
public init(from todo: ToDo) {
super.init()
self.id = todo.id
self.name = todo.name
self.symbol = IconDAO(from: todo.symbol)
self.image = todo.image
self.executedDate = todo.executedDate
self.executedTime = todo.executedTime
self.remindTime = todo.remindTime
self.reminderRepetition = todo.reminderRepetition.rawValue
self.tag = todo.tag
self.subtasks.append(objectsIn: todo.subtasks.map { SubToDoDAO(from: $0) })
self.isArchived = todo.isArchived
self.isDone = todo.isDone
}
}
and i cannot update two properies: symbol and subtasks and this is Icon model:
public struct Icon: LocalStorable {
public let id: String
public let symbol: Data
public let circleColor: Color
public init(id: String = UUID().uuidString, symbol: Data, circleColor: Color) {
self.id = id
self.symbol = symbol
self.circleColor = circleColor
}
public init(from dao: IconDAO) {
self.id = dao.id
self.symbol = dao.symbol
self.circleColor = Color(hex: dao.circleColor) ?? .accentColor
}
public init(icon: Icon) {
self.id = icon.id
self.symbol = icon.symbol
self.circleColor = icon.circleColor
}
public enum CodingKeys: String, CodingKey {
case id
case symbol
case circleColor
}
public static let tapIcon = Icon(symbol: Icons.tap?.pngData() ?? Data(), circleColor: .blue.opacity(0.2))
public static let dishIcon = Icon(symbol: Icons.dinner?.pngData() ?? Data(), circleColor: .brown)
public static let trayIcon = Icon(symbol: Icons.tray?.pngData() ?? Data(), circleColor: .blue.opacity(0.2))
public static let clipboardIcon = Icon(symbol: Icons.clipboard?.pngData() ?? Data(), circleColor: .yellow)
public static let photoIcon = Icon(symbol: Icons.photo?.pngData() ?? Data(), circleColor: .blue.opacity(0.1))
public static let birthdayIcon = Icon(symbol: Icons.birthday?.pngData() ?? Data(), circleColor: .yellow.opacity(0.5))
public static let catIcon = Icon(symbol: Icons.cat?.pngData() ?? Data(), circleColor: .green.opacity(0.6))
public static let cinemaIcon = Icon(symbol: Icons.cinema?.pngData() ?? Data(), circleColor: .red.opacity(0.5))
public static let dentist = Icon(symbol: Icons.dentist?.pngData() ?? Data(), circleColor: .green.opacity(0.7))
public static let dog = Icon(symbol: Icons.dog?.pngData() ?? Data(), circleColor: .yellow.opacity(0.7))
public static let football = Icon(symbol: Icons.football?.pngData() ?? Data(), circleColor: .purple.opacity(0.4))
public static let learning = Icon(symbol: Icons.learning?.pngData() ?? Data(), circleColor: .blue.opacity(0.4))
public static let roadTrip = Icon(symbol: Icons.roadTrip?.pngData() ?? Data(), circleColor: .red.opacity(0.3))
public static let running = Icon(symbol: Icons.running?.pngData() ?? Data(), circleColor: .red.opacity(0.4))
public static let shopping = Icon(symbol: Icons.shopping?.pngData() ?? Data(), circleColor: .yellow.opacity(0.5))
public static let vacation = Icon(symbol: Icons.vacation?.pngData() ?? Data(), circleColor: .orange.opacity(0.6))
public static let allIcons: [Icon] = [
dishIcon,
clipboardIcon,
birthdayIcon,
catIcon,
cinemaIcon,
dentist,
dog,
football,
learning,
roadTrip,
running,
shopping,
vacation
]
}
and IconDAO:
public final class IconDAO: RealmSwift.Object, LocalDAOInterface {
@Persisted(primaryKey: true) public var id: String
@Persisted public var symbol: Data
@Persisted public var circleColor: String
override public init() {
super.init()
self.symbol = Data()
self.circleColor = ""
}
public init(from icon: Icon) {
super.init()
self.id = icon.id
self.symbol = icon.symbol
self.circleColor = icon.circleColor.toHex() ?? ""
}
}
and also SubToDo:
public struct SubToDo: LocalStorable {
public let id: String
public var title: String
public var isCompleted: Bool
public init(
id: String = UUID().uuidString,
title: String,
isCompleted: Bool = false
) {
self.id = id
self.title = title
self.isCompleted = isCompleted
}
public init(from dao: SubToDoDAO) {
self.id = dao.id
self.title = dao.title
self.isCompleted = dao.isCompleted
}
public enum CodingKeys: String, CodingKey {
case id
case title
case isCompleted
}
}
and DAO:
public final class SubToDoDAO: RealmSwift.Object, LocalDAOInterface {
@Persisted(primaryKey: true) public var id: String
@Persisted public var title: String
@Persisted public var isCompleted: Bool
override public init() {
super.init()
self.title = ""
self.isCompleted = false
}
public init(from subtodo: SubToDo) {
super.init()
self.id = subtodo.id
self.title = subtodo.title
self.isCompleted = subtodo.isCompleted
}
}
i try to update it using func below but it didn't work, for example updating ICON: ToDo gets deafult value which is .clipboardIcon and in Realm Studio it's display nil.
Update ToDo
func updateToDo(toDo: ToDo) async throws {
state = .loading
var updatedToDo = ToDo(
id: toDo.id,
name: newTaskTitle,
symbol: newTaskSymbol,
image: selectedTaskImage?.jpegData(compressionQuality: 0.9),
executedDate: taskDay,
executedTime: taskTime,
remindTime: remindTime,
reminderRepetition: repetition,
tag: tag.rawValue,
subtasks: subtasks
)
print("DEBUG - todo: ", toDo.symbol)
print("DEBUG - updatedToDo: ", updatedToDo.symbol)
let updates = compare(old: toDo, updated: updatedToDo)
try await toDoManager.updateToDo(todo: toDo, data: updates)
state = .loaded
}
this is also compare func:
public func compare(old: any LocalStorable, updated: any LocalStorable) -> [String: Any] {
var differences: [String: Any] = [:]
let oldMirror = Mirror(reflecting: old)
let newMirror = Mirror(reflecting: updated)
for (propertyName, oldValue) in oldMirror.children {
guard let propertyName else { continue }
if let newValue = newMirror.descendant(propertyName) {
if let oldEnumValue = oldValue as? (any RawRepresentable),
let newEnumValue = newValue as? (any RawRepresentable),
"\(oldEnumValue.rawValue)" != "\(newEnumValue.rawValue)" {
differences[propertyName] = newEnumValue.rawValue
} else if "\(oldValue)" != "\(newValue)" {
differences[propertyName] = newValue
}
}
}
return differences
}
the fun fact is that when i create a ToDo everything works fine. Create ToDo
func createToDo() async throws {
state = .loading
let newToDo = ToDo(
name: newTaskTitle,
symbol: newTaskSymbol,
image: selectedTaskImage?.jpegData(compressionQuality: 0.9),
executedDate: taskDay,
executedTime: taskTime,
remindTime: remindTime,
reminderRepetition: repetition,
tag: tag.rawValue,
subtasks: subtasks
)
try await toDoManager.createToDo(todo: newToDo)
state = .loaded
}
this is also viewModel where i have functions: View Model
@MainActor
final class AddTaskViewModel: ObservableObject {
enum State: Equatable {
case loading
case loaded
case error(String)
}
@Published private(set) var state: State = .loaded
@Published private(set) var showDivider: Bool = true
@Published private(set) var selectedPicker: Picker?
@Published private(set) var selectedTaskImage: UIImage? = nil
@Published var newTaskTitle: String = ""
@Published var isScrolled: Bool = false
@Published var newTaskSymbol: Icon = .clipboardIcon
@Published var taskDay: Date = .now
@Published var taskTime: Date = .now
@Published var remindTime: Date?
@Published var repetition: Repetition = .noRepeat
@Published var tag: Tag = .all
@Published var newSubtaskTitle: String = ""
@Published var editSubtaskTextFieldText: String = ""
@Published var editingSubtaskIndex: Int? = nil
@Published var subtasks: [SubToDo] = []
@Published var defaultScrollAnchor: UnitPoint? = .top
@Published var taskImageSelection: PhotosPickerItem? = nil {
didSet {
Task {
do {
try await setImage(from: taskImageSelection)
} catch {
handleError(error: error)
}
}
}
}
//TODO: add validation to newTaskTitle
@Inject private var toDoManager: ToDoManagerInterface
private var existingToDo: ToDo?
private var isEditing: Binding<Bool?>
var buttonTitle: LocalizedStringKey {
existingToDo == nil ? "Create Task" : "Update Task"
}
var isPickerSelected: Binding<Bool> {
Binding<Bool>(
get: {
self.selectedPicker != nil },
set: {
if !$0 {
self.selectedPicker = nil
}
}
)
}
var selectedDate: String { taskDay.isToday ? "Today" : dateFormatter(dateFormat: .dateWithDots).string(from: taskDay) }
var selectedTime: String { "Time: \(dateFormatter(dateFormat: .time).string(from: taskTime))" }
var selectedReminder: String { remindTime != nil ? dateFormatter(dateFormat: .timeWithPeriods).string(from: remindTime ?? Date()) : "No reminder" }
init(isEditing: Binding<Bool?> = .constant(nil)) {
if let toDo = existingToDo {
self.existingToDo = toDo
self.newTaskTitle = toDo.name
self.newTaskSymbol = toDo.symbol
self.taskDay = toDo.executedDate
self.taskTime = toDo.executedTime
self.remindTime = toDo.remindTime
self.repetition = toDo.reminderRepetition
if let tag = Tag(rawValue: toDo.tag) {
self.tag = tag
}
self.subtasks = toDo.subtasks
self.selectedTaskImage = toDo.image.flatMap { UIImage(data: $0) }
}
self.isEditing = isEditing
}
func handleScrollActions(position: Double) {
showDivider = position >= -80.0
isScrolled = position < -10
}
func onPickerSelected(picker: Picker?) {
if let index = editingSubtaskIndex, selectedPicker == .editSubtask {
subtasks[index].title = editSubtaskTextFieldText
}
if picker == .subtask {
createSubtask()
print("DEBUG - created Subtask:", subtasks)
}
onShowOptionToggle()
}
private func onShowOptionToggle() {
withAnimation(.smooth(duration: 0.1)) {
selectedPicker = nil
}
}
func handlePickerSelection(_ picker: Picker, subtaskIndex: Int? = nil) {
withAnimation(.smooth(duration: 0.7)) {
selectedPicker = picker
if let index = subtaskIndex {
editingSubtaskIndex = index
editSubtaskTextFieldText = subtasks[index].title
}
}
}
typealias Picker = ToDoInterface.Picker
}
extension AddTaskViewModel {
private func setImage(from selection: PhotosPickerItem?) async throws {
guard let selection else { return }
let data = try await selection.loadTransferable(type: Data.self)
guard let data, let uiImage = UIImage(data: data) else {
throw AppError.unableToLoadImage
}
selectedTaskImage = uiImage
}
func createToDo() async throws {
state = .loading
let newToDo = ToDo(
name: newTaskTitle,
symbol: newTaskSymbol,
image: selectedTaskImage?.jpegData(compressionQuality: 0.9),
executedDate: taskDay,
executedTime: taskTime,
remindTime: remindTime,
reminderRepetition: repetition,
tag: tag.rawValue,
subtasks: subtasks
)
try await toDoManager.createToDo(todo: newToDo)
state = .loaded
}
func handleError(error: Error) {
withAnimation {
if let localizedError = error as? LocalizedError {
state = .error(localizedError.localizedDescription)
} else {
state = .error(AppError.unexpectedError(error.localizedDescription).errorDescription ?? error.localizedDescription)
}
}
}
func handleDismissErrorView() {
withAnimation {
state = .loaded
}
}
private func createSubtask() {
guard !newSubtaskTitle.isEmpty else { return }
let newSubtask = SubToDo(title: newSubtaskTitle)
subtasks.append(newSubtask)
newSubtaskTitle = ""
}
}
//MARK: Task Management
extension AddTaskViewModel {
func manageToDo() async throws {
if let existingToDo = existingToDo {
try await updateToDo(toDo: existingToDo)
isEditing.wrappedValue = false
} else {
try await createToDo()
}
}
func updateToDo(toDo: ToDo) async throws {
state = .loading
var updatedToDo = ToDo(
id: toDo.id,
name: newTaskTitle,
symbol: newTaskSymbol,
image: selectedTaskImage?.jpegData(compressionQuality: 0.9),
executedDate: taskDay,
executedTime: taskTime,
remindTime: remindTime,
reminderRepetition: repetition,
tag: tag.rawValue,
subtasks: subtasks
)
print("DEBUG - todo: ", toDo.symbol)
print("DEBUG - updatedToDo: ", updatedToDo.symbol)
let updates = compare(old: toDo, updated: updatedToDo)
try await toDoManager.updateToDo(todo: toDo, data: updates)
state = .loaded
}
func readActiveToDo(primaryKey: String) async throws {
self.existingToDo = try await toDoManager.readToDo(primaryKey: primaryKey)
print("DEBUG - read todo:", existingToDo)
}
}
If anyone have a idea why is this not working please help me... Maybe I created the objects incorrectly and that's why updating doesn't work, but the question is why adding ToDo works.
Upvotes: 0
Views: 29