App development lessons: Building an offline-first solution with a dynamic data model

Users of pharmaceutical commercial technology need seamless, real-time access to data and insights – in the office, at home, or on the go. As a result, these technologies need equally robust and accessible desktop and smartphone app versions.

Pharmaceutical companies use Beghou Consulting’s Mainsail platform to manage a mix of commercial activities – from forecasting to incentive compensation to sales reporting to customer management.

Mainsail is a highly configurable and dynamic platform, and customers leverage it to address many business challenges. But this configurability created unique challenges when our team started to build the mobile app version of the platform. We encountered three key challenges during the app development process:

  1. Constructing a dynamic data model: Different Beghou clients use the app to access their custom Mainsail dashboards and data. This single app therefore had to be able to accommodate these various customizations and each client’s unique business rules.
  2. Replicating the layout of the web application: The team needed to ensure the navigation and structure of the app interface mirrored the web-based platform to optimize the user experience.
  3. Ensuring offline functionality: Clients need to be able to update the app in the field, even when disconnected from cell service. So, we had to work to ensure the app was fully functional offline and synced efficiently when reconnected.

Let’s dig into these challenges.

Constructing a dynamic data model 

The traditional first step in an app development process is to start sketching out the objects and data schema that the app will use as a model. For example, an e-commerce app might have products, orders and customers. Developers think about the properties of these objects and the relationships between them. This framework becomes the static model for the app. To add a new feature, the developer will add new code, update the data schema and release an updated version of the app.

Mainsail is different. It uses configuration metadata to describe the domain model, and there is no static domain model to code against. Not only did we need the app to work with multiple systems, each with its own domain model, but we also needed to accommodate changes to the domain schema over time without requiring new code or app updates. The fundamental challenge was for the Mainsail app to dynamically create a persistent data schema and use an object model that was entirely driven from metadata.

Ideally, we wanted to have the app run on one database that contained both the static metadata model and the dynamic domain data model. We started researching the possibilities of programmatically building a Core Data model for the domain data. This exploration led us to investigate the documentation of NSManagedObjectModel. We ended up building an operation class that could inspect the metadata for a Mainsail system and produce a serialized file that we could use to initialize our Core Data stack on app startup. This was a major first step. But we also needed to address schema updates.

Over time, the configuration for a Mainsail system changes as the team adds new domain objects and new fields to the domain data tables. For instance, to build a sales report for a customer, we might need to add an entirely new table that summarizes sales data by product and quarter at the ZIP code level. So, we established a requirement that the Mainsail app adapt to these changes based on available metadata and update the domain data schema on the fly. We built the app to query the API for the most recent domain schema version and compare it to the current version on the device. If the version on the server was a later version than what the app was using, then we would run the operation to create the new domain schema and re-download all the new domain data.

This migration scheme enabled us to retain all the flexibility of the Mainsail platform with no extra burden on the end user.

Replicating the layout of the web application

While we wanted to keep our configuration effort as low as possible for deploying a mobile app, we also needed to optimize the user experience. Since users were already familiar with the pages, reports, filters and maps in the web-based platform, we had to ensure they could effortlessly navigate the mobile app and access the features they needed. The key here was to build a dynamic user interface that hit the right balance of flexibility and usability. Addressing this challenge forced us to touch every aspect of our interface – from the menu of pages a user could view to the validation messages we would display if a newly added hospital was missing a required field. The bottom line was that the metadata needed to drive everything.

Take the main navigation menu as an example. The web app uses a standard hierarchy of tabs and sub-tabs as its navigation scheme.

In the app, we decided to use a vertical list with collapsible “tabs” so that pages with longer names would easily fit.

Though it is a basic layout, it is functional and, most importantly, easily adaptable to any Mainsail system that maps directly to what is displayed in the web platform. To add some visual interest to this simple menu style, we produced a series of general icons that would be commonly used in Mainsail systems and could be configured alongside the title of each page.

Ensuring offline functionality

Sales reps need to access and update data in Mainsail in the field. So, the app had to work seamlessly without an internet connection and update efficiently when reconnected. This meant that we needed to build the app against a disconnected data store and create a robust data synchronization process between the device and the Mainsail server. Keeping the data current with a minimum of network chatter became the next problem to solve.

The Mainsail web app called a REST API to fetch and post data to the server, so we were able to quickly get started with proof-of-concept data sync code. From the start, we knew that we were going to need a sync process that could not only handle a “full refresh” where we fetched all the metadata and all the domain data into a clean database, but also a “differential sync” that would only fetch recently changed data from the server. For both sync scenarios, we would first push modified or “dirty” data from the device before fetching anything.

We built the sync operation code but considered several iterations related to how and when the sync process would run. We settled on a 30-second polling period to check for locally modified data with a five-minute threshold for starting a differential sync with the server whether there was locally modified data or not. We had a button on the “settings” page for a user to start a differential sync, but the expectation was that they would never use it and instead rely on the notification bar to keep them informed of the latest sync status.

To ensure the app reflected significant data updates or changes to the server’s data schema, we implemented a “forced full refresh.” The refresh is triggered by a timestamp on the user record on the server. So, if the device started a differential sync and its last full refresh date was before the “forced” timestamp, the app exits the differential sync, interrupts the UI and prompts the user to start a full refresh. This “forced full refresh” was a powerful improvement that gave us confidence that disconnected schemas and data on our users’ devices were as current as possible.

Lessons for complex app builds from the Mainsail experience

The result of our efforts was an iOS application that captured the full power of Mainsail. A key to our team’s success that other developers should consider as they work on similarly complex app development projects is to stick closely to “minimal viable product” thinking and work to “shrink the change.” We intentionally kept the UI simple to ensure the app accommodated our various customers’ Mainsail setups and needs.

During complex builds, it’s easy to get bogged down in the details and try to accomplish too much at once. When developers fall into this trap, they suffer endless delays and often lose sight of the main goals of the project. It’s important to minimize complexity by focusing on only the essentials, to start. Then, add features little by little from there. This approach ensures the team reaches the finish line and produces a usable app in a timely manner.