Chapter 24 - Actions and Lazy Variables

First off, thanks for the in depth book on the topic!

Quick question, to quote from Chapter 24:

The last thing to tackle is the addition of existing items. For this, you’ll need a new Action that isn’t temporary; remember that actions have to be referenced other than via a subscription, otherwise they’ll be deallocated. As mentioned in Chapter 19, this is a frequent source of confusion.

Create a new lazy variable in TasksViewModel:

lazy var editAction…

I did refer back to Chapter 19 and couldn’t find a discussion there about when and how to use Actions in a temporary vs permanent way. Can I trouble you to elaborate a bit further on the when and how? Is that the purpose of the “lazy var”, to “use” the action outside of a subscription? Why do we want the action to be permanent in this particular example?

The problem I’m trying to solve is extending the ideas in Chapter 24 to include a login (through Realm Cloud) before viewing the task list, and in order to do that I need to pass login credentials from the LoginViewController to the LoginViewModel, and not sure if I should be pointing those credentials towards a function within LoginViewModel, towards a lazy var, or what. In the example ViewModel code, you have a combination of short functions and actions defined; when would you choose one over the other? Help would be greatly appreciated.

A concrete example would be contrasting these two:

func onCreateTask() -> CocoaAction {
    return CocoaAction { _ in
        return self.taskService
            .createTask(title: "")
            .flatMap { task -> Observable<Void> in
                let editViewModel = EditTaskViewModel(task: task,
                                                      coordinator: self.sceneCoordinator,
                                                      updateAction: self.onUpdateTitle(task: task),
                                                      cancelAction: self.onDelete(task: task))
                return self.sceneCoordinator
                    .transition(to: Scene.editTask(editViewModel), type: .modal)
                    .asObservable().map { _ in }
        }
    }
}

lazy var editAction: Action<TaskItem, Swift.Never> = { this in
    return Action { task in
        let editViewModel = EditTaskViewModel(
            task: task,
            coordinator: this.sceneCoordinator,
            updateAction: this.onUpdateTitle(task: task)
        )
        return this.sceneCoordinator
            .transition(to: Scene.editTask(editViewModel), type: .modal)
            .asObservable()
    }
}(self)   

They both end up presenting the editing modal, but one is constructed as a lazy var and the other as a function?

Many thanks in advance,

Scott

@fpillet Can you please help with this when you get a chance? Thank you - much appreciated! :]

Hi @scott36,

An Action is and object with input (Observer) and output (Observable) endpoints. But one important thing to remember is that, like any object, it has to be retained by something to survive the current scope.

Returning a newly created Action from a function will create a new Action object, than pass ownership to the caller. If caller merely subscribes to the action output, for example, without retaining a reference to the action, then the Action will be destroyed as soon as the caller’s local scope exits.

In some cases, returning a new Action from a function is fine when you know that the caller will retain it (for example, set it as a button’s .rx.action). In other cases, using a lazy var guarantees that your model object itself is the owner of the Action, that it is being created only once and that it won’t be destroyed until your model object is destroyed.

So yes my examples show both ways and I intended to highlight both ways of managing action lifetimes. Guess I should expand the explanation a bit to make it clearer, thanks for pointing this out!

Let me know if you need further examples or clarification.

First, thank you for this wonderful book.

Probably this is an off-topic question, but it is related with the source code. Could you help me please with the thing I want to solve? In the function:

onCreateTask()

You call

.createTask(title: “”)

With an empty string. The problem is when without tapping the Ok or Cancel buttons, you (or the user) stop the application and run the application again, you ended with an empty task.

Too much work to do (and a little bit ugly):

  • Create a Realm TaskItem object.
  • onUpdate Update the TaskItem Object with user changes when the user taps OK button.
  • onDelete Delete the TaskItem Object when the user taps Cancel button.

Is there another way to create that object?, probably a temporarily Realm Object and persist it just when the user taps OK button?

I have no problem with the lazy var editAction, because the Realm object already exists.

@fpillet Can you please help with this when you get a chance? Thank you - much appreciated! :]

@williammr this is more an issue with Realm than with RxSwift. You’re right that this is a shortcoming of the example, for which I tried to keep the code size minimal.

One solution you have is to NOT add the object to the database during createTask. Instead, don’t touch the uid at this point, and during update(task:title:), set the uid and add to database if it’s not already done. Here is an example implementation:

  @discardableResult
  func update(task: TaskItem, title: String) -> Observable<TaskItem> {
    let result = withRealm("updating title") { realm -> Observable<TaskItem> in
      try realm.write {
        task.title = title
        if task.uid == 0 {
          task.uid = (realm.objects(TaskItem.self).max(ofProperty: "uid") ?? 0) + 1
          realm.add(task)
        }
      }
      return .just(task)
    }
    return result ?? .error(TaskServiceError.updateFailed(task))
  }

of course, you’ll need to change the delete(task:) and toggle(task:) calls as well. In toggle(task:) you want to add the object to the database if needed (user may be typing the title, then taps the checkmark), and in delete(task:) you’ll want to check that uid is non-zero before trying to delete the object.

Hope this helps,
Florent

1 Like

@fpillet Thank you for answering this question. I’ll give it a try. In the other hand, my solution was to create a helper item struct and I also to create a different scene and view model for add item. I know, two scenes and code to maintain that do almost the same, but at the end I’m going to have a better control of the tasks, I also know that if I change the Realm TaskItem structure I’ll have to change the struct (bad design, I’m working on that). In the following code you can see my implementation:

The struct is so simple.

struct Item {
let title: String
let type: String

init(title: String, type: String) {
self.title = title
self.type = type
}
}

On protocol TaskServiceType:

protocol TaskServiceType {

@discardableResult
func createItem(title: String, type: String) -> Observable<Item>


}

On TaskViewModel:

func onAdd(item: Item) -> Action<(String, String), Void> {
    return Action { fields in
      return self.taskService.createTask(title: fields.0, type: fields.1).map { _ in }
    }
  }

  func onCreateTask() -> CocoaAction {
    return CocoaAction { _ in
      return self.taskService
        .createItem(title: "", type: "")
        .flatMap { item -> Observable<Void> in
          let addViewModel = AddTaskViewModel(task: item,
                                              coordinator: self.sceneCoordinator,
                                              updateAction: self.onAdd(item: item))
          return self.sceneCoordinator
            .transition(to: Scene.addTask(addViewModel), type: .modal)
            .asObservable().map { _ in }
      }
    }
  }

As you can see, only when the user taps the OK button, the updateAction is performed, that executes onAdd function and on that function is when the data is really saved to Realm. With this approach, no matter if the application is closed by the user or the operating system when the app is on the Create Item Scene.

self.taskService.createTask(title:, type:)

I like so much your code, I also found some variations of this code on internet and on my own I implemented SceneCoordinator with UITabBarController.

Thank you,
William

This topic was automatically closed after 166 days. New replies are no longer allowed.