Review of OH!YA, INC -Part III (iOS)

This chapter illustrates on the tips, tools/libraries and difficulties of iOS development.

Design Style

MVC vs MVVM

The choice of MVC or MVVM depends on the company and the stage/phrase of product development. I’ve went to over 12 onsite interviews recently and each company has its favorite design style/pattern.

To sum up, if you are at the early stage, sticking to MVC is a smart move. However, if the product is quite mature, then go for MVVM to make your iOS structure more powerful. There are numerous reasons and arguments, among which, I believe, the major one is that MVC usually requires less code, while MVVM consumes much more code to implement. In addition, MVVM is quite powerful in observing/binding pattern between the View/ViewModel and Model.

However, even for MVC, we do have some strategies to make your UIViewController less clumsy. The most popular way is to separate the UIView and UIViewController. And for UIViewController, using extensions to separate different functions/controls.

e2

Figure 1. Split the UIView and UIViewController

e1

Figure 2. Using extension to split the logics in the UIViewController

Storyboard VS UI in Coding

As Version 1 shows, we are using the storyboard, but it doesn’t work out for us. The main reason is setting up constraints in the storyboard cause too many operations, and if you want to check the constraints of a component, you have to deep dive into inspector.

Another disadvantage is that it is not easy to change color/attributes in general for multiple pages. And some developers are likely to draw segues on the storyboard, making it even more complex to sync up and review.

Therefore, starting from Version 2, we refactored the product and started to use SnapKit to code the UI layout, which is an easy-to-study and -use library, allowing you spend less code to setup layout and constraints. All the components are clear and easy to inherit and setup. Also, implementing UI in coding will give you a deep understanding of the properties, delegates and usage about the UI components.

Core Data

Using core data or not is service dependent. For us, we didn’t use it. But most health tracking apps or personalized service apps may heavily depend on disk storage. Below are the levels of storage in our apps.

NSUserDefault – store computing simple data

Keychain — store login information (username, token, refresh_token)

NSCache —- cache downloading images

UITableView Optimize

It is a good interview question, almost every company I have interviewed with asked me to write a tableView with UI layout in coding. I will specify some key points that need your attention in future articles.

Some strategies:

  1. Avoid too much CAlayer level rendering.
    1. Using shadowPath to add Shadow, using shouldRasterize
    2. Avoid opaque layers
    3. Flatten the layers/subviews
  2. Cell Height Optimize
    1. Constant cell height: we don’t need numberOfRowsInSection delegate method. Instead, just set the rowHeight property to the tableView. This could save computing time.
    2. Dynamic cell height: computing the cell height and save it to the cache for reusing. Someone even suggests saving to the backend database.
  3. Lazy Loading
    1. Page based: If we consider the cells displayed on the tableView as one page, we can detect when user scrolling to the bottom of the current screen and display them according. It is an easier way to do lazy loading and we are using this way.
  4. Threshold based: once the cell number is over some threshold, we will load the next pages, which means requests the API before user scroll to the bottom of the screen.

Network and Deserialization

The most comfortable way for us to do JSON deserialization is using libraries including ObjectMapper, AlamofireObjectMapper, SwiftyJSON.

Declare the Model

p1

Figure 3. Model declaration

General network function

Screen Shot 2017-08-20 at 8.36.41 PM

Figure 4. General network function

Network Function Calling

p3

Figure 5. call the network function

It is clear that, after you have defined the mappable model (Model Profile here), and implement a general networking function, you just need to call the networking function and its return value will be an instance of the model. This deserialization flow saved me tons of code lines and developed a good decoupling mechanism.

Transaction Flow

In our product, the transaction flow consists of three steps in general by Using Stripe API:

  1. Charge fees from the customer app (meals price + Stripe fee)
  2. The fees will be deducted from Stripe Fee and saved to the Oh!ya company’s Stripe Account.
  3. According to Stripe Teams,  the fees will be transferred out to the Cook App in 2 days.

stripe

Figure 6. Oh!ya transaction flow (from Stripe)

Customer Side:

  1. Validation the credit card in client side (iOS):
    1. Ask user to provide credit card number, CVC and expiration date.
    2. The information will be sent to the Stripe API.
    3. The Stripe API will send back a stripe_token if the validation is success.
  2. Dealing the backend
    1. Send the stripe_token to the backend server.
    2. Call Stripe.Customer.Create API and get a stripe_customer object response.
    3. Save the stripe_customer.stripe_id to the Payment model in the database.
    4. Never save credit card information to our database. The Stripe will take care of them
  3. Charge transaction
    1. In each transaction, Stripe will response a transaction_id, save it to the database and create a Transaction model row. It is useful for future analysis and processing. Especially, if user cancel this transaction later, we can use the transaction_id quickly calling relevant Stripe API to refund the fee.

Cook Side:

  1. Validation the debit card in cook side (iOS):
    1. Ask user to provide debit card number, CVC and expiration date, birthday, name, address, SSN. On cook side, Stripe need more personal information to validate the debit card and create a Stripe Account.
    2. Send the information to the Stripe API.
    3. The Stripe API will sent back a stripe_token if the validation is success.
  2. Dealing the backend
    1. Send the stripe_token and personal information to the backend server.
    2. Calling Stripe.Account.Create API and get a account object response.
    3. Save the account.stripe_id to the Payment model in the database.
  3. Transferring Out
    1. When transferring out, save the transaction_id to the database and create a Transaction model row.

In conclusion, these are indispensable parts in the platform products. However, if we thinking of integrating this procedure to the Interaction System of the Customer and Cook App, the entire system design is becoming more and more complex. I will discuss the Interaction System in the next chapter.

Token Integration

We are using Oauth2 Restful API that will grant token and refresh_token for the user to perform API REST requests. After integrating the Facebook login API, however, it will response another token. The problem is: how to integrate both tokens?

login.png

Figure 7. Login page

Below are the steps to complete the flow:

  1. Once the user log in successful on the iOS, Facebook API will grant a facebook_token.
  2. Sent the facebook_token to the backend.
  3. Authenciate the facebook_token by calling https://graph.facebook.com/me
    1. def get_facebook_id(access_token):
          params = {'fields': 'id', 'access_token': access_token}
          text = requests.get("https://graph.facebook.com/me", params=params).text
          json = simplejson.loads(text)
      
          return json["id"]
  4. Backend should create a backend_token and backend_refresh_token and response back to the iOS clients.
  5. iOS clients save the backend_token and backend_refresh_token in the keychain.
  6. Upon the next time user log in, the iOS client App will fetch the backend_token from the keychain and try to login.
  7. If it fails, use backend_refresh_token to redo login.
  8. If it fails, ask the user to manually login.

Note the backend_token has an expired property that usually set as one month.

Text Processing

Pay attention to the length limit of the UITextView/UITextField. Add a count down warning would be a wise choice.

Once during the internal testing, the app was crashed by one of our team member. It kind of weird because I hold a strong belief the system and user logic is robust. After carefully debug, we found he entered Chinese in UITextView. Well, keep in mind that we need to stick to UTF-8 encoding in the backend.

There is price UITextField setup in the Cook app, which needs the limitation of the max price and positive integer check.

Similarly, password/email general format check need to taken care of.

 

Many thank your reading! The next chapter will unveil our interaction system design.

 

 

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s