Kaya Thomas

Jul 10, 2018 ⋅ 5 min read

Getting Stuck

Where’s the first place you go to when you get stuck on a programming problem? Stack Overflow most likely, but what if there’s nothing there to help you? Do you go to Medium in hopes someone wrote a blog post about that very obscure thing you’re working on? Well, this happened to me recently when I was finishing the last part of refactoring We Read Too’s suggestion view.

Working full time as a developer is very different than working on a solo project. At work you have your team to bounce ideas off of and if you get stuck you often have several other people to help you figure out your issue. When you want to merge new code into the app, you’ll do a code review with one of your teammates which hopefully teaches you how to improve your approach. Being on a team of other programmers helps you grow as a programmer. Side projects on the other hand are sometimes done solo. How can you make sure you’re still growing while working alone?

You have to ask for help. It may sound simple, but asking for help can be daunting. Working alone does give you the flexibility to make whatever decisions you please but if you get stuck it can be very frustrating which is why a lot of side projects never get finished. Many folks in the development community who are passionate about their work on are willing to answer your questions if you just reach out, whether it’s by email or Twitter DM. If that doesn’t work there’s always Stack Overflow or publicly asking a question on Twitter or in a forum.

So here’s where I got stuck. I needed to create an Observable<String> that my view controller would subscribe to in order to display an alert that shows the user whether or not sending the suggestion form worked. I decided to use RxFirebase to write the form to my database, but RxFirebase returns an observable of a database reference.

import CodableFirebase
import RxFirebase
import RxSwift
import Firebase

class FirebaseService {
    func processForm<T : Encodable>(data: T, pathString: String, id: String) -> Observable<DatabaseReference> {
        let ref = Database.database().reference()
        let data = try! FirebaseEncoder().encode(data)
        return ref

As shown above, I created a class, FirebaseService, where I’m going to add all the methods that interact directly with my Firebase database. My point of confusion was related to the Observable<DatabaseReference>, I know that if the write transaction succeeds then the observable will trigger an onNext event and if it fails the onError event will trigger. The issue is, I don’t want my view controller to subscribe to the Observable<DatabaseReference>, I want that to be abstracted and only have my view model return the success or error string to the view controller.

Now I could get these events by subscribing to that observable in my view model, but my view model does not have a dispose bag (nor do I want it to, since I want my view model to just provide observables to a consumer, the view controller, which does the subscriptions). So how do I get my view model to consume the onNext and onError events without subscribing? I didn’t know. So I decided to ask Shai Mishali, a well-known iOS developer in the RxSwift community. He was incredibly helpful and provided me with suggestions on how I can use PublishSubject, flatMap, materialize and RxSwiftExt to solve my problem.

From his suggestions here’s what I came up with for my view model’s intializer:

init(service: FirebaseService = .init()) {
  let formData = Observable.combineLatest(title, author, category)
  let timestamp = "\(NSDate().timeIntervalSince1970)"
  let request = sendButtonTapped
    .distinctUntilChanged { $0 == $1 }
    .flatMapLatest { service.processForm(data: Suggestion(title: $0, author: $1, category: $2), pathString: "suggestions", id: timestamp).materialize() }
  successMessage = request.elements()
    .map { _ in
         "Successfully sent"
  errorMessage = request.errors()
    .map { "An error has occured: \($0)" }

Let’s break it down. Before the view model had an empty initializer but now we initialize it with my FirebaseService class as a parameter. The formData combines all inputs into one Observable<(String, String, String)> which will come from text fields and a picker view. Title, author and category are all PublishSubjects. A PublishSubject emits items to a subscriber only after they’ve subscribed. I was using BehaviorRelay subjects before, but because my text fields have no initial value, I can use PublishSubject.

We make the request by using sendButtonTapped which is a PublishSubject<Void>. The withLatestFrom operator gets the latest item emitted by a given Observable. To ensure that we don’t make multiple request for the same data, I am using the distinctUntilChanged operator which suppresses duplicates. Then we use the flatMapLatest. Here’s a diagram from bacon.js that shows the difference between flatMap and flatMapLatest:

Photo of flatMap diagram.Photo of flatMapLatest.

flatMap takes each emitted item and turns it into a new stream whereas flatMapLatest overwrites the previous stream so there is only one stream to subscribe to.

The data I’m passing into the processForm method is a simple Encodable struct I created so it can be turned into JSON and sent to the database. After the Observable<DatabaseReference> is returned from processForm I use the materialize operator which takes the emits items and turn them into events (onNext, onError, onCompleted). The last operator I use is share, which allows you to have multiple subscriptions to one observable.

This is where RxSwiftExt comes in handy. RxSwiftExt is a library that provides convenience operators separate RxSwift in order to avoid bloating the RxSwift library. The two operators we’re using are elements and errors. Defining my outputs, successMessage and errorMessage, you can see why the operators come in handy, now I am able to send the right message to my view controller according to the events emitted from the Observable the RxFirebase method returns.

What's Next

That wraps up the suggestion view (for now!). Next up I have to pull the book data from Firebase and insert it into a collection view. I also want to connect these different views using a Tab Controller so you can tab from Books, Search and Suggestion.