• Jennifer Eve Vega

RxSwift: Creating Observables + Traits



In the previous post, I have only mentioned different, sort of "predefined" Observable operators. This time, I'd like to show how you can create your own Observables and define all the events that you would like to be emitted to your subscribers.


Example:

func creatingObservables() -> Observable<String> {
  Observable<String>.create { (observer) -> Disposable in
   observer.onNext("hello")
   observer.onNext("world")


   observer.onCompleted()
 
   return Disposables.create()
  }
 }

And if we have a subscriber to the observable above, it will look like this:

self.creatingObservables()
 .subscribe { (event) in
   print("event: ", event)
 }
 .disposed(by: bag)

And, the console will print the following:

event: next(hello)
event: next(world)

Or, you can do this, too:

self.creatingObservables()
 .subscribe { event in
   print("event: ", event)
 } onError: { error in
     debugPrint("error: ", error.localizedDescription)
 } onCompleted: {
     debugPrint("completed")
 } onDisposed: {
     debugPrint("disposed")
 }
 .disposed(by: bag)

And, the console will print the following:

event: hello
event: world
"completed"
"disposed"

Let's update the first Observable function that we created:

private func creatingObservables() -> Observable<String> {
  Observable<String>.create { (observer) -> Disposable in
   observer.onNext("hello")

   observer.onCompleted()
 
   observer.onNext("world")
 
   return Disposables.create()
   }
 }

What do you think will happen?


Since we already sent out a "completed" event, the "world" will no longer be emitted to the subscribers. On your console, you will only see the "hello" string being printed out.

event: hello
"completed"
"disposed"

Same goes when you send an error:

private func creatingObservablesWithError() -> Observable<String> {
  Observable<String>.create { (observer) -> Disposable in
   observer.onNext("hello")
   observer.onError(MyError.anError)
   observer.onNext("world")
 
   return Disposables.create()
   }
 }

Subscribe to the Observable above with error:

self.creatingObservablesWithError()
 .subscribe { (event) in
  print("event: ", event)
 }.disposed(by: bag)

Since we sent out an error, the "world" will no longer be emitted to the subscribers, as well. And on your console, you will see something like this:

event: next(hello)
event: error(anError)

No onCompleted or onError event

When you don't add any of these events, you have just created a memory leak. Because the observable will never complete, and the disposable will never be disposed.


Traits

There are 3 kinds of traits in RxSwift: Single, Completable and Maybe. Please note that how I use these traits are my personal preferences and experience, and may/may not necessarily be for these purposes only.

  • Single

  • Only emits onNext or onError events.

  • Useful for one-time processes that will either succeed of fail. I usually use Singles when doing API requests.

  • Completable

  • Only emits onCompleted or onError events

  • No onNext (values)

  • I sometimes use this for API requests that only returns a boolean value if it has succeeded, or not.

  • Can also be used when you need user permission, like PushNotifications, or Location, or LocalAuthentication, or even custom alerts that only accepts Yes / No.

  • Maybe

  • Can either emit onNext, or an onCompleted or onError events

  • Can be used in an operation that could succeed or fail, and can also send values on success, or not.


Examples:


Single - I wanted to concatenate strings as long as they are not empty strings.

func testConcatenateStrings(first: String, second: String) -> Single<String> {
  return Single<String>.create { single in
   if !first.isEmpty && !second.isEmpty {
     single(.success("\(first)\(second)"))
   } else {
     single(.error(MyError.emptyString))
   }
 
   return Disposables.create()
   }
 }

Subscriber:

testConcatenateStrings(first: "Jen", second: "nifer")
 .subscribe { mergedText in
  debugPrint(mergedText)
 } onError: { error in
  debugPrint("error: ", (error as! MyError).localizedDesciption)
 }
 .disposed(by: bag)

Console:

"Jennifer"

Now, if one of the strings is empty, the console will print:

"error: " "Empty String"

Single API Call Sample:

func testAPICall(userId: String) -> Single<UserInfo> {
 return Single<UserInfo>.create { [weak self] single in
   let request = UserInfoRequest()
   request.id = userId
 
   if let future = self?.client.getUserDetails(request).response {
     future.whenSuccess { (response) in
       single(.success(UserInfo(firstname: response.firstname,
                                 lastName: response.lastname)))
     }
     future.whenFailure { (error) in
       single(.error(error))
     }
   }

    return Disposables.create()
   }
}

Completable - for user permission requests, local authentication sample.

func authenticate() -> Completable {
   return Completable.create { (completable) -> Disposable in
      let localAuthenticationContext = LAContext()
       localAuthenticationContext.localizedCancelTitle = "Cancel"
       var authorizationError: NSError?
       let reason = "The Reason"


    if localAuthenticationContext.canEvaluatePolicy(LAPolicy.deviceOwnerAuthentication, error: &authorizationError) {
 localAuthenticationContext.evaluatePolicy(LAPolicy.deviceOwnerAuthentication, localizedReason: reason) { (success, evaluationError) in
       if success {
          completable(.completed)
       } else {
 completable(.error(LocalAuthenticationError.failedToAuthenticate))
       }
    }
    } else {
       // No Password/Biometrics setup in Device
       completable(.completed)
    }

    return Disposables.create()
 }
 }

Maybe - for the sake of making an example, it will either return a success(value), or a completed or an error.

func testMaybeOddEven(number: Int) -> Maybe<String> {
  return Maybe<String>.create { maybe in
   if number > 0 {
     if number % 2 == 0 {
       maybe(.success("Even number: \(number)"))
     } else {
       maybe(.success("Odd number: \(number)"))
     }
   } else if number == 0 {
     maybe(.completed)
   } else {
     maybe(.error(MyError.invalidNumber))
   }
 
   return Disposables.create()
 }
}

Subscriber:

testMaybeOddEven(number: 0)
 .subscribe { result in
  debugPrint(result)
 } onError: { error in
  debugPrint((error as! MyError).localizedDesciption)
 } onCompleted: {
  debugPrint("completed")
 }
 .disposed(by: bag)

81 views0 comments

Recent Posts

See All