Skip to main

How to improve the stability of your mobile app

by Michał Tuszyński

How to improve the stability of your mobile app

Ever wondered how to improve the quality of our iOS apps and become a more productive developer? This is the first article in a series that fully focuses on the problem of shared state and how to possibly reduce it.

Mobile applications are becoming more and more popular every day. There are also many tools becoming available to develop apps, we have both Swift for iOS and Kotlin for Android that promise us more compile-time checks so that there’ll be fewer issues runtime.

However, despite sophisticated tools, it’s still tricky to write a stable application. In this article, I’ll share some tips from my personal experience on how to improve the quality of our software and make our lives a little easier.

Copy link
Shared state and side effects

When developing modern mobile apps, we tend to use object-oriented programming. We create objects, these objects can have fields that can be altered from many parts of our program. This can cause issues due to some part of the program expecting an object in a specific state and receiving something unexpected, leading to bugs. These bugs can be hard to reproduce on our local environment since a specific set of steps need to be taken in order to reproduce the issue. Modern programming languages like Swift or Kotlin favor immutable data structures to reduce the issue. If we have an object whose fields are immutable, then once it’s created the object is in a stable state.

Let’s take a look at some practical examples. Assume that we’re asked to implement a class which fetches user’s location in the world. Since this article is not a tutorial about how to use CLLocationManager, I’m going to focus on the interface only. One of the simplest ways to tackle the problem, is to use the delegate pattern, so it’d look something like this:

js
1 2 3 4 5 6 7 8 9 protocol LocationFetchterDelegate: class { func locationFetcher(_ fetcher: LocationFetcher, didUpdateLocation location: CLLocation) } final class LocationFetcher { weak var delegate: LocationFetcherDelgate? //Class implementation goes here }

This solution has a few problems:

  1. The field delegate can be freely modified by other components. One place can set a delegate and another one can choose to assign it to another object, which can cause one of the components not receiving results whatsoever.

  2. We don’t know on which thread the results can be delivered on, without looking into the implementation. If we received this class as part of a closed-source library, we’d either need to disassemble the binary or check it in runtime.

  3. What if we wanted multiple objects receiving the location updates? We’d need to change the class implementation, which would lead to public interface change and all relying components would need to update.

Copy link
The solution

Okay, since we know about the problems now, let’s move on to a possible solution. I myself, like to use ReactiveX APIs. In short, ReactiveX is an API for asynchronous programming using observable streams that combines the best ideas from the observer and iterator as well as functional programming. ReactiveX is implemented on different platforms: there is RxSwift for Swift, RxJava for Java, RxJS for JavaScript, etc. and they all share similar API respecting the language’s design and idioms.

In order to understand how to use observable streams in our applications, we need to change the way we think about designing applications. The most straightforward example of how it works is to think of an Excel spreadsheet. Let’s imagine that we have two cells in the spreadsheet and we want to sum them. That’s easy! Let’s take a third cell, take the addresses of two previous ones, and write a formula that adds their values. Now, the cell will display the sum or an error if either of the previous ones contained an invalid value (like a string instead of a number). Observable streams work in a similar way from a very high-level point of view.

Now that we’ve got a basic understanding of how observable streams work, let’s look at how the class interface would look if we chose streams over delegate. For the purpose of this article, we’re using the RxSwift library.

js
1 2 3 final class LocationFetcher { let location: Observable<CLLocation> }

As we can see, problem #1 is gone. We have one immutable property location which emits location updates and no component can modify it. Our program not only more deterministic but since we don’t need a specific reference to the delegate, we got rid of the additional protocol as well! Let’s see how subscribing to updated would look like:

let locationFetcher: LocationFetcher //Assume exists

locationFetcher.location
.observeOn(MainScheduler.instance)
.subscribe(onNext: { print("new location: \($0)") })
.disposed(by: disposeBag)

This solves problems #2 and #3. As we can see, RxSwift allows us to specify on which thread we’re receiving the updates, as well as multiple objects, can subscribe to a single stream to get updates via simple DSL-like API.

Copy link
The downsides

The problem with many ReactiveX articles out there is that I didn’t see many of them outlining the disadvantages of this approach. All tools and libraries have good sides and bad sides and it’s up to us, developers to make the call to use them or not.

As I mentioned earlier, the main downside of using the observable streams approach is that we have to rethink the way we write mobile apps. This means that we’ll make many mistakes along the way and because of this, I highly discourage using ReactiveX for an important project for the first time. The best way is to find either a personal project or one that allows us to experiment and see if we like it. The learning curve for RxSwift is very steep and there aren’t that many good tutorials out there, so keep that in mind before using it for the first time.

Another disadvantage I experienced, is that while some things are very easy to implement using observable streams and make our code look nice and elegant, it doesn’t apply to everything. For example, there is a way to implement a UITableView data source using RxCocoa bindings, however, it’s not straightforward and takes longer rather than using the Apple recommended approach. My advice here is to use observable streams selectively. Start with networking, for example, make it work well and move on to other components and see if you really gain something by implementing them. Don’t blindly rewrite your whole app using RxSwift just because someone on the internet told you to do so.

Copy link
Conclusion

In this article, we introduced the concept of shared state and side effects and brought to attention on what issues can they create as well as my proposition on how to easily fix them using ReactiveX libraries. In the next articles, we’ll focus on other common issues in mobile apps and how to fix them.


IntroductionArticle

Related Articles