Jetpack Compose — Let’s Make An Application
What are we going to make?
Below are the screenshots of the application which we will try to make using Jetpack Compose.
A video of the application will be more helpful to understand
What are we using to achieve the above application?
So we will be using some libraries to achieve the above
- Jetpack compose — For UI
- Retrofit — To connect to an exchange rate service
- Hilt — For dependency injection, don’t want to do the dirty work by my own
- Room — To store the response locally, so that the number of calls to the service is less and also if network is not available we are still able to use the application (to a certain extent)
- Logger — For logging
- Testing libraries — To add few test cases to see if everything is working fine
So, which service we will be using to get the exchange rates. There are many available, the one which we will be using is
It provides a monthly usages of 1000 requests, it should be enough to complete the application, hopefully.
How are going to make the application?
Let’s first see how our build.gradle.kts looks like
Now most of the code in the above snippet is something I assume you would be familiar with, but what is updateTranslations task for?
As the name suggests, this is use for updating the application translations file. Let’s see the ./script/generate_translation_file.py file.
If you don’t know Python basic, then the gist of this code is to fetch the JSON content from ./app/src/main/assets/localization_en.json and create the TR.kt file, by using the key as a constant variable which can be used to get the value and show it on the UI.
This script is helpful in maintaining the localisation of the application. Check the localization folder to understand how it works.
And in the gradle you can see we have added the task to the prebuild, which runs the python script before the build is done and updates the translation file of Kotlin.
Why do we need the script? — Well we don’t. But in some ways it makes our life easier after spending 2–3 hours. So why not? And also it’s fun writing script.
Now our gradle is done. So let’s move to the next step of deciding how our project structure will look like.
Project Structure
It looks something like below
Basic details of all the packages are given below, you will get the idea of how each layer communicate with each other.
di is only for dependency injection.
This structure basically removes the business logic from UI, and also hides the place from where the data is fetched from. So in future if you want to change the service or make some changes to the logic of local storage then not every layer will be affected.
There are many great architecture patterns to go with, in my opinion which is easier and understandable to you, as a developer, you should go with that. Architecture is all about making developers life easy.
Now let’s look at each layer one by one, in such a way that in the end we will have our application up and running.
Let’s start with data and device layer
Layers structure
Data
This layer is basically for communicating with our remote service and make the requests and get the response
Let’s create our retrofit client first, we will be needing it to make the requests
Few things here to note,
- What is ChuckerInterceptor? — This is basically to check the requests/response details. You can check the https://github.com/ChuckerTeam/chucker link to know more about it.
- What is DebugInterceptor? — This interceptor is used to redirect the request from actual service to a local JSON files. DebugInterceptor is helpful while debugging the application and also not affecting our monthly usage. And also helpful in many scenarios where the actual service is not ready and this idea can be used to make the UI and also handles all the other edge cases without making any changes to the actual service. Check the class to know how it is implemented. It can be done in a more proper way, this is the most simplest way I can think of.
- What is ApiKeyInterceptor? — This is only used to add the API key given by the service to each requests header. Rather than adding it to the each request this intercepter will automatically add it before making the requests to the service.
Hope the explanation was enough to understand the ApiClient, any doubt let me know in the comments will be happy to help. This will be used by our service classes which will define our requests.
Now after the client we will be needing to create the service classes. In this particular application we will be needing only one service class, and the implementation is as below
ExchangeService.kt has only 2 functions, first to get all the currencies and other one to get the exchange rates for the source currency.
Now we need to add it to the DI module, so that when we need it in the repositories to make the requests then it can be injected.
Now with the above setup, we will be able to connect to https://api.apilayer.com/currency_data/ service and make the /list and /live requests.
We have completed our data layer for the remote, let’s create the device layer which will help us in storing the response in local storage and fetch whenever needed.
Device
Before jumping to Room Database, few things you need to know which you can get from https://developer.android.com/training/data-storage/room/. Basic knowledge will be needed before hand.
We need 2 entities for room
This will define our tables.
Now we need the DAO interfaces for making our queries for each table
Both the DAO interfaces have only three usage
- Get all details
- Insert new values
- Delete entries
Let’s create our database, which will connect both the entities and DAO
This is just the basic structure of any Room database. But hold on what are these TypeConverters. These are basically for room to know on how to store the Map<K, T>.
Now we have completed our Device layer as well. So the application can connect to remote and store the details in local storage as well.
Let’s see how we are going to create our domain layer which will connect to Data and Device layer.
Domain
Let’s see our repository class
ExchangeRepository.kt class is what works as a source of truth. It gets the details from data or device layer without letting other layers the source.
Check the response folder in core layer to know how the common response class is created to handle the loading, success, error and idle state.
Now the ExchangeUsecase.kt is something which becomes easy to create after all the handling is done by the repositories
And yes we will be using flows to update the app layer on the latest state. This will help us in updating the loading state and also know when the data is received.
Looks like we have created our data, device and domain layers. Lets see the app layer which will show the UI and also make the requests.
App
I will be taking only one screen, the home screen, and showing the implementation. Rest all are likewise and also less complicated.
Forgot to mention, we will be using ViewModel to handle the business logic and also update the UI.
First let’s check the HomeViewModel.kt class
- Declaring the state variables
2. Getting all the currencies
3. Getting all the live exchange values
Now these are main calls to the domain layer.
Now whenever a user input a number in the test field, we also update the exchange values. Let’s see how we are doing it
So this is what makes the whole HomeViewModel.kt class. There are other methods as well but those are basically to update the UI and other small stuff, which you can understand by going through the class.
Now let’s see how we will be making our HomeView
We will be dividing our UI into smaller composables which makes it easier to tests
We also need the exchange rate composable to show the rate and also update it based on the text field changes
Now we have our smaller composables, lets combine it into the screen
Now, when everything is combined — HomeView + HomeViewModel then we get
There are other parts of the UI and business logic as well, make sure to go through the application and code as well to understand the whole project.
It’s not a big project but helpful to start with something.
So in the end we completed our ExchangeRate application
NOTE
In this project you can see that I have hardcoded the API_KEY into
the project.
For this demo project this is okay, but it's not a good practice. Since the
key is being deleted from the service so it is not useful.
Make sure you follow guidelines on how to store or get the API_KEYS or
sensitive information.
Same goes for storing data in local storage.
There are many things we should consider or do while making the application, and this project doesn’t show all of them.
Take this as a demo application and make your own better versions of it.
Get the full source code from