For the last few years, we have been working on a user-friendly construction project management app. This kind of tool includes an array of features, for example issuing tasks and document management. However, there is one crucial feature that is often overlooked but plays a vital role on construction sites: offline functionality.
Goal
Offline mode plays a critical role on construction sites, particularly in areas with limited or no connectivity, such as basements or remote locations, making the app quicker and more reliable. This allows teams to document progress and report issues efficiently. Although it would be perfect to implement the offline mode across the whole app, we took a more cost-effective approach. We implemented the offline mode gradually covering the most important modules. Starting from the top…
Projects module
Projects are the core of the application. They are containers for most other resources like Documents, Scope of Works, Issues, Forms, Tasks, and Project Members. This means that we must keep them synchronized and ensure the database’s integrity.
Synchronizing too often would drain too much battery so we synchronize all of them every 24h and store them in the database. That’s not good enough for most visited projects, so if you visit one and it hasn’t been synchronized in the last 2h, we synchronize it individually.
The synchronization is resilient to connection problems, server errors and other unexpected issues. If requested in app settings, it only uses wi-fi to save the mobile data. Our IDEs of choice (respectively Android Studio for Android and Xcode for iOS) come with profilers which allow us to track the battery and mobile data spending and optimize if it gets out of control.
To synchronize the data, on Android we used the native state of the art approach- Work Manager. It’s a native library that issues Workers managed by the system. There are other libraries but the choice wasn’t random. This is a solid native library that is still being improved while also being long past its alpha stage.
One of the immediately helpful features of Work Manager is setting the workers to run only when there’s an internet connection, as well as setting any other constraints.
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
workerRequest.setConstraints(constraints)
public enum NetworkType {
/**
* A network is not required for this work.
*/
NOT_REQUIRED,
/**
* Any working network connection is required for this work.
*/
CONNECTED,
/**
* An unmetered network connection is required for this work.
*/
UNMETERED,
/**
* A non-roaming network connection is required for this work.
*/
NOT_ROAMING,
/**
* A metered network connection is required for this work.
*/
METERED,
/**
* A temporarily unmetered Network. This capability will be set for networks that are
* generally metered, but are currently unmetered.
*
* Note: This capability can be changed at any time. When it is removed,
* {@link ListenableWorker}s are responsible for stopping any data transfer that should not
* occur on a metered network.
*/
@RequiresApi(30)
TEMPORARILY_UNMETERED
}
Documents module
Having a good foundation, we implemented the most important project module- Documents. The app supports multiple types of documents, like images, PDFs, MS Office, BIM files, and many others. As mentioned above, adding full offline mode support for all of them would be too time-consuming. This meant that we had to start with the vital file types- images and PDFs.
The way of storing the data varies depending on the platform. However, the consistent part is storing the binaries outside the database as regular system files and keeping the path to the file in our database models. Apart from the file path, similar to what we have in other modules, the DB model stores the most important document data and metadata (like projectId, userId, upload status, or user permissions).
Most of the data is strictly project’s domain knowledge but data like upload status is a bit different. It’s a good solution for projects with solid error handling. We keep it updated in our DB while tracking the progress of the upload. This ensures that whatever exception happens- the data won’t be lost and the failed requests will wait until picked up by our clean-up workers.
Our DB of choice for Android was the native library- Room with the support of Paging used for pagination. This is a very flexible solution (thanks to leveraging SQLite) that’s still very clean and easy to maintain.
@Query("SELECT * FROM projects WHERE user_id = :userId ORDER BY created_at DESC LIMIT :limit OFFSET (:page - 1) * :limit")
fun projects(userId: String, limit: Int, page: Int): List<ProjectEntity>
@Entity(
tableName = "projects",
foreignKeys = [
ForeignKey(entity = UserEntity::class, parentColumns = ["id"], childColumns = ["user_id"])
],
indices = [
Index(value = ["user_id"])
]
)
data class ProjectEntity(
@ColumnInfo(name = "id") @PrimaryKey val id: String,
@ColumnInfo(name = "user_id") val userId: String,
@ColumnInfo(name = "project_name") val projectName: String? = null,
…
)
Issues module
Even with the best documentation, on a construction site, alike in software development, there are always some issues. For this, we have a separate module, which lets the users report and discuss them. The discussion part of the solution is a dedicated chat with multiple features and that’s not something necessarily required for the offline mode. What the users needed was the ability to issue tickets without an internet connection.
Adding new issue without internet connection, categories and project members are stored offline with the project details Queued issue at the top, created while being offline
This process had to be especially reliable so we also implemented the retry mechanism vaguely mentioned earlier. The way it works is straightforward for the users (a simple retry and cancel buttons) but there’s some interesting tech to it in our codebase. So let’s take a look at how exactly we manage the upload status.
enum class UploadStatus {
PENDING,
UPLOADING,
PROCESSING,
UPLOADED,
ERROR,
FATAL_ERROR,
CANCELED
}
We start with the PENDING status which is set automatically when creating the entity in the db and requesting the upload worker. Once we start the upload, we update the status to UPLOADING. When the data hits the server, we could set the status to UPLOADED immediately but for the best UX instead, we wait for the server-side processing and the confirmation the data is available for the users to set the PROCESSING status first. And that’s if everything goes well.
If the upload fails, the first thing we do is identify the reason. We easily identify the internet connection loss, API maintenance, connection timeout, etc. exceptions and set the worker to automatically retry up to 5 times with incrementing delay.
workerRequest.setBackoffCriteria(backoffPolicy= BackoffPolicy.EXPONENTIAL, backoffDelay = 10, timeUnit = TimeUnit.SECONDS)
Though, if the reason is a bad API request the retries would never succeed until there’s a dedicated mobile app hotfix. All it would bring is some data-wasting clutter. This means we set the status to FATAL_ERROR right away. It’s uncommon but a good error-handling system has to be prepared for it. Otherwise, the data could be lost without any way of retrieving it even after a mobile app hotfix.
Scope of Work module
Scope of Works is a module that spans multiple other modules together (like previously mentioned Documents, Issues, Images, and many others). It’s a hefty feature with a scrollable view full of useful features. As always, we had to consider the most cost-effective approach.
We know from the customers that the feature in its full version is mostly used in high-level planning on the Web. This made us introduce a more compact mobile-friendly version called “Actions”. It’s all about adding images, comments, and forms to already created items since that’s what mobile users usually do.
However, if you think about it, adding all of the actions to the offline mode would take many iterations. We wanted to bring the value as soon as possible, so in a modular approach, we started with the most popular actions- photos, comments, and item completion while temporarily disabling forms in the offline mode.
Offline SoW Actions uses all the tech previously mentioned in the previous modules but introduces a new problem- queueing.
SoW Actions view
with required inputAdding a comment
to an item
If specified by a project manager, the items might require at least 1 image and 1 comment to be completed. In this case, we had to make sure that the completion API request is sent after the required data. To solve this problem on Android, we leveraged another WorkManager feature.
workManager.beginWith(firstRequest).[...].then(lastRequest).enqueue()
Legal compliance
Aside from the UX, we also solved the legal problem which was the Part-L legislation. In the context of mobile apps used in construction and building projects, having photos with geolocation is crucial for meeting Part L compliance requirements. This feature allows for accurate documentation of where each photo was taken on-site, linking it directly to specific building sections or construction phases. This is especially useful for remote inspections and for maintaining a verifiable record of compliance with energy-saving regulations.
Geolocation data is usually stored in EXIF files, along with all other pieces of metadata like device name, image size, orientation, and many others. However, our camera modules didn’t have the location info in the automatically generated EXIF. We solved this by retrieving it manually and applying it on top of the generated EXIF. Still, we ran into problems like missing location caused by either a problematic phone OS, denied location permissions, or inability to retrieve the location. For this reason, we included custom EXIF “geolocationRejected” data. This way we knew whether we were dealing with a stubborn user or a device-specific bug.
if isGeolocationRejected, let newExif = (newMetaData[kCGImagePropertyExifDictionary as String]) {
newExif[kCGImagePropertyExifUserComment as String] = "geolocationRejected"
newMetaData[kCGImagePropertyExifDictionary] = newExif
}
Photo with Geo Location visible in the web app Geo Location information
Summary
In conclusion, adding offline mode to our contech project management app has significantly enhanced its utility, particularly in areas with limited or no internet connectivity. By prioritizing essential features like document management and issue reporting, and utilizing robust technology like Android’s Work Manager, we’ve ensured undisturbed operation even in challenging environments. The inclusion of location data in photos not only fulfills legal requirements (Part L compliance) but also strengthens the project’s historical record (golden thread) by providing a verifiable, visual account of work completed and decisions made. Moreover, this photographic evidence, combined with other project data, contributes to the creation of a more comprehensive and accurate digital representation (digital twin) of the construction project, enhancing its value for real-time monitoring, remote inspections, and future maintenance. Offline functionality transforms our app into an indispensable tool for construction professionals, empowering them to work efficiently and effectively regardless of internet availability.
Popular posts
From Hype to Hard Hats: Practical Use Cases for AI chatbots in Construction and Proptech.
Remember the multimedia craze in the early 2000s? It was everywhere, but did it truly revolutionize our lives? Probably not. Today, it feels like every piece of software is labeled "AI-powered." It's easy to dismiss AI chatbots in construction as just another tech fad.
Read moreFears surrounding external support. How to address concerns about outsourcing software development?
Whether you’ve had bad experiences in the past or no experience at all, there will always be fears underlying your decision to outsource software development.
Read moreWhat do you actually seek from external support? Identify what’s preventing you from completing a project on time and within budget
Let’s make it clear: if the capabilities are there, a project is best delivered internally. Sometimes, however, we are missing certain capabilities that are required to deliver said project in a realistic timeline. These may be related to skills (e.g. technical expertise, domain experience), budget (hiring locally is too expensive) or just capacity (not enough manpower). What are good reasons for outsourcing software development?
Read more