This article series contains parts of my thesis about Kotlin Multiplatform. The thesis is based on my Fuller Stack project which consists of an Android App, a Kotlin React.JS app and a Ktor server. The full thesis pdf can be found here.
Part 1 Introduction and the Server
Part 2 The shared module
Part 3 The Web App
Part 4 The Android App (This part)
Part 5 The Summary
Originally this part was supposed to be the last part of the article series, however the resulting article was just to big so is split it into two parts.
The app
Kotlin/JVM
Kotlin started out as a language for the Java Virtual Machine. This means that Kotlin targeting the JVM is the most used and refined part of the Kotlin ecosystem. Kotlin offers 100% interoperability with Java. Consequently, Kotlin can be introduced gradually to a Java codebase.
Kotlin/JVM can be used for building applications in every environment that Java can be used in, which includes:
- Desktop applications;
- Android apps;
- Servers.
In this MSc project Kotlin/JVM is used on the Ktor server and in the Android app.
Modules
The main Android module uses three sub-modules:
- Shared - provides shared functionality;
- Authentication - provides user authentication functionality using the Auth0 library;
- Framework - contains shared Android specific classes and interfaces for the main and Authentication sub-module.
Architecture
Model-View-Viewmodel
The Android architecture in this MSc project makes use of the official Android Architecture Components which recommend using the MVVM (Model-View-ViewModel) architecture. The Model in MVVM should represent business logic which in this project resides in the domain layer in the form of use cases discussed before.
View layer
Without going into details, this MSc project uses Android Fragments as the View. Fragments are reusable portions of the UI but in this project they always represent a single screen.
All three screens are represented by a fragment and the bottom buttons change which fragment is shown. The Android app for this MSc works in the same way.
Briefly speaking fragments are responsible for showing the UI and reacting to a user input. Sometimes they do more than that, but I will omit such discussion to keep the explanation concise. In this MSc project they work in the same way that React.JS components do. They react on data changes and send user events to the presentation layer.
Presentation layer
The ViewModel is the heart of the presentation layer and is a mediator between the View and the Domain layer.
The data for the View is exposed through so called LiveData which basically is an implementation of the Observer pattern. It allows the View to listen to LiveData data changes but it is only emitting changes if the fragment is in correct state (i.e. the fragment is shown and not closing).
User interaction usually revolves around calling ViewModel functions based on the View callbacks. Most of the time the ViewModel functions reach out to the Domain layer and then update the corresponding LiveData which updates the UI.
On more complicated screens like the notes list or the note editor all of the UI related data are encapsulated in a single class. Thanks to this the View is only concerned with one LiveData which holds all the necessary View data. This pattern is often called "Single View States". These Single View states are either represented by data classes or sealed classes:
data class NoteEditorState(
val note: ParcelableNote? = null,
val titleError: String? = null
)
In this case note holds the current user input in the editor and titleError holds the error message in case the user gives an incorrect title. Both of these states can happen simultaneously and that is why a data class is used for this state.
sealed class NotesListState {
object Loading : NotesListState()
data class ShowingList(val notes: List<Note>) : NotesListState()
}
In this case the the state can be in one of two states, either loading or showing a list. Both of the states cannot be shown at the same time, and that is why a sealed class was used for this state.
Architecture summary
Testing
The Android module only contains unit tests for the presentation layer and most of it are tests for ViewModels. The framework used for writing these tests is called JUnit which is the standard for writing JVM tests. The particular version used is JUnit 5.
Because the shared module already contains fakes for the database and the API, the Android module makes use of them. This means that the UseCases in the ViewModels are not mocked, their real implementation is used but in place of the database and the API a fake is used. Using real implementations in unit tests instead of test doubles could be perceived as integration tests.
Technologies used
A lot of libraries coming from the androidx package were omitted in order not to make this list too exhaustive. The list below contains the most notable ones:
- Android ViewModel - the official implementation of the ViewModel in Android;
- Android LiveData - an observer wrappper which is aware of the Android life cycle;
- Room - persistence library;
- Auth0 - provides an easy authentication integration in Android;
- Simple Stack - a library for navigation between screens;
- Retrofit - a type safe HTTP client;
- OkHttp - a networking library;
- Coil - provides fast image loading;
- Kotlin Coroutines - allows for using asynchronous programming
- Kotlin features. It is mostly used in the Android presentation layer.;
- Klock - library used for formatting the notes timestamps;
- Kotlin Serialization - allows for retrofit to automatically serialize
- JSON responses and requests;
- Kodein - provides dependency injection from the Android module.
Testing libraries:
- JUnit5 - a testing framework for the JVM;
- Mockk - allows for mocking in Kotlin using a DSL;
- Coroutines test - contains utilities for easier coroutines testing.