Group members: Krzysztof Lesniewski (krles14)

Introduction

It is a common issue among groups of people to have shared expenses. Tracking those isn’t the simplest task and it is actually quite time consuming. Usually it requires collecting receipts or writing down all expenses and afterwards calculating it in an Excel sheet or manually on a calculator. Expenses are not always well tracked, sometimes forgotten, sometimes no one have time to sum everything up and all in all the debts blur away. I believe that simplifying settling group expenses will help save time and make sure that there are no blurred debts.

I decided to make an iOS application, which will help you to record expenses on the go, when you make them. The process should be as easy as writing the values down in a notepad and it should give you a summary of debts in no time.

Methods

Developing an application is not only about writing the code. You need to know what you have to write and that requires some upfront thoughts and research. The reasearch was started with looking for similar applications in the App Store and Google Play to see if there are any already available. Apps were found through simple search for “group expenses”, “settle”and also navigating to “Similar apps” when seeing a matching application. The best options were preselected by evaulatuing its UI, features and comments. For further evaluation, they were installed on a phone and were tested to see what are the good and bad points about them.

After the market research, 3 people were selected for a semi-structured interview. The interviewees were asked about the problem and how they deal with it, just to be sure that the problem is common and there is no secret, simple solution others are using. They were also asked how they would improve the process and if they have searched for alternatives to what they do currently.

The research allowed to create an initial list of requirements. Alhtough backed by research, the initial list was still a guess, which can be only judged when people review the requirements, something that is best done with the aid of scenarios and early designs or a prototype. This is why the analysis-design-evaluation process is so iterative (Benyon, 2014, p.139). From the initial list, a conceptual model was built, which was then transformed to very early prototypes – hand-drawn screens to visualize the idea and make it easier to evaluate it with others. The prototypes were consulted with the interviewees by showing the designs, explaining its functional features and asking for feeback. This method was chosen mostly to ensure, that requirements were understood correctly and no key feature was missing.

Having interface mock-ups it is easy to understand what is needed to be build. The next step was to transform idea into a working prototype. Such prototype gives not only a great tool for further design evaluation, but also helps to evaluate constraints and find (also technical) obstacles. The prototype was built using the same tools that will be used for the final application, i.e. iOS application development platform with Swift as a language and XCode as a code editor. For building the interface UIKit, while Core Data was  responsible for persistence. The prototype was built sticking to good practices i.e. dividing code in Models, Views and Controllers, as keeping close to final solution helps to identify and resolve problems early, before developing the full scale application.

As mentioned before, prototype is a great tool for evaluation of the user interface, architecture and the development platform. New insights were gathered during the application development and “hallway tests”, where random peers were asked to evaluate if the interface is easy to work with.

Results

Market Research

The market research has revealed that there are already great solutions on the market. Because I am an Android user it was easier to evaluate options available for that platform, rather than iOS. App Store was however browsed as well. For each platform there were at least several options available. The evaluation was very subjective, but there were very good options, but also some which weren’t user friendly. Below 3 most prominent applications are shortly described

Party Calc

Party Calc has a pleasing UI, but at a cost. When adding a payment, it asks you for a category, choosing an icon, date and time. It also has divided adding payment into two steps requiring more taps to complete the task. The second step allows to split the amount across people. The good thing is that it supports all scenarios: multiple payers, even splits, uneven splits, fixed amounts etc. The step 2 screen may be in the beginning confusing, before meaning of all the symbols becomes familiar.

Screenshot_2015-10-18-19-53-15   Screenshot_2015-10-18-19-54-47

Settle Up

Settle Up is the best application I found. It is both available for iOS and Android, yet differing in UI, since iOS version was redesigned. The application has very simple, easy to use interface, which requires very few taps to add a payment. It supports even and uneven splits, multiple payers, adding photos to expenses (e.g. receipts), but also sharing the groups across people, so that everyone in the group can add the expenses. What I also found nice is adding people when creating a group, because you can simply just write names, whereas in other applications you usually had to open a dialog, provide a name, icon/photo, contact information and such, which I consider unnecessary for the purpose. The only thing which could be improved is the interface for setting fixed amounts for splits, as then many UI elements block and suddenly the interface changes behavior. It is also hard to revert it. I would not consider it a big issue. Since I found the application I am using it and I am happy with it. It does the job very well.

Screenshot_2015-10-18-21-13-24   Screenshot_2015-10-12-18-58-17

Abcba

Abcba is a counter example. It has both unpleasing UI and is cluttered with inputs for unnecessary data. It had however good reviews from the days where it was the only one supporting all scenarios and it also has some nice features, e.g. it is easy to add another person to the group while adding a new expense.

Screenshot_2015-10-18-20-07-43    Screenshot_2015-10-18-20-14-11

Interviews

Interviews have revealed that interviewees also had problems with group expenses and handled the problem in a similar manner as I did, i.e. collecting receipts / writing down the payments on the go and then making an Excel sheet at the end to summarize. All have agreed that the process wasn’t great and it required significant effort. When asked about improvement in the process, either small optimizations to current process were proposed or entirely complex solutions. No one of the interviewed people searched for alternatives nor knew about mobile applications easing the task.

Conceptual design

From the research and own preferences a following list of requirements were compiled:

  1. It needs to show how to settle (who owes whom and how much).
  2. It is important, that adding an expense will require as few taps as possible, so that it it is perceived as quick action, encouraging to add them on the go.
  3. It should support even, uneven and fixed splits, currencies and grouping. Even splits are when expense is divided equally among all group members. Uneven split is when expense is not divided equally, e.g. one of group members did not participate or had larger share. Fixed split is when some shares are defined with exact amounts, e.g. when it is not possible to easily distribute expense proportionally.
  4. It should work offline.
  5. It would be nice to have a calculator at hand when inserting the values (e.g. to subtract an amount from the bill)

The application should be centered around adding new payments. It should be as quick and easy as possible. The screen from Settle Up had the most minimal form, it was also well laid out starting from the amount paid and splits, asking for the name at the end, since it is easiest to keep in your memory, while numbers tend to fade away quickly. In contrast to Settle Up I wanted to add multiple payers to be visible at all times, whereas in Settle Up you have to choose “Multiple payers” and click a button to open a form to set the payers and amounts. The idea was that you either would have an edit icon, which would allow you to edit the list, or always leave a blank field or plus at the end, which would let you to add another payer, where by default only one would be defined.

New Doc 4_2   New Doc 4_8New Doc 4_3

There were a few options considered for switching between coefficients and fixed amounts. I thought it would make most sense to just switch the inputs, since the modes differ much in behavior and therefore it is hard to present it on the same set of controls without cluttering the UI. It was also considered to use one input per screen, but it was discarded, since it forces user to input data in a specific order and doesn’t give an overview, which helps users to quickly check if all values were provided correctly, before payment is added.

Whenever you have a numeric field, a calculator should popup easing entering the values, since it has a keypad, as well as it could help to compute values if necessary.

Right after the new payment screen, the importance is in being able to see the past expenses (to verify or edit) and a summary. The list of expenses should simply just list them, with amounts and currency used. Information about who paid might be also useful for identification or quick verification. The summary (debts) should just list who owes, how much and to whom. To navigate from the list to the summary an equal sign icon was introduced in top right corner, next to the plus giving it a semantic similar to a calculator where you can add values and see the sum in the end.

New Doc 4_1   New Doc 4_4

User should be also able to switch between groups and create new groups. To see the groups one could use a side menu dragged from the side or revealed after clicking a burger icon, or tabs or nested navigation could be used. When groups are displayed in a table it is easier to edit them, but in a side menu it is quicker to switch or just see the groups and other application screens (e.g. settings) without leaving the current group. For the prototype a table view was chosen.

New Doc 4_7 New Doc 4_5New Doc 4_6

The process of creating group should be easy and no extra information about users other than name should be requested, since only the name is needed for the purpose.

Design evaluation

The prototypes were consulted with the interviewees by showing the designs, explaining its functional features and asking for feeback. Interviewees asked several questions about the design, e.g. why if it was meant to be simple there are so many input fields, and new features, e.g. adding pictures of receipts and export afterwards to proove expenses when requesting insurance after an accident on vacations or to settle a bussiness trip with the company.

Questions about the design were rather a case for further design explanation due to limitation of pen and paper technique. In given example, despite the form has several inputs, at most times all but two fields (amount and name) will be correctly prefilled, allowing to enter expense fast, but also allowing to easily customize it when needed. All the inputs are however necessary to correctly answer how to settle.

The proposed features, despite they could provide additional value, weren’t related to the core goal of the application, which is to answer how to settle. New features should be considered carefully, as one of the requirements was to speed up the process of adding expenses by not asking for unneccessary details. Especially for the initial prototype, it was more important to get the settling right, i.e. build the minimum viable product.

Other than that, interviewees considered the design good. It was then accepted for building a prototype, so that it can be further evaluated.

Building a prototype

The source code can be found on BitBucket. Below is a video with a short overview of the built application. Further down different components of the solution are described.

Model

Four classes were distilled: Group, Member, Payment and MemberShare. Group contains members and payments. Payments are related to members in a many to many relation through MemberShare, which also defines an optional share amount. MemberShares are used differently depending on payment splits type – coefficients or fixed amounts. When payment uses coefficients, MemberShare must have a share value, where in fixed amounts mode share should have a value when it was provided and nil otherwise (where it should be automatically calculated). MemberShares are also used for contributions to the payment, despite the fact contributions also require value to be provided. It was however simplified to a single object for the data store just to simplify the model. The values can be remapped to more adequate classes before exposing to the rest of the application.

Screen Shot 2015-12-03 at 04.09.21Core Data data model

You can notice that in Core Data model some properties are prefixed with cd. These properties are then hidden in the managed object and exposed under different type, compliant with the rest of the application. See examples below:

// Related objects as typed array
@NSManaged private var cdContributions: NSOrderedSet
var contributions: [MemberShare] {
    get { return cdContributions.array as! [MemberShare] }
    set { cdContributions = NSOrderedSet(array: newValue) }
}

// NSNumber is used for Core Data boolean values. Rest of the application uses Bool
@NSManaged private var cdUseSharesAsCoefficients: NSNumber
var useSharesAsCoefficients: Bool {
    get { return Bool(cdUseSharesAsCoefficients) }
    set { self.cdUseSharesAsCoefficients = NSNumber(bool: newValue) }
}

Besides the displayed properties, objects define also computed properties to get balance, debts, last used currency etc. which helps to keep the business logic in the model.

Views

The application has scenes for list of groups, new/edit group, list of payments, new/edit payment, debts. Most were rather basic, where table views with custom cells were used in 3 of the scenes. The most challenging to implement was the new payment scene, since the controls to define splits are dynamic – they depend on amount of users in the group, but also can be replaced when switching between using coefficients and fixed amounts.

Screen Shot 2015-12-03 at 04.38.35

The aforementioned controls for getting splits input were defined as custom controls. Describing the simpler one:

  • CoefficientShareView is a control for a single member, which defines a label, text field and a stepper. It wires up text field to the stepper and makes sure no invalid value can be provided in the text field. It exposes properties to access Member and coefficient as an Int. It defines all the Auto Layout constraints from the code. I tried to implement it using .xib file, yet I didn’t succeed and after many trials I decided to implement the control entirely in code.
  • CoefficientSharesView is a control displaying a list of CoefficientShareView controls given list of shares. It exposes a property to access a list of members with inserted coefficients. It is used in PaymentViewController, tied with layout constraints to another UI parts.
The controls for fixed amounts are a bit more complex, since they also implement firing events on data changes, so that the FixedAmountSharesView can react to it ensuring consistency across all the controls, making sure that at least one of the controls will remain flexible (user will not be able to provide a value). It also exposes the shares used by PaymentViewController to put the data in the model.
Example implementation with adding UI elements and Auto Layout constraints from code:
class CoefficientShareView : UIView {

    var memberNameLabel: UILabel!
    var coefficientTextField: UITextField!
    var coefficientStepper: UIStepper!
    
    var member: Member! {
        didSet {
            memberNameLabel.text = member.name
        }
    }
    
    var coefficient: Int! {
        didSet {
            coefficientStepper.value = Double(coefficient)
            coefficientTextField.text = "\(coefficient)"
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    private func setup() {
        createUiElements()
        layoutUiElements()
    }

    private func createUiElements() {
        memberNameLabel = UILabel()
        memberNameLabel.font = UIFont.systemFontOfSize(20)
        addSubview(memberNameLabel)

        coefficientTextField = UITextField()
        coefficientTextField.textAlignment = .Center
        coefficientTextField.keyboardType = .NumberPad
        coefficientTextField.borderStyle = .RoundedRect
        coefficientTextField.clearsOnBeginEditing = true
        coefficientTextField.returnKeyType = .Done
        coefficientTextField.enablesReturnKeyAutomatically = true
        coefficientTextField.addTarget(self, action: "textFieldEditingDidEnd:", forControlEvents: .EditingDidEnd)
        addSubview(coefficientTextField)

        coefficientStepper = UIStepper()
        coefficientStepper.stepValue = 1
        coefficientStepper.value = 1
        coefficientStepper.minimumValue = 0
        coefficientStepper.maximumValue = 99
        coefficientStepper.addTarget(self, action: "stepperValueChanged:", forControlEvents: .ValueChanged)
        addSubview(coefficientStepper)
    }

    private func layoutUiElements() {
        translatesAutoresizingMaskIntoConstraints = false

        memberNameLabel.translatesAutoresizingMaskIntoConstraints = false
        coefficientTextField.translatesAutoresizingMaskIntoConstraints = false
        coefficientStepper.translatesAutoresizingMaskIntoConstraints = false

        memberNameLabel.centerYAnchor.constraintEqualToAnchor(self.centerYAnchor).active = true
        coefficientTextField.centerYAnchor.constraintEqualToAnchor(self.centerYAnchor).active = true
        coefficientStepper.centerYAnchor.constraintEqualToAnchor(self.centerYAnchor).active = true

        coefficientStepper.trailingAnchor.constraintEqualToAnchor(self.trailingAnchor).active = true
        coefficientStepper.leadingAnchor.constraintEqualToAnchor(coefficientTextField.trailingAnchor, constant: 8.0).active = true
        coefficientTextField.widthAnchor.constraintEqualToConstant(38.0).active = true
        memberNameLabel.leadingAnchor.constraintEqualToAnchor(self.leadingAnchor).active = true
    }

    func stepperValueChanged(sender: UIStepper) {
        let newValue = Int(sender.value)
        coefficient = newValue
    }

    func textFieldEditingDidEnd(sender: UITextField) {
        let enteredValue = coefficientTextField.text ?? ""
        let parsedValue = Int(enteredValue)
        if let value = parsedValue {
            coefficient = value
        }
        else {
            coefficientTextField.text = "\(coefficient)"
        }
    }
    
    override func intrinsicContentSize() -> CGSize {
        return CGSize(width: 350, height: 30)
    }
    
    override class func requiresConstraintBasedLayout() -> Bool {
        return true
    }
}

View Controllers

Each scene has its own ViewController, which ties the model with the UI elements. View controllers behind the input scenes also handle validation. Example validation code can be found below:

// View controller defines properties, which extract valid data from the fields. Example:
var amount: Money {
    let amount = Money(value: paymentTotalTextField.text ?? "")
    return amount ?? Money() // Returns parsed amount or 0
}

// Method validating the form
private func getValidationErrors() -> [String] {
    var errors = [String]()
        
    if amount <= Money() { // If value is lower or equal to 0
        errors += ["How much did you pay?"]
    }
        
    if currency == "" {
        errors += ["What currency?"]
    }
        
    if name == ""{
        errors += ["What did you pay for?"]
    }
        
    return errors
}

// Then when Save button is pressed below method is invoked
@IBAction func saveChanges(sender: UIBarButtonItem) {
    let errors = getValidationErrors()
    if errors.count > 0 {
        showErrors(errors) // Opens an alert, which displays the errors
        return
    }
        
    ... // Apply changes to existing object or create a new object and save changes

    // Perform a segue, so that payment form is dismissed and removed from navigation stack
    self.performSegueWithIdentifier("SavePayment", sender: sender)
}

Persistence

As mentioned before, Core Data was used for persistance. It was chosen, since it had advantages over plists, SQLite and serialization. It provides an abstraction over SQLite, allowing for easier data management. Comparing to serialization it offers data migrations out of the box. It is also possible to integrate it with iCloud to synchronize the data with other user’s devices. Plists weren’t suitable for the data model.

User preferences / plists could be used for storing the information about application state to restore it, when not using the API for restoring the application state. It could be also used for storing user settings like default currency etc.

DataService

To abstract the persistance to not spread it all across the code a DataService class was created, which offers methods to query, create new, delete objects and save changes. It is then used by ViewControllers whenever access to data store is needed. Example methods:

// Example methods
func getGroups() -> [Group]? {
    let fetchRequest = NSFetchRequest(entityName: "Group")
    let sortDescriptor = NSSortDescriptor(key: "created", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]
    do {
        return try managedObjectContext.executeFetchRequest(fetchRequest) as? [Group]
    }
    catch {
        return nil
    }
}

func createMember(group: Group, name: String) -> Member {
    let member = NSEntityDescription.insertNewObjectForEntityForName("Member", inManagedObjectContext: self.managedObjectContext) as! Member
        
    member.group = group
    member.name = name
        
    return member
}

// Example usage. Note: Singleton pattern is used to share one datacontext instance
let data = DataService.sharedInstance
// Create a new group
group = data.createGroup(name)
membersNames.map({ name in data.createMember(group!, name: name) })
try! data.saveChanges()

Pain points

During the application development there were a few small details, which knowing about would save great amount of time.

  • Core Data default value – even though the share in MemberShare was defined as an optional, it was using a default value whenever nil was inserted, resolving to 0. It was affecting a big portion of the application and it wasn’t that easy to find the cause. Remember to consider default value, when marking property as optional. One checkbox, 2 hours of work.
  • Placeholder constraint – when adding constraints from code to interface, which is built in Storyboard, you need to add all constraints in the Storyboard, so that it will not report an ambiguous layout and then mark the constraints you plan to replace from code as Placeholders, so that they are removed at build time. Otherwise another constraints will be added, which will not necessarily collide with your custom constraints, but will rather affect UI in an unpredictable way. One checkbox, 3 hours of work.

Dropdowns / PickerView – it is not so easy to implement it, since by default PickerView is not displayed as a keyboard as can be seen in a web browser on dropdown fields. In the and I have implemented choosing a payer as a button opening an ActionSheet, where each button chooses different member.

Evaluating prototype

During the implementation a few design patterns have emerged, which improved code quality in terms of maintenance and readability. It has also revealed the hard to implement parts, important issues in the concept and improvements in the UI. Also performing “hallway tests” with the peers helped to identify spots, which would greatly increase intuitiveness of the solution, especially right after installing the app.

Here are some key thoughts/comments regarding the UI:

  • When you have no groups (just freshly installed application) it would be great to say “You didn’t create any groups yet. Go ahead an create one” with an arrow pointing to the plus sign to add the group instead of showing a blank table view. Same goes for the payments scene.
  • In the payments scene, equals sign could be explain (could be in the beginning, when showing that group has no payments).
  • When you create a group, it should open it immediately instead of going back to list of groups.
  • Choosing the payer could be more prominent, than displaying payer name in blue (as a button).
  • The text for a button to switch between coefficients and fixed amounts might be a bit confusing. Consider changing this part.
  • When clicking on a debt, it should be possible to settle it (e.g. create a payback).
  • Since the debts are displayed as a modal, clicking on a debt to create a new payment settling the debt would open a modal on top of modal. Perhaps another way should be used.

Discussion

The purpose was to create an application, which will enable users to record payments on the go and create summary of debts with respect to currencies when needed. In that regard the goal was achieved, since the application has the minimal functionality required for it. Not all of the features were however implemented e.g. multiple payers, resolving the debt after clicking on it, editing members list in a group, copying information about debts to a text to share it as a text/Facebook message with group Members, restoring UI state after application was killed are all not currently supported.

The good thing about the app is that it is simple and it requires small amount of taps to add a payment on the go. It asks you only for the necessary information, giving you instant summary in return. The bad sides are that there is still along way before it is in a state that could compete with other applications already on the market. I do not plan however to develop it further, since during the market research I have settled on using Settle Up and I find it to be a great app that does what is needed.

Through development of the application I gained knowledge about the iOS development as well as OS X usage. I also had a chance to work with a language, which uses optional types instead of nulls, which are by some called a billion dollar mistake (introduction of nulls). I think that iOS development is quite time consuming and that learning curve in the beginning is quite steep, but once you get familiar with available options, APIs, design patterns it should be fairly easy to work with. However, looking at mine and peers’ market researches, everyone could find good apps, that were doing what they planned to implement. It is on the one hand amazing, since when you have an idea for an app, there is a great chance you can use it already, but it also shows that the market is quite crowded and it is not so easy to come up with an innovative idea.

References

Benyon, D. (2014). Designing interactive systems. 3rd ed. Pearson Education Limited.

Leave a Reply