Cross Platform Apps the Hard Way – Part 2: The Architecture

In my previous post where I outlined the goals for this project of mine, I briefly mentioned my thoughts on how to structure the code in order for it to work on as many platforms as possible. In this post, I intend to dive in a little deeper on the architecture of the app to maximize reusability without causing myself too many headaches on whichever native platform I’m writing for.

I’m an Android developer by day, and when structuring a native app written in Java or Kotlin, I usually like to break them down in the following layers:

  • UI layer
  • Repository layer
  • Service layer

I intend to mirror this structure for my cross-platform apps as well. My hope is that I’ll be able to easily use UI-specifc objects that are unrelated to the data transfer objects in C, so that I can load up the UI and then only communicate with the native layer where necessary, as I could be wrong but I’m fairly certain that accessing C code from Java is fairly expensive.

The UI layer

On Android, I generally consider the UI layer to be the Activities, Fragments, and ViewModels/Presenters. For anyone not familiar with Android development, that’s essentially all of the components related to outputting information to the user, or receiving input from the user. I like to keep this layer very simple, having little-to-no logic beyond just listening for updates from the lower layers or providing updates from the user. I’ll use ViewModels only as a means to avoid losing state between screen rotations but this will otherwise contain no logic. This is also ideally going to be the only layer that contains platform-specific code, so that would be Kotlin for Android, Swift for iOS, GTK/QT for Linux, etc.

The Repository Layer

The way I see it, the Repository layer is meant to be an abstraction around the various data sources for your app. In most cases this is fairly simple, either just a local database or just a network resource, but I intend to implement some local caching so that the app can still be usable offline. For retrieving data, the app should store a copy locally so that if the user opens the app again without an internet connection, they can still see the last updates. Even if the user has an internet connection, this ensures that something will be visible upon launching the app as a local cache will inevitably be faster than making a network request. I intend to write this layer in C, with the use of cURL and OpenSSL for making secure network requests, and SQLite for caching the data locally. Given that these are all open source cross-platform libraries that, as far as I know, run on multiple architectures including ARM and x86, they should be sufficient for my needs. I’ll likely only need platform-specific code for the object mapping from the C structs to the objects in the platform’s language.

The Service Layer

While the repository abstracts away where the data comes from, the service layer abstracts access to each data source individually. So for my purposes, the repository object would have a reference to a database service object that’s responsible for managing the connections to the database and retrieving or updating the data it holds, and a network service object, which would have a similar role as the database service object except it would handle network resources. I also intend to write this layer in C for use across multiple platforms.

For the next post, I’ll likely write a quick tutorial on how to include C code in your Android app, as it’s something I’m only vaguely familiar with myself, so that will give me a little hands-on experience with the process. From there on, I’ll document the process of actually making this work. Stay tuned for further updates and don’t hesitate to get in touch if you have any questions or suggestions!