State management is the root of most headaches for native mobile development - as well as single page app web development. Mobile applications are almost always stateful. On top of keeping track of the current state in business flows, you need to make sure you handle app lifecycle transitions (iOS, Android). Examples of the app-level lifecycle changes are the app pause and going to the background, coming back to the foreground or being suspended. The states are similar, but not identical for iOS and Android.
Events drive state changes in most cases for mobile apps. These events trigger in an asynchronous way - application state changes, network requests, user input. Most bugs and unexpected crashes are usually caused by an unexpected or untested combination of events, and the state of the application getting corrupted. State becoming corrupted is a common problem area with apps where global or local states are manipulated by multiple components unbeknown to each other. Teams that run into this issue start to isolate component and application state as much as possible, and likely end up with reactive state management sooner or later.
Events driving state changes in mobile apps
Reactive programming is a preferred method to deal with a large and stateful app where we want to isolate state changes. You keep state as immutable as possible, storing models as immutable objects that emit state changes. This is the practice we used at Uber, the approach Airbnb takes or how N26 have built their app. Though the approach can be tedious in propagating state changes down a tree of components, the same tediousness makes it difficult to accidentally make unintended state changes in unrelated components.
Applications sharing the same resources with all other apps and the OS killing apps on short notice is one of the biggest differences of developing for mobile, versus developing for other platforms - like backend and web. The OS monitors CPU, memory and energy consumption. If the OS determines that your app is taking up too many resources - may this be in the foreground, or the background - then it can be killed with very little warning. It is the responsibility of the app developer to react to application state changes, save state, and restore the app to where it was running - even after it was killed. On iOS, this means handling app states and transitions between them. On Android, the same concept is called the Activity lifecycle.
Global application state - permissions, bluetooth and connectivity state and others - brings an interesting set of challenges. Whenever one of these global states changes - for example, the network connectivity drops - different parts of the app might need to react differently. With global state, the challenge becomes deciding what component owns listening to these state changes. On one end of the spectrum, application pages or components could listen to global state changes they care about - resulting in lots of code duplication, but components handling all global state concerns. On the other end, a component could listen to certain global state changes, and forward these on to specific parts of the application. This might result in less complex code, but now there’s tight coupling between the global state handler and the components that it has knowledge of.
App launch points like deeplinks or internal “shortcut” navigation within the app also add complexity to state management. With a deeplinks, additional state might need to be set up to put the app in the same state as if the user navigated to the deeplinked content, after opening the app. We’ll talk about deeplinks in more detail
Building Mobile Apps at Scale
"An essential read for anyone working with mobile apps. Not just for mobile engineers - but also on the backend or web teams. The book is full of insights coming from someone who has done engineering at scale."
- Ruj Sabya, formerly Sr Engineering Manager @ Flipkart