Hello everyone,
I hope you are doing well. Welcome to this project, if you are a dog lover and want to learn more about the different dog breeds then you are at the right place. 😊 This is the Jetpack Compose Android app which shows you all the dog breeds of this world. And let you see the details, images, and subbreeds for every dog breed. The app will also let you add/remove breeds to/from the favourites. Dog Breeds App can be used offline as well and it is available in both light and dark themes.
On the first run, the app uses the Dog CEO API to load data into the app. For later uses the data is persisted into the app using the database. So that users can also use the app offline.
Minimum SDK Version: 21
compile SDK Version: 33
Build System : Gradle
The architecture of this project is MMVM (Model View ViewModel) Clean Architecture. The app is architectured in a way that it would be easier to understand for other developers and reviewers. I have used clean architecture because it goes a step further in segregating the code base’s responsibilities and concerns. The logic of the actions that can be performed in the app is abstracted. Clean Architecture could also be combined with MVP (Model View Presenter) but I have used it with MVVM because Android Architecture Components already has a built-in ViewModel class. I have tried to follow the recommended app architecture from android.
The architecture of the app is divided into three layers.
- Ui Layer (Presentation Layer)
- Domain Layer
- Data Layer
The responsibility of the UI layer (or presentation layer) is to display the application data on the screen. In this project, the UI layer includes UI elements such as Jetpack Compose functions, State Holders, and the UI States. All the UI element designing is done using declarative UI (Jetpack Compose) instead of XML.
This app, for example, uses a DogBreedsListViewModel class as a state holder to produce the UI state for the screen displayed in that section. For each screen, I have created UI states to ensure Unidirectional Data Flow. For example, the state of a screen displaying dog breeds is called DogBreedsListUiState. I have followed android guidelines for UI Layer to come up with a better solution.
The image above shows the transporting of data from data layer directly to the UI layer but I am also using domain layer in between for better abstraction. The UI layer part of the diagram explains how I am handling UI states.The domain layer sits between the UI and data layers. The domain layer is responsible for encapsulating complex business logic, or simple business logic that is reused by multiple ViewModels. It includes all the Use Cases of the app, for example, DogBreedsUseCase. Moreover, it also includes our database entities.
The data layer contains business logic, it is made of repositories that contain zero to many data sources. In our case, the DataRepository contains two data sources such as RemoteDataSource and LocalDataSource. The RemoteDataSource fetches data from API, whereas, LocalDataSource gets data from the Room database. The DataRepository is responsible to decide whether the data from a remote source or local source should be forwarded to the domain layer and then later to the UI layer.
I am using Hilt dependency injection library to provide all the dependencies I have to pass among different layers.
The diagram below briefly gives an overview of the architecture of the project.
I have used the dependencies which are part of Jetpack and also some third-party libraries. The project dependencies which are used in this project are as follows:
- Compose - Defines UI programmatically with composable functions that describe its shape and data dependencies.
- Compose Foundation - Write Jetpack Compose applications with ready-to-use building blocks and extend the foundation to build your design system pieces.
- Compose UI - Fundamental components of compose UI needed to interact with the device, including layout, drawing, and input.
- Compose Material - Build Jetpack Compose UIs with ready-to-use Material Design Components.
- Arch Core - Helper for other arch dependencies, including JUnit test rules.
- Room - Create, store, and manage persistent data backed by an SQLite database.
- Hilt - Dependency injection plays a central role in the architectural pattern used.
- Navigation - Build and structure your in-app UI, handle deep links, and navigate between screens.
- Kotlinx Coroutines - Library Support for coroutines. I used this for asynchronous programming to obtain data from the network.
- Retrofit - Type-safe HTTP client and supports coroutines out of the box.
- Moshi - JSON Parser, used to parse requests on the data layer for Entities and understands Kotlin non-nullable and default parameters.
- okhttp-logging-interceptor - Logs HTTP request and response data.
- Hilt - Dependency injection plays a central role in the architectural pattern used.
- JUnit - This was used for unit testing the database, the use cases, and the ViewModels.
- Mockk This is a mocking library for Kotlin. I used it to provide test doubles during testing.
- Truth - Assertions Library, provides readability as far as assertions are concerned.
- Espresso - Used for writing Android UI tests for our DAO.
- Coil - Image loading for Android backed by Kotlin Coroutines.
- Accompanist Pager - A library that provides paging layouts for Jetpack Compose.
- Accompanist Navigation Animation A library that provides Compose Animation support for Jetpack Navigation Compose.
- Kotest Assertions - I am using it in the test cases. Kotest calls types of state assertion functions matchers.
I have tried to cover all three layers i.e. UI, data, and domain with our test coverage. But I assume, with time, the coverage can be improved.
The data layer has an instrumented test for the database more specifically DAO. The database is our main source of data after the first run of the app that's why I found it important to be tested.
The Use Cases are also tested to ensure whether they call the right repository methods.
In the UI layer, I have tested our state holder classes i.e. ViewModels.
The app is available in two themes Light Theme and Dark Theme. The themes can be changed by the user by clicking the bulb💡 icon on Dog Breeds List. The proper error screen is shown if the user tries to use the app for the first time without the internet. The user can also retry to load the data after connecting to the internet.
The user has also been informed through the Snackbar when the app is being used offline. In offline mode, some images might not load properly but the user can still use the app and can add and remove dog breeds to favourites.
You can see screenshots of the app below:
I hope you like my work. Thank you! 🙌