I've this simple signal for signing in user.
-(RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success) {
if(success)
{
[subscriber sendNext:#(success)];
[subscriber sendCompleted];
}
else
[subscriber sendError:nil];
}];
return nil;
}];
}
and for my button
[[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
doNext:^(id x) {
NSLog(#"In do next");
self.signInButton.enabled = NO;
self.signInFailureText.hidden = YES;
}]
flattenMap:^id(id x) {
NSLog(#"flatten map");
return [self signInSignal];
}]
subscribeNext:^(NSNumber *signedIn) {
NSLog(#"In subscribe");
self.signInButton.enabled = YES;
self.signInFailureText.hidden = 1;
[self performSegueWithIdentifier:#"signInSuccess" sender:self];
} error:^(NSError *error) {
self.signInButton.enabled = YES;
self.signInFailureText.hidden = 0;
}];
It works perfectly until I get an error so I change the password text and press the login button but it does nothing it means it calls only once (the sign in button is enabled)
I came up with an answer using RACCommand
RACCommand *submitCommand =
[[RACCommand alloc] initWithEnabled:signUpActiveSignal signalBlock:^RACSignal *(id input) {
return [[[self signInSignal]
doCompleted:^{
self.signInButton.enabled = YES;
self.signInFailureText.hidden = 1;
[self performSegueWithIdentifier:#"signInSuccess" sender:self];
}] doError:^(NSError *error) {
self.signInButton.enabled = YES;
self.signInFailureText.hidden = 0;
}];
}];
self.signInButton.rac_command = submitCommand;
Try this:
[[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
doNext:^(id x){
NSLog(#"In do next");
self.signInButton.enabled = NO;
self.signInFailureText.hidden = YES;
}]
flattenMap:^id(id x){
NSLog(#"flatten map");
return [self signInSignal];
}]
subscribeNext:^(NSNumber*signedIn){
NSLog(#"In subscribe");
self.signInButton.enabled =YES;
BOOL success =[signedIn boolValue];
self.signInFailureText.hidden = success;
if(success){
[self performSegueWithIdentifier:#"signInSuccess" sender:self];
}
}];
- (RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber){
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success){
[subscriber sendNext:#(success)];
[subscriber sendCompleted];
}];
return nil;
}];
}
Related
The result of the test is always 0, as the code after the getNotificationSettingsWithCompletionHandler: block is executed before notificationsAlertAuthorization value is set in the block.
How do I solve this problem?
in AppDelegate.h:
- (BOOL)testNotificationsAuthorization;
in AppDelegate.m:
- (BOOL)testNotificationsAuthorization {
__block BOOL notificationsAlertAuthorization = 0;
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
if (settings.alertSetting == UNNotificationSettingEnabled) {
notificationsAlertAuthorization = YES;
NSLog(#"settings.alertSetting: YES");
}
}];
// gets executed before notificationsAlertAuthorization is set,
// result is always NO.
if (notificationsAlertAuthorization == YES) {
NSLog(#"Alert Authorization Granted");
return YES;
} else {
NSLog(#"Alert Authorization NOT Granted");
return NO;
} }
in ViewController:
- (IBAction)testForNotificationSettings:(UIButton *)sender {
BOOL result = [[[UIApplication sharedApplication] delegate] testNotificationsAuthorization];
NSLog(#"testForNotificationSettings result: %d", result);}
log:
2016-11-20 17:18:51.221 UserNotificationsTest[11873:536763] Alert Authorization NOT Granted
2016-11-20 17:18:51.222 UserNotificationsTest[11873:536763] testForNotificationSettings result: 0
2016-11-20 17:18:51.223 UserNotificationsTest[11873:536812] settings.alertSetting: YES
You need to use CompletionHandlers in order to get the result, as you are getting the status for Notification in CompletionHandle and before getting the result you are comparing it, thats why you are getting wrong result.
Change your method like this
-(void) testNotificationsAuthorization :(void (^)(BOOL isActive))handler {
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
if (settings.alertSetting == UNNotificationSettingEnabled) {
handler(YES);
NSLog(#"settings.alertSetting: YES");
} else {
NSLog(#"settings.alertSetting: NO");
handler(NO);
}
}];
}
and use it like this
[[[UIApplication sharedApplication] delegate] isNotificationActive:^(BOOL isActive) {
if (isActive) {
NSLog(#"settings.alertSetting: YES");
} else {
NSLog(#"settings.alertSetting: NO");
}
}];
If I add a UIControlEventTouchUpInside signal to a doneButton, and call an API, if the API fails, the catch will be called. But if I try to click the button again, the button control event does not get triggered.
- (void)viewDidLoad {
[super viewDidLoad];
[[[[[self.doneButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
[SVProgressHUD show];
}] flattenMap:^RACStream *(id value) {
return [[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)];
}] catch:^RACSignal *(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
return [RACSignal empty];
}] subscribeNext:^(id x) {
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}];
}
I think this thread will help. https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1218
A signal will automatically be unsubscribed to if it fails / errors. You can use - retry, however that will simply keep trying your operation until is doesn't fail, which, if there is a perpetual issue will just loop indefinitely.
Wrapping this condition in a flattenMap will capture the issue without unsubscribing the initial rac_signalForControlEvents observation.
See mdieps comment in the thread above on GitHub, and maybe do something like.
[[[[self.doneButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
[SVProgressHUD show];
}] flattenMap:^RACStream *(id value) {
return [[[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)]
catch:^RACSignal *(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
return [RACSignal empty];
}];
}] subscribeNext:^(id x) {
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}];
I've not actually constructed a test with this code. Just guessing based on what you might have in your HttpService Class.
You can use RACCommand to solve this problem.
RACCommand *doneCommand =
[[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString *selected) {
return [[[self updateImageSignal]
doCompleted:^{
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}] doError:^(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}];
}];
self.doneButton.rac_command = doneCommand;
Now create RACSignal that send success and error according to your request.
-(RACSignal *)updateImageSignal {
#weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self)
[[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)
complete:^(BOOL success) {
if(success)
[subscriber sendNext:#(success)];
else
[subscriber sendError:nil];
[subscriber sendCompleted];
}];
return nil;
}];
}
Hope it will help you. And If you have any question then feel free to ask.
For example, My current implementation is like below:
- (RACSignal *)getPlaylist {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[[buttonClickSignal
flattenMap:^(UIButton *sender) {
return [self logInWithUsername:username password:password];
}]
flattenMap:^(NSDictionary *json) {
return [self fetchPlaylistForToken:token];
}]
subscribeNext:^(NSDictionary *json) {
[subscriber sendNext:json];
[subscriber sendCompleted];
}];
return nil;
}];
}
How to return a new signal without using [RACSignal createSignal] method?
Why don't you just return the mapped buttonClickSignal?
I don't see any problems with just this:
- (RACSignal *)getPlaylist {
return [[buttonClickSignal
flattenMap:^(UIButton *sender) {
return [self logInWithUsername:username password:password];
}]
flattenMap:^(NSDictionary *json) {
return [self fetchPlaylistForToken:token];
}];
}
Since you appear to be ignoring errors right now your current implementation will never actually complete if any of the flattenMapped signals error.
I'm trying to create signal that will trigger it side effects on first subscription, and replay events for any further subscriptions. Once the signal sends complete or error I would like to re trigger side effect for next subscription. I've come up with this as solution, but I'm wondering if there is more elegant way to solve this.
#interface ViewController ()
#property (nonatomic, assign) NSUInteger counter;
#property (nonatomic, strong) RACSignal *defferedIncrement;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
}
- (IBAction)buttonDidTap:(id)sender {
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
}
- (RACSignal *)realIncrement
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACDisposable *disposable = [RACDisposable new];
if (disposable.disposed) { return disposable; }
++self.counter;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:#(self.counter)];
[subscriber sendCompleted];
});
return disposable;
}];
}
- (RACSignal *)defferedIncrement
{
if (!_defferedIncrement) {
_defferedIncrement = [[RACSignal defer:^RACSignal *{
return [[self realIncrement] finally:^{
_defferedIncrement = nil;
}];
}] replayLazily];
}
return _defferedIncrement;
}
#end
I'm trying to communicate with the login service and update the UI the reactive way. The thing is that my login service works with delegates and almost every example I find works with blocks.
I wrote a solution that works, but it seems a bit to clunky, I'm not sure if this is the best way:
LoginViewController:
- (void) viewDidLoad
{
[super viewDidLoad];
//Assign the "loginCommand" command to the button. It'll get executed on button pressed and the button is only enabled when the command says so.
self.entrarBtn.rac_command = self.viewModel.loginCommand;
//Subscribe and respond to command's successful signals
#weakify(self);
[self.viewModel.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
[loginSignal subscribeNext:^(id x) {
#strongify(self);
[self.viewPresenter enterMainNavigation];
}];
}];
//Subscribe and respond to command's error signals
[self.viewModel.loginCommand.errors
subscribeNext:^(NSError* error) {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"ERROR" message:[NSString stringWithFormat:#"Error: %#", error.localizedDescription] delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}];
}
LoginViewModel:
- (id)init
{
self = [super init];
if(self) {
self.loginCommand = [[RACCommand alloc] initWithEnabled:self.enableLoginSignal
signalBlock:^RACSignal *(id input) {
return [self loginSignal];
}];
}
return self;
}
- (RACSignal *)loginSignal
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//LOGIN OK
RACDisposable* loginOKDisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginOK)
fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
PositionGlobalService *positionGlobalService = [PositionGlobalService sharedInstance];
positionGlobalService.delegate = self;
[positionGlobalService getPositionGlobal];
}];
//GETTING USER INFO DELEGATE THEN SEND THE COMPLETED SIGNAL
RACDisposable* positionOKDisposable = [[self rac_signalForSelector:#selector(positionGlobalServiceDidReceivePositionGlobal)
fromProtocol:#protocol(PositionGlobalServiceDelegate)] subscribeNext:^(id x) {
[subscriber sendNext:nil];
[subscriber sendCompleted];
}];
RACDisposable* positionErrorDisposable = [[self rac_signalForSelector:#selector(positionGlobalServiceDidReceivePositionGlobalError:)
fromProtocol:#protocol(PositionGlobalServiceDelegate)] subscribeNext:^(id x) {
NSError* error = [NSError errorWithDomain:LoginErrorDomain code:LoginErrorGettingUserInfo userInfo:nil];
[subscriber sendError:error];
}];
//ERRORS
RACDisposable* loginKODisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginKO)
fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
NSError* error = [NSError errorWithDomain:LoginErrorDomain code:LoginErrorKO userInfo:nil];
[subscriber sendError:error];
}];
RACDisposable* deniedDisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginKOAccessDenied)
fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
NSError* error = [NSError errorWithDomain:LoginErrorDomain code:LoginErrorAccessDenied userInfo:nil];
[subscriber sendError:error];
}];
RACDisposable* connectionErrorDisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveConnectionError)
fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
NSError* error = [NSError errorWithDomain:LoginErrorDomain code:LoginErrorConnectionError userInfo:nil];
[subscriber sendError:error];
}];
RACDisposable* genericErrorDisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveGenericError:)
fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
NSError* error = [NSError errorWithDomain:LoginErrorDomain code:LoginErrorGenericError userInfo:nil];
[subscriber sendError:error];
}];
LoginService *loginService = [LoginService sharedInstance];
loginService.delegate = self;
[loginService checkLogin:self.usuario withPassword:self.password documentType:LoginDocumentTypeNIF saveLogin:YES];
return [RACDisposable disposableWithBlock:^{
[loginOKDisposable dispose];
[positionOKDisposable dispose];
[positionErrorDisposable dispose];
[loginKODisposable dispose];
[deniedDisposable dispose];
[connectionErrorDisposable dispose];
[genericErrorDisposable dispose];
}];
}];
}
As you can see there's a bunch of code that is almost the same for every delegate, that's why I'm unsure whether this is the best way to do it.
Your view looks good, but I have a few suggestions for the model. The main point is that I'd simplify the signals on the LoginService and PositionGlobalService by moving them into the respective classes for those services. You can then merge the errors and create a single signal, e.g.:
#interface LoginService : SomeSuperclass<LoginServiceDelegate>
- (RACSignal *)loginWithID:(NSString *)userid password:(NSString *password);
#end
#implementation LoginService()
- (RACSignal *)loginWithID:(NSString *)userid password:(NSString *)password {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACDisposable *errorDisposable = [[RACSignal merge:#[[[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginKO) fromProtocol:#protocol(LoginServiceDelegate)] mapReplace:[NSError errorWithDomain:LoginErrorDomain code:LoginErrorKO userInfo:nil]],
[[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginKOAccessDenied) fromProtocol:#protocol(LoginServiceDelegate)] mapReplace:[NSError errorWithDomain:LoginErrorDomain code:LoginErrorAccessDenied userInfo:nil]],
[[self rac_signalForSelector:#selector(loginServiceDidReceiveConnectionError) fromProtocol:#protocol(LoginServiceDelegate)] mapReplace:[NSError errorWithDomain:LoginErrorDomain code:LoginErrorConnectionError userInfo:nil]],
[[self rac_signalForSelector:#selector(loginServiceDidReceiveGenericError) fromProtocol:#protocol(LoginServiceDelegate)] mapReplace:[NSError errorWithDomain:LoginErrorDomain code:LoginErrorGenericError userInfo:nil]]]] subscribeNext:^(id x) {
[subscriber sendError:x];
}];
RACDisposable *loginDisposable = [[self rac_signalForSelector:#selector(loginServiceDidReceiveLoginOK) fromProtocol:#protocol(LoginServiceDelegate)] subscribeNext:^(id x) {
[subscriber sendNext:x];
[subscriber sendCompleted];
}];
[self checkLogin:userid withPassword:password documentType:LoginDocumentTypeNIF saveLogin:YES];
return [RACDisposable disposableWithBlock:^{
[errorDisposable dispose];
[loginDisposable dispose];
}];
}
}
#end
Then, your login function can become something like this (though I'd probably rename this function since it does two things):
- (RACSignal *)loginSignal
{
return [[[LoginService sharedInstance] loginWithID:self.usuario password:self.password] then:^RACSignal *{
return [[PositionGlobalService sharedInstance] getPositionGlobalSignal];
}];
}];