Solving a Data Flow Diagram Using PromiseKit

Solving a Data Flow Diagram Using PromiseKit

Before they start coding, most programmers begin by creating a data flow diagram to know exactly how each piece of code is supposed to work (in other words, how it should behave). If the behavior of the application is not complex, you do not have to sketch it, although it is a good habit. However with more complicated behavior, it is hard to properly code such flow.

Data flow diagram – dedicated blocks for each function

We sit down and code, everything is fine… but how to write it down so that it makes sense? And what to do when we need to call something asynchronously (e.g., download some data from the API)? It is certainly a good idea to divide it into items and save them each in a separate method. This way we will create code blocks that are responsible for specific functionalities, which will make the whole thing cleaner.

Conditional expression – good or bad idea?

We already have code blocks, so now, how do we put them together? We have conditional blocks here, so I will wrap the whole thing in if / else and it should be ok, right? Well, yes and no. Packaging our blocks in a conditional expression will work, but it will not look good, it will simply be not very readable. Example:

class HelloDiagramFlow {
    func flow() {
        if isSessionValid {
            userInfoBlock {
                
            }
        } else {
            refreshSession { sessionKey in
                userInfoBlock {
                    
                }
            }
        }
    }
    
    func userInfoBlock(closure:()->()) {
        getUserInfoAsync { userInfo in
            if isPremiumUser {
                if isPremiumPlanExpired {
                    
                } else {
                    
                }
            } else {
                
            }
        }
    }
    
    func getUserInfoAsync(closure:(UserInfo)->()) {
        
    }
func refreshSession(closure:(String)->()) {
        
    }
}

As you can see, already with the first few blocks, the code becomes quite unreadable, and our diagram is not particularly complicated. How can we fix it? We can use the so-called “promise” (I recommend to look here). Ok, but how do we go about it?

Creating a diagram with PromiseKit

We will begin by packing our blocks into our “promises” using PromiseKit.

// -- 1
    //
    fileprivate func sessionValid() -> Promise {
        return Promise{fulfill, reject in
            fulfill(helper.sessionExpired)
        }
    }
    
    // -- 2
    //
    fileprivate func refreshToken() -> Promise {
        return firstly {
            Promise(value: self.helper.sessionToken)
            }.then { token -> Promise in
                debugPrint("- Token refreshed: \(token)")
                return self.getUserInfo()
        }
    }
    
    // -- 3
    //
    fileprivate func getUserInfo() -> Promise {
        return Promise{fulfill, reject in
            self.helper.queue.async {
                for _ in 0...1000000000 {}
                DispatchQueue.main.sync {
                    fulfill(self.helper.userInfo)
                }
            }
        }
    }
// -- 4
    //
    fileprivate func checkAccountType() -> Promise {
        if let userInfo = userInfo {
            debugPrint("- Account type checked: \(userInfo.accountType)")
            switch userInfo.accountType {
            case .premium:
                return self.premiumAccountFlow()
            case .regular:
                return self.regularAccountFlow()
            }
        } else {
            return Promise{fulfill, reject in reject(NSError.cancelledError())}
        }
    }
    
    // -- 4.1
    //
    fileprivate func premiumAccountFlow() -> Promise  {
        return firstly {
            self.isPremiumPlanExpired()
            }.then { expired -> Promise in
                if expired {
                    return self.renewPremiumAccount()
                } else {
                    return self.premiumUserAccount()
                }
        }
    }
    
    // -- A
    //
fileprivate func regularAccountFlow() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.regular)
            }
            fulfill()
        }
    }
    
    // -- B
    //
    fileprivate func premiumUserAccount() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premium)
            }
            fulfill()
        }
    }
    
    // -- C
    //
    fileprivate func renewPremiumAccount() -> Promise  {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premiumRenew)
            }
            fulfill()
        }
    }
    fileprivate func regularAccountFlow() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.regular)
            }
            fulfill()
        }
    }
    
    // -- B
    //
    fileprivate func premiumUserAccount() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premium)
            }
            fulfill()
        }
    }
    
    // -- C
    //
    fileprivate func renewPremiumAccount() -> Promise  {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premiumRenew)
            }
            fulfill()
        }
    }
    fileprivate func regularAccountFlow() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.regular)
            }
            fulfill()
        }
    }
    
    // -- B
    //
    fileprivate func premiumUserAccount() -> Promise {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premium)
            }
            fulfill()
        }
    }
    
    // -- C
    //
    fileprivate func renewPremiumAccount() -> Promise  {
        return Promise {fulfill, reject in
            if let closure = finishedClosure {
                closure(.premiumRenew)
            }
            fulfill()
        }
    }
    // -- 5
    //
    fileprivate func isPremiumPlanExpired() -> Promise {
        return Promise{fulfill, reject in
            fulfill(self.helper.premiumExpired)
        }
    }
    

As for the asynchronous data call, to make things simpler we do not connect to any API, but just start a loop with a large counter. However, instead of using a loop you can also put there a call to API, by using URLSession, Alamofire or something like this.

Okay, the code is nicely packaged in blocks, but it will not work yet, especially since it is not much different from the usual method. Well not exactly, see below an example of calling the whole flow, for better encapsulation I packaged this flow into a method that outputs the data we are interested in:

func start(closure:@escaping (ViewType)->()) {
        finishedClosure = closure
        debugPrint("Start")
        firstly {
            return self.sessionValid()
            }.then { sessionExpired -> Promise in
                debugPrint("- Is session expired: \(sessionExpired)")
                if sessionExpired {
                    return self.refreshToken()
                } else {
                    return self.getUserInfo()
                }
            }.then { userInfo -> Promise in
                debugPrint("- User info downloaded: \(userInfo.email)")
                self.userInfo = userInfo
                return self.checkAccountType()
            }.always {
                debugPrint("Promises chain finished - hide activity indicator etc.")
            }.catch { error in
                debugPrint(error)
        }
    }
    

Boom! And that’s it? Isn’t it more readable? You can also easily add error handling anywhere in the chain of calls. So, I encourage you to get acquainted with PromiseKit or other libraries using “promises”.

You can download running code here (Swift 3.0).

Learn more

Bitrise Tests Made Easier: Update JIRA Issues with Build Number and Forget About Delays in QA Testing

When you trigger Bitrise build with changes and forget to tell QA specialists some essential information, there are two likely scenarios. You waste time waiting for the update from the tester, only you don’t know it’s never coming. Or you get so many questions about builds that you can’t keep up. Sounds familiar? If so, let us tell you about the JIRA issue update that keeps workflow in order.

Read more

How to Send iOS Notifications in Different Languages? Guide to Dynamic Localization

The best way to set a connection between a user, provider, and an app? System notifications. They allow users to get the latest news in no time. This solution is easy to implement in apps dedicated to one market. But it gets complicated when the messages must be displayed in many languages. In such cases, the dynamic localization of remote notifications can be a real game-changer.

Read more

WebSockets on iOS – Real-time Communication That Doesn’t Slow Down the App

When you want to download data for the app, you probably use API RESTful interface. All it takes is to ask the server for the data and that’s it! This method works well when the app doesn’t need permanent access to new information. But what to do if the content has to be updated in real-time? A delay can make it impossible for the user to buy an item or make a sports bet, for example. But there’s a solution – WebSockets on iOS. Check out why you should implement them, and how to do it.

Read more

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2017

Nomination in
M-COMMERCE category

Logo Legalni bukmacherzy

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2020

Nomination in SPORT & RECREATION category

17

client reviews

Clutch logo