To build a high-quality application, one has to pay great attention to the app architecture, since its role is decisive. In fact, the most fundamental decisions that determine the structure and interaction of components rely on the architecture. Hence, choosing the appropriate architecture is one of the key points in building a decent product. For the application to be reliable, scalable and easily testable, it is necessary from the start of development to lay down the principles of its operation. This helps to eliminate the rigid interconnection of elements, make it easy to read and modify code and ensure a balance between quality and development speed.

In the article brought to the attention of readers, we try to consider one of the options for implementing high-quality architecture using the stack of Kotlin (development language), MVVM (application component interaction logic), Koin (dependency injection) and coroutines (multithreading). Why this stack? Kotlin is the officially recommended Google language for Android development. Compared to Java, it eliminates a large number of boilerplate, has a beneficial Null Safety approach, data classes, etc. MVVM (Model-View-ViewModel) – an architectural pattern that allows one to separate UI, business logic and data sources. Koin is a lightweight library with a set of convenient functions for implementing the idea of ​​dependency injection. It makes it easy to create singletons, create custom scopes, and inject dependencies. Finally, coroutines are an interesting feature of Kotlin for multithreading. They can be regarded as light streams, the creation of which does not require a lot of resources.

Our test application will be called “Dog Explorer” and it will display a list of different dog breeds. We will pull data from the resource https://api.thedogapi.com/

First, create a new project in the Android Studio IDE. Among the proposed templates, select empty Activity. The development language is Kotlin; the minimum API level is 21 (Android 5); use androidx artifacts is true.

Connect the necessary dependencies for the project. First of all, add the Koin library. In the build.gradle file (app module), insert the following lines:

The $ koin_version variable is saved in build.gradle of the entire project. After ext.kotlin_version, add the line ext.koin_version = ‘2.0.1’. Please note that the project uses the second version of Koin.

For server requests, we will use the OkHttp and Retrofit 2 libraries. By the same principle, add to build.gradle (app module)

and in build.gradle of the whole project

We synchronize Gradle files.

Now we can prepare the first injections. In Koin, the object provider is the class Module, created using a function with the unexpected name module(). To create it, we use the wonderful ability of Kotlin – package-level functions and variables. Unlike Java, Kotlin allows you to store functions and variables not only in classes but also in files. In terms of meaning, this is similar to static utility classes in Java, but much more convenient. In the dogexplorer package, create a new di package, and in it the appModule file. In the file, create an object of class Module

The names of the file and variable do not have to match. In our project, this is done for convenience and comprehensibility.

To work with the network, we need 3 classes: OkHttpClient (for the actual requests), Retrofit (for conveniently converting our objects to JSON and vice versa) and the NetworkApi interface (in which we define server requests).

Create 3 functions. The first will return an OkHttpClient instance with the connected logging interceptor – an advantageous thing for controlling the data being sent and received.

The second creates a Retrofit instance. We specify Gson as a converter of our objects to JSON and vice versa, CoroutineCallAdapter for wrapping server responses into coroutines for asynchrony. The base URL for convenience can be rendered in constants. To do this, we create the utils package inside the dogexplorer package, then inside it, we create the constants.kt file, in which we add the line val baseUrl = “https://api.thedogapi.com/v1/“.

The third function should look like this.

Since we have not yet created the interface, we will add it to the project. We create the model package and inside it NetworkApi. Please note that you should not add a Kotlin file or class, but an interface.

Since we don’t need to create a new NetworkApi instance every time we add it to the ViewModel, we will use the single function to create singletons. Change the function appModule () so that it looks like this:

In order for NetworkApi to start to benefit, we’ll define methods in it to get a list of dog breeds and get images. Before writing methods, it is advisable to familiarize yourself with the documentation of our backend. It can be found at https://docs.thedogapi.com/. As you can see, the server will require an API key for each request. Therefore, first, we need to get them by filling out a simple form from the email and description of our application on the page https://thedogapi.com/signup. The email with the generated key of the string type “580e40f4-4144-8d75-a8fb-89a822a3126f” will be sent to the email you specified (Caution! This is an invalid key, just for example). Save your key in constants.kt file as a variable

The getBreeds () method will return us a list of dog breeds. First, you need to create a wrapper class into which the Gson library will convert the response from the server. You can create it manually by creating a class and defining the appropriate attributes for it. A list of required fields is listed at https://docs.thedogapi.com/api-reference/models/breed.

However, we highly recommend the second method, which is much more productive. Add the JSON To Kotlin Class plugin to Android Studio. To do this, go to the open plugin search menu File / Settings / Plugins. In the search field, enter the desired name, install the plugin and restart the IDE.

Next, in the models’ package, add the new entities package. We call the context menu with the right mouse click and among the options for the new item, select Kotlin Data Class File from JSON. This plugin allows one to generate Kotlin data classes from a JSON sample. You can get an example response at https://docs.thedogapi.com/api-reference/breeds/breeds-list, sending a test request. We copy the received response into the plugin input field, indicate the name of the Breed class and click Generate. The plugin will create 3 class dates: Breed, Height, and Weight. Date classes in Kotlin provide convenient functionality for creating the so-called POJO-classes designed only for data storage. They will automatically generate functions equals (), hashCode () and copy (), setters and getters for each attribute. The code is very concise, but effective.

Having received a wrapper class, we can finish writing a function to get a list of breeds. There are 2 options for its implementation. The first is to add a suspend modifier that allows pausing the function. The second option is to wrap the call in the Deferred interface, which is a non-blocking cancellable future. In this case, the call is more concise. Let us dwell on it.

An annotation over the function means that our application will make a get request to the endpoint base URL + breeds. For simplicity, we pass the header as the request parameter. In more complex applications, it is advisable to add it to a separate auth interceptor.

The functionality for working with the network is virtually complete. We now take up the implementation of the MVVM approach. If you are not familiar with it, we recommend reading the Hazem Saleh article.

First, let’s streamline our project. In the dogexplorer package, create the UI package, which, in turn, the main package. We transfer MainActivity to this package. Here we create a new class MainViewModel, which extends the ViewModel. To work, it needs networkApi (to request data from the server), coroutineScope (to manage coroutines) and LiveData to transfer data to Activity. Dispatchers.IO in the CoroutineScope constructor means that we use the recommended thread pool for I/O operations with a limit of 64 or more (if the device has a processor with more than 64 cores).

Add a function to get a list of breeds. In our coroutineScope, we create a new coroutine with the launch coroutine builder. Here, we get a list of breeds from the server and post it to LiveData.

We also add the cancellation of coroutine in the onCleared () function of our MainViewModel. When destroying the MainViewModel, the coroutines running in its coroutineScope will be stopped.

Now we need to add MainViewModel to MainActivity. In the di package, create a separate file viewModelModule.kt. We define the variable viewModelModule in it. We could, as well, add it to the appModule.kt file, but with the example below it will be more clear. This time we inject the dependency using a special viewModel () method, which allows injecting ViewModel descendants into an Activity or Fragment.

To get a link to MainViewModel, we just need to add one line to MainActivity.

In order for our injections to work, we need to start the Koin library. This happens when the application starts. To do this, create an App class that extends Application, and add code to its onCreate () method. Do not forget to register a new class in the application manifest.

The final touch – we display the results of the request to the server on the screen of our application. First, add a new dependency in build.gradle (app module). Sync gradle.

Change the layout of the main screen by adding a RecyclerView.

In the res/layout package, add the layout of the list item of our RecyclerView called dog_item.xml. It is as simple as possible and will display the name of the breed.

Create an adapter for RecyclerView.

Finally, we can query the data and display it in the DogAdapter. To do this, add the code in the onCreate MainActivity method

Now we can launch our project and radiate pride as we have just mastered a new stack of Android development.

5 (100%) 2 votes