Android demo | iOS demo |
---|---|
Kotlin Multiplatform Mobile demo for Android and iOS. App for viewing Cat pictures from Cats API.
This sample showcases:
- Kotlin Multiplatform Mobile
- Android Architecture
- Shared state management with Flow
- Networking with Ktor
- Persisting state with Multiplatform Settings
- Logging with Kermit
- Android UI with Jetpack Compose
- iOS UI with SwiftUI
- Android image (GIF) loading with Coil
- UI tests
- Unit tests
I leave here some notes that might help anyone who would like to build it themselves.
The project is using a property named catsApiKey
to retrieve the key for the API.
You can provide it through gradle.properties
, for example.
You should follow KMM setup to build the application, both for Android and iOS.
Google Services configuration file is required for Crashlytics, which the app uses to report issues to the authors. If you want to build and run the app, you need to create your own Firebase project:
- Create a project on Firebase console.
- Add applications using the package name
eu.rajniak.cat.android
for Android andeu.rajniak.cat.iosApp
for iOS. - Follow the instructions and download the configuration file to the appropriate folder. Refer to setup Android or setup iOS for further details.
- Enable Crashlytics on the Firebase console.
In case you don't want to create a new Firebase project, you can still build applications with dummy configuration files. Keep in mind that applications with this config will not run.
This guide is inspired by code-with-the-italians/bundel.
To keep dependencies updated Version Catalog Update Plugin is used.
All you need to do is to run ./gradlew versionCatalogUpdate
.
The goal of the exercise was to build Kotlin Multiplatform Mobile applications for Android and iOS, with only UI written in platform-specific code (ComposeUI and SwiftUI).
Everything from ViewModel to Repository and data sources is shared.
Architecture follows the standard blueprint proposed in Guide to app architecture.
The shared module consists of Kotlin Multiplatform code that I use to share business and presentation logic between Android and iOS.
CatsViewModel is the class that UI platform-specific code interacts with. It prepares cats data displayed in the UI. It also listens to the actions that happen in UI.
It extends SharedViewModel that provides shared scope for coroutine work. I took inspiration from MOKO MVVM library, which I plan to use instead once there is more time.
CatsStore is responsible for providing Cats model data. It fetches data from CatsApi and to map it to model data used in application.
In the future, I might use the Mapper pattern for mapping data from external systems to domain models.
Data from CatsApi is intentionally not persisted, to provide up-to-date results.
CatsApi uses Ktor to fetch data from Cats API.
SettingsStorage provides platform specific implementations for key value storage. On Android it is Datastore.
All dependencies are provided by CatViewerServiceLocator. For such a small application, it perfectly serves its purpose.
For larger applications, I would recommend Hilt (DI library).
MainActivity is a single activity in the application as the only application entry point. Even with more screens, I would still recommend using single activity as screens can be represented as composable functions now.
CatsUI file and composable function currently represent the only screen in the application. I divided the file into multiple functions for clarity.
CatsView is single screen displaying list of Cats. Cats are represented by CatItem view. For filtering, view has been created and is displayed as a sheet.
CatsStore serves as adapter between Kotlin Flow world and SwiftUI.
Shared code has one test file with a bunch of basic unit tests. You can find them in CatsTest.
I test the shared code through the ViewModel interface and I use Fakes for external systems like networking and IO.
It allows us to test most of the business logic from a single interface. Once I add more logic to tested objects, I might need to write more isolated tests.
There is also one UI instrumentation test in the Android application module. It runs with dummy ComponentActivity provided by the test library. It allows us to test the UI in isolation.
This project was way simpler thanks to all the resources, libraries and projects I was able to find on this topic.
First and foremost, thanks to the people that created and maintained a free public service API that I could use to showcase KMM.
His repositories that showcase Kotlin Multiplatform were crucial to my work.
Especially, repository that demonstrates paging. I could not use the same multiplatform paging library (time constraints), but I want to try it in the future.
People at Touchlab and all the resources they provide publicly were especially helpful for my understanding of how Kotlin native threading works.
During the project, I was using Apple M1 Silicon and with it arm64 simulators. Support in libraries is getting better, but the community on #kotlin-lang Slack channel, was extremely helpful when I was fighting with this.
Special thanks also to all people that share their solutions around Kotlin Multiplatform. It is hard to find any up-to-date resources, so every single one of them counts.
Thanks to all the Kotlin Multiplatform libraries out there. I used:
Some things were left undone.
Coil on Android helps us with displaying GIFs, but this has not yet been implemented on iOS.
Coil on Android is already caching images on disk, but there is no caching of images on iOS.
There is almost no error handling implemented at the moment.
It would be nice to show errors when image loading fails, there is a network issue, etc.
It would be nice to be able to display images on full screen.
I could showcase Navigation with Compose, since I would have another screen.
I believe that even when developing a project alone, Continuous Integration service can help.
I would at least know that the build, tests and static analysis run with every new commit.
Another advantage is that I can be sure that someone else (another machine) can build it.
GitHub should be able to provide this functionality.
There is a lot of work on UI.
One thing that stands out is filtering, I don't think it is intuitive at this point. But it is there primarily to show work with Flows, so it is OK for now.
There are probably a lot of bugs, but one stands out.
Pagination, loading of new images is working nicely. But only when the user scrolls to the bottom of the list.
But new images are not loaded when the user gets to the bottom by filtering.