Streamlining gRPC Development in Offline-First Apps for Mobile Engineers

Chan Ryu
5 min readintermediate
--
View Original

Overview

SafetyCulture describes how they streamlined gRPC development for their offline-first mobile apps by automating code generation for their C++ middleware layer called Crux. The article explains how a Python-based code generation script replaced hundreds of lines of manual C++ and Djinni IDL boilerplate with concise Python definitions, lowering the barrier to entry for mobile engineers who lack deep C++ expertise.

What You'll Learn

1

How to design a middleware layer for offline-first gRPC communication in mobile apps

2

Why automating boilerplate code generation reduces developer friction in cross-platform mobile development

3

How to categorize gRPC calls into distinct patterns for caching and response reduction

4

When to use code generation scripts instead of rewriting legacy C++ systems in modern languages

Prerequisites & Requirements

  • Understanding of gRPC protocol and client-server communication patterns
  • Familiarity with offline-first mobile app architecture and data caching strategies
  • Experience with mobile development (iOS/Android) and cross-platform libraries
  • Basic understanding of C++ and code generation concepts(optional)

Key Questions Answered

How does SafetyCulture handle gRPC communication in offline-first mobile apps?
SafetyCulture uses a cross-platform C++ middleware called Crux that sits between feature domain modules and the gRPC client. Crux provides response caching and response reduction capabilities, allowing users to access data while offline. Feature modules never make direct gRPC calls to backend services; instead, they go through Crux, which categorizes calls into four distinct patterns with specialized caching and data reduction handling.
What is the Crux middleware and what role does it play in mobile offline support?
Crux is a cross-platform C++ library that has powered SafetyCulture's mobile apps since 2018. It acts as an intermediary between app domain feature modules and the gRPC client, providing response caching so users can access data offline, and response reduction to reconcile local changes with cached responses before reaching the backend. It uses Djinni to generate binding code for Swift and Kotlin integration.
What are the four gRPC call patterns used for offline mobile app support?
Crux categorizes all gRPC calls into four distinct patterns, each with specialized handling for caching and reducing data. The article mentions specific examples including Single Object Fetch (with or without response caching), Batch Fetching (for listing operations with cache keys and item types), and Mutation Enqueue (for write operations with container IDs and tags). Each pattern determines how responses are cached and how local changes are reconciled.
How can code generation reduce boilerplate in cross-platform mobile middleware?
SafetyCulture built a Python script that takes concise service and call definitions and generates all required C++ gRPC client code and Djinni IDL automatically. In one real example, a mobile engineer converted a customized gRPC call from more than 500 lines of C++ and Djinni IDL to just 11 lines of Python definition. The generator ensures consistency across all generated code and reduces human error.
Why is C++ problematic for mobile developer productivity in gRPC middleware?
C++ presents several challenges for mobile developers: its steep learning curve means not all mobile engineers are proficient in it, Djinni adds another complexity layer for binding code generation, and maintaining cross-platform build systems for both iOS and Android is cumbersome. This creates bottlenecks where only a small number of developers can contribute to gRPC-related changes, forcing other teams to wait for specialized help.
Why didn't SafetyCulture rewrite their C++ middleware in Go or Rust?
SafetyCulture explored replacing C++ with modern alternatives such as Go or Rust, but these efforts were not successful for various reasons. Instead, they found a pragmatic middle ground through script-based gRPC call generation, which allows them to continue leveraging C++ where it's needed for performance while reducing complexity for mobile developers through Python-based code generation.
What is response reduction in offline-first mobile apps?
Response reduction is a technique where local changes made while offline are reconciled with cached server responses before the data reaches the backend. This ensures users always see an up-to-date dataset even when offline, as the app merges pending local modifications with the last known server state. Crux provides a library for developers to customize response reduction logic for their specific use cases.

Key Statistics & Figures

Lines of code replaced by code generation
More than 500 lines of C++ and Djinni IDL replaced with 11 lines of Python
A mobile engineer converting a highly customized existing gRPC call to use script-generated code
Crux operational history
Since 2018
Crux has been powering SafetyCulture's mobile apps as a cross-platform library
Script refinement period
6 months
Time spent continuously refining and expanding the code generation script to support all four gRPC call patterns

Technologies & Tools

Some links below are affiliate links. We may earn a commission if you make a purchase.

Key Actionable Insights

1
Introduce a middleware abstraction layer between feature modules and network clients to enable offline support. By preventing direct gRPC calls from feature code and routing all communication through a middleware like Crux, you can transparently add caching, response reduction, and other offline capabilities without requiring changes to business logic.
This pattern is especially valuable for apps used in environments with unreliable connectivity, such as remote worksites, cruise ships, or mining locations where network access is intermittent or unavailable.
2
Categorize your network calls into distinct patterns (e.g., single fetch, batch fetch, mutations) and build specialized handling for each. This classification allows you to apply appropriate caching strategies and response reduction logic per pattern rather than handling each call individually, which reduces complexity and ensures consistency.
SafetyCulture uses four distinct gRPC call patterns in Crux, each with specialized caching and data reduction handling. This pattern-based approach makes it easier to add new calls since developers only need to specify which pattern applies.
3
When legacy code creates productivity bottlenecks, consider automating code generation before attempting a full rewrite. A Python script that generates C++ boilerplate and IDL definitions can dramatically reduce the barrier to entry, allowing engineers without deep C++ expertise to contribute effectively.
SafetyCulture's experience shows that rewriting from C++ to Go or Rust was unsuccessful, but code generation achieved similar developer productivity improvements while preserving the performance benefits of the existing C++ system.
4
Start with automating a single, limited use case and iteratively expand coverage. SafetyCulture's initial script only supported Single Object Fetch with no response caching, but its immediate productivity gains justified continued investment, eventually growing to support all four gRPC call patterns.
The iterative approach over six months allowed the team to validate the approach early and expand based on real developer needs rather than trying to build a comprehensive solution upfront.
5
Use declarative Python definitions as the interface for code generation rather than requiring developers to write the target language directly. This approach lets mobile engineers specify behavior (caching keys, item types, mutation tags) in a familiar, concise syntax while the generator handles language-specific complexities.
The code example in the article shows how domain, service, and call definitions in Python replace hundreds of lines of C++ and Djinni IDL, making the system accessible to developers who are not C++ experts.

Common Pitfalls

1
Attempting a full rewrite of a working C++ system to a modern language like Go or Rust can fail for various organizational and technical reasons. SafetyCulture explored these alternatives but none were successful, wasting effort that could have been spent on incremental improvements.
Code generation provides a pragmatic alternative that preserves the performance benefits of C++ while abstracting away the complexity for most developers.
2
Allowing only a small number of specialists to make gRPC-related changes creates organizational bottlenecks. When C++ expertise is required for every gRPC modification, teams without that expertise become dependent on others, slowing down feature development across the organization.
Automating boilerplate generation with accessible tooling (like Python scripts) democratizes contributions and removes the expertise bottleneck.
3
Manually writing repetitive C++ boilerplate and Djinni IDL for each new gRPC call is error-prone and inconsistent. The verbose syntax and intricate build setups compound the difficulty, even though the underlying gRPC patterns are straightforward.
Most of the difficulty in adding gRPC calls stems from verbose C++ syntax, Djinni IDLs, and build complexity — not from gRPC itself. Automating these aspects eliminates the primary sources of errors.

Related Concepts

Offline-first Architecture
Response Caching
Response Reduction
Cross-platform Mobile Development
Code Generation
Protobuf
Mobile Middleware Patterns
Djinni Bindings
Developer Experience Tooling
Build System Automation