Project Structure - Hipo/mobile-team-guidelines GitHub Wiki
This chapter outlines the directory structures and various other basic requirements for iOS & Android projects.
iOS
Project structure is the first thing we should pay attention when starting a project. Generally, there are a few rules which every iOS team member should follow.
-
GIT
All projects in Hipo will be hosted in 'Github'. So, first thing we should do is to put a standard .gitignore file into the project. We do not push installed pods (CocoaPods) to git, therefore; do NOT forget to add the restriction into gitignore because, before, we had experienced that some pods might have too much size to be hosted in Github. Also, it would be nice to add a README file with general info about the project and how to setup development environment.
Sample .gitignore
# OS X temporary files that should never be committed
.DS_Store
.Trashes
*.swp
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
*.xcworkspace
!default.xcworkspace
xcuserdata
compile_commands.json
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# CocoaPods
Pods/
-
COCOAPODS
It is possible for a project not to have CocoaPods integrated. However, almost every project needs to benefit open-source libraries somewhere in the development process. For that, it is highly recommended to start a project with CocoaPods integrated.
If there is an open-source library, you want to use in the project, you must add it as a pod unless there is a specific reason not to do. It would be nice to write it down to README. Also, if you want to add a pod, but it does not meet all your requirements and you need to change the code, first you fork the project into Hipo repo, make your changes, then add ours into podfile.
Sample Podfile
platform :ios, '9.0'
use_frameworks!
inhibit_all_warnings!
def main_pods
#Analytics
pod 'Intercom'
pod 'Mixpanel-swift'
#Debug
pod 'Reveal-SDK', :configurations => ['Debug']
#Layout
pod 'PureLayout'
#Model
pod 'Mantle'
pod 'KVOController'
#Networking
pod 'AWSS3'
pod 'HIPNetworking', :git => 'https://github.com/Hipo/HIPNetworking.git'
pod 'Kingfisher', '~> 4.0'
#View
pod 'ActiveLabel', :git => 'https://github.com/Hipo/ActiveLabel.swift.git', :branch => 'swift-4-migration'
pod 'CCHLinkTextView'
pod 'HIPImageCropper'
pod 'VENTokenField', :git => 'https://github.com/Hipo/VENTokenField.git'
pod 'PullToRefresher', :git => 'https://github.com/Hipo/PullToRefresh.git'
pod 'SZTextView'
#Utilities
pod 'DeviceUtil'
pod 'SSKeychain'
pod 'PhoneNumberKit', '~> 2.1'
pod 'AnyFormatKit', :git => 'https://github.com/Hipo/AnyFormatKit'
#Structure
pod 'IGListKit', '~> 3.0'
end
target 'Sample' do
# Pods for Sample
main_pods
end
target 'Sample-Preprod' do
# Pods for Sample-Preprod
main_pods
end
target 'Sample-Staging' do
# Pods for Sample-Staging
main_pods
pod 'Tryouts', :git => 'https://github.com/Hipo/Tryouts-iOS-SDK.git'
end
-
GROUPING FILES
This is the one thing every iOS developer in Hipo must put effort. There are several groups which needs to be included in almost every project.
- Classes
- API
- Application
- Extensions
- Managers
- Models
- Protocols
- ViewControllers
- Views
- Utility
- Resources
- Assets
- Fonts
- LaunchScreen
- Localization
- Support
- Info.plist
- Dev-info.plist
New concepts such as Animators, Datasources, Delegates, Layouts, ViewModels etc. should also be grouped with the same systematic.
The files created under these general concepts should be grouped by feature-set such as Common, Feed, Profile, Publish etc. , as well.
Under feature levels, you do not have such grouping instructions, but it is encouraged to create new groups for every sensible concept. Additionally, you should sort all your groups and files by alphabetical order. These will make project structure more readable.
Besides this grouping strategy, there are a few key points you should bear in mind. First. you should maintain a logical order by putting source files in groups. For example, if you create a view class for a view controller positioned under Classes>ViewControllers>Profile, the file should be put under Classes>Views>Profile. This way, all groups will be in sync by the same hierarchical order. And if you have a class which is used for multiple feature-set, then these files should be moved to 'Common' groups and their names should be changed if needed. Moreover, the group structure within both XCode and filesystem MUST be identical. It is a big plus for a well-organized project.
- Classes
-
TARGET MANAGEMENT
Generally we have three targets which are Production, Pre-Production, Staging. According backend, we should create targets for each environment.
You can then add swift compiler flags so that you can check the current configuration through code. To do that, goto Target->Build Settings->Swift Compiler->Custom flags->Other Swift Flags section and edit the flags for different environments. Add the flags using the sytax, "-D{target_name}" like "-DAPPSTORE", "-DPREPROD", and "-DSTAGING". If you have satellite applications in the project, you can set the custom flags using the same method.
To get the relevant configuration about build flags on compile time, you can use the
Environment.swiftfile.
Sample Environment.swift
import Foundation
fileprivate enum AppTarget {
case staging, preprod, prod
}
// MARK: Frameworks
struct IntercomBundle {
// MARK: Variables
lazy var token: String = {
switch target {
case .staging, .preprod:
return "intercom_test_token"
case .prod:
return "intercom_prod_token"
}
}()
private let target: AppTarget
// MARK: Initialization
fileprivate init(target: AppTarget) {
self.target = target
}
}
struct FrameworkBundle {
// MARK: Variables
lazy var intercom: IntercomBundle = {
return IntercomBundle(target: target)
}()
private let target: AppTarget
// MARK: Initialization
fileprivate init(target: AppTarget) {
self.target = target
}
}
struct Environment {
// MARK: Singleton
private static let instance = Environment()
// MARK: Variables
static var current: Environment {
return instance
}
lazy var serverHost: String = {
switch target {
case .staging:
return "https://staging.sampleapp.com"
case .preprod:
return "https://preprod.sampleapp.com"
case .prod:
return "https://sampleapp.com"
}
}()
lazy var serverApi: String = {
return "\(serverHost)/api"
}()
lazy var frameworks: FrameworkBundle = {
return FrameworkBundle(target: target)
}()
private let target: AppTarget
// MARK: Initialization
private init() {
#if APPSTORE
target = .prod
#elseif PREPROD
target = .preprod
#else
target = .staging
#endif
}
}
-
STYLEGUIDE
In Hipo, we do not use interface builder, except Launch Screen, to build the UI components. Since they are implemented in the code, it is an important job for us to design styling code in a well-organized way. The general rule we should consider is to use the three types of configuration, application-based, screen-based and_ component-based styling_, for the respected properties. This will be especially helpful to standardize the code to be able to answer the requirements of the different projects, customers and designers.
- Application-based: To support the shared styling for the same interface elements in the application.
- Screen-based: To support the shared styling for the interface elements in the same screen, but having different styling from the application-based configuration.
- Component-based: To support the shared styling for the interface elements in the same containing component, but having different styling from the application-based and the related screen-based configurations.
We are using a shared pod (currently in progress) for styling, and also the common UI code, which can be found in [https://github.com/Hipo/HIPUIKit-Sample]: here.
-
FILE STRUCTURE
The developers can follow his/her own rules to the code structure; however, there are a few rules everyone should consider.
- Whatever structure you choose, you must be consistent throughout the whole project. If you join an existing project, you should make an effort to keep it on. However, if you see a inconvenient rule, and want to change it, you should do it everywhere in the project before pushing it to master branch.
- You should fix the all issues generated by Swift linter as soon as possible. These rules are strict and decide by the whole team. Thus if you want to change any of them, you should first talk to the team, explain your reasons. For other issues, you can check 'Swift Style Guide' before you decide a rule or consult the other team members.
- Pragma marks and extensions are very helpful for the other team/project members to follow and understand the code/project; therefore, it is highly recommended to use them as much as possible.
For a reference, you can also check the shared pod, which can be found in [https://github.com/Hipo/HIPUIKit-Sample]: here.
Android
Project Structure
Package structure plays a big role for project's readability and maintenance. Files should be in appropriate seperate groups to access easily.
Package structure must follow the structure in the image below.

BaseActivity and BaseFragment in core folder must be implemented by nearly all Activity and Fragments unless there's a special case. Base classes have basic functions such as injecting network, sharedpreferences, loading view. So, there's no need to write unnecessary codes to each class. If the method used in more than one class and related to activity or fragment functionality(if not util class can be used), It must be moved to base class.
If you use custom ui pieces more than one and it has special functionalities as feature, developers should create custom view to use it later when needed.
Every different screen in the application must be folderized in feature folder. These folders can be named as login, signup, main. Every fragment, adapter related to same screen should be also in folder unless used in different pages.
Methods that are used more than one place must be in utils package if not in base classes. Utility classes must follow the naming convention as UiUtils, DateUtils. Utility classes can include functions such as getting current time or getting image url according to specific phone. (PrismUtils)our project and needs to use frequently like getting current time, download file, cropping image etc.
Res folder also has specialized structure and can be shown like in the image below.

Like package structure, gradle files also have structure that should be followed to increase readability. Example of gradle files included in the project can be shown like in image below.

The example below is taken from the Chroma project which creates 5 different application from the same project. This gradle file called as signing.gradle and includes keystore, keyalias, keypassword, storepassword for each flavour in the project. These parameters must be different from each other for every different flavour.

Creating a gradle named versions.gradle is great idea to update dependencies or version of application from the single file. The structure of versions.gradle should look like image below.
