New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best Pattern for Cancellable Commands #1326

Closed
BrettThePark opened this Issue May 14, 2014 · 4 comments

Comments

Projects
None yet
4 participants
@BrettThePark

BrettThePark commented May 14, 2014

I have been looking for a proper pattern for commands that can be cancelled.

Here is the scenario:
Joe user wants to log into the twitter service on their device. There is a button on the screen that says twitter and when the user taps on it, the login process starts and the text on the button changes to cancel. The login process could take a long period of time (30 seconds for example) and if the user presses the button again during this time, the existing login will be cancelled.

I have tried many different ways to make this work, but they all have a very bad smell. Here is the best solution I have come up with so far:

@property (strong, nonatomic) RACCommand *twitterLoginCommand;
@property (strong, nonatomic) RACCommand *cancelCommand;
@property (strong, nonatomic) id authenticatedUser;
@property (weak, nonatomic) RACDisposable * authenticationDisposable;

-(void) viewDidLoad {
  RAC(self, twitterButton.rac_command, self.twitterLoginCommand) = [[RACObserve(self, twitterLoginCommand.executing) flatten]
  map:^id(NSNumber * value) {
    @strongify(self);
    if (value.boolValue) {
      return self.cancelCommand;
    } else {
      return self.twitterLoginCommand;
    }
  }];
}

-(RACCommand *) cancelCommand {
  if (!_cancelCommand) {
    @weakify(self);
    _cancelCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
      @strongify(self);
      [self.authenticationDisposable dispose];

      return [RACSignal empty];
    }];
  }

  return _cancelCommand;
}

- (RACCommand *)twitterLoginCommand {
  if (!_twitterLoginCommand) {
    @weakify(self);
    _twitterLoginCommand = [[RACCommand alloc]initWithSignalBlock: ^RACSignal *(UIButton *button) {
      @strongify(self);

      RACSignal * signal = [self twitterSignInSignal];
      self.authenticationDisposable = [signal subscribeNext:^(FAUser * user) {
        self.authenticatedUser = user;
      }error:^(NSError *error) {
        self.authenticationDisposable = nil;
      }];
      return signal;
    }];
  }

  return _twitterLoginCommand;
}

- (RACSignal *)twitterSignInSignal {
//Left out, returns a signal with a user
}

Originally I had the twitter command being subscribed to in viewDidLoad and utilizing a takeUntil for the cancel signal (rather than using the disposable and subscribing with the command), but that did not appear to stop the command from executing immediately upon cancellation (the login task would remaining running until the signal processed the takeUntil).

@notxcain

This comment has been minimized.

Show comment
Hide comment
@notxcain

notxcain May 14, 2014

Try this one.

_twitterLoginCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) {
      @strongify(self);
      return [[self 
          twitterSignInSignal] 
          takeUntil:self.cancelCommand.executionSignals];
    }];

RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals switchToLatest];

I suggest you to move command creations to init method

notxcain commented May 14, 2014

Try this one.

_twitterLoginCommand = [[RACCommand alloc] initWithSignalBlock:^(id _) {
      @strongify(self);
      return [[self 
          twitterSignInSignal] 
          takeUntil:self.cancelCommand.executionSignals];
    }];

RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals switchToLatest];

I suggest you to move command creations to init method

@BrettThePark

This comment has been minimized.

Show comment
Hide comment
@BrettThePark

BrettThePark May 14, 2014

That worked like a charm and it all smells good. Thanks, it took me a couple days to get as far as I was, I must have missed something when I tried an approach like that last time.

BrettThePark commented May 14, 2014

That worked like a charm and it all smells good. Thanks, it took me a couple days to get as far as I was, I must have missed something when I tried an approach like that last time.

@notxcain

This comment has been minimized.

Show comment
Hide comment
@notxcain

notxcain May 14, 2014

You're welcome!

notxcain commented May 14, 2014

You're welcome!

@jlg8023

This comment has been minimized.

Show comment
Hide comment
@jlg8023

jlg8023 Apr 10, 2018

RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals switchToLatest];
if change it to
RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals flatten];
better ? @notxcain

jlg8023 commented Apr 10, 2018

RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals switchToLatest];
if change it to
RAC(self.authenticatedUser) = [self.twitterLoginCommand.executionSignals flatten];
better ? @notxcain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment