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

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

Promises on the Example of PromiseKit in Objective-C

Sooner or later every programmer encounters the problem of synchronous execution of certain actions. For example: get user information from the API, parse server response, save data to the database, update the view, and many others. To make it even more, at some of these stages you still need to deal with error handling. What should you do exactly?

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 2021

Winning app in
EVERYDAY LIFE

Nagroda Legalnych Bukmacherów

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2023

Winning app in MCOMMERCE DEVELOPMENT

24

client reviews

Clutch logo