Building an Android System Design Architecture for Efficient Image Uploads with Clean Architecture

As mobile app development continues to grow in popularity, it has become increasingly important for developers to have a proper understanding of system design architecture. This is especially true for android app development, as the platform has become increasingly complex and feature-rich over the years.

The purpose of system design architecture is to provide a structure for building and organizing software applications in a manner that is both scalable and maintainable. In other words, it helps ensure that your app will work properly today, tomorrow, and years from now, regardless of the changes that may occur in the technology landscape.

To illustrate the importance of system design architecture, let’s consider the sample case of a mobile app that allows users to upload images to a server. In this app, we want the user to be able to upload more than 1000 images while one image is still uploading. We also want to hold all images and only dispatch one at a time, while allowing the user to interact with other functionalities of the app while the uploading process is ongoing.If a network fail, we want to be able to upload the last image that failed till we no longer have any image to upload, if network is not available, we want to be able to detect when there is network and continue from the last uploading.

Here are some of the libraries we will be using:

  1. — for handling network requests and communication with the server
  2. — for efficient HTTP request/response handling
  3. — for parsing JSON responses
  4. or — for image loading and caching
  5. — for local database storage
  6. — for background tasks and scheduling
  7. — for real-time data updates and observing changes
  8. — for managing UI-related data
  9. — for managing concurrency and background tasks

Here is the high level explanation
User Interface: The app will have a user interface that allows the user to select images from their device and initiate the upload process. The user interface will also display the progress of the upload process and allow the user to interact with other functionalities of the app while the uploading process is ongoing.

  1. Image Upload Manager: The Image Upload Manager will handle the uploading process and will be responsible for managing the queue of images waiting to be uploaded. It will use WorkManager to handle the background tasks and scheduling of the upload process.
  2. Network Connection: The app will need to check the network connection to ensure that the images are being uploaded when the network is available. It will use the ConnectivityManager to detect network status changes and determine if the app can proceed with the upload process.
  3. Local Database: The app will use Room to store data locally. It will store the images that are waiting to be uploaded and the status of the upload process. This will enable the app to resume the upload process from where it stopped in case of a network failure.
  4. Server-side Architecture: The server will handle the image upload process and store the images in a database. It will receive the images from the app via HTTP requests using Retrofit and OkHttp libraries.
  5. Error Handling: The app will handle errors that may occur during the image upload process. For instance, if there is a network failure, the app will store the last image that failed to upload and resume the upload process once the network is available again.
  6. Data Management: The app will use LiveData and ViewModel to manage the data between the UI and the database. LiveData will enable the UI to observe changes in the data while ViewModel will manage the UI-related data.

In order to achieve these goals, we will use Clean Architecture and MVVM (Model-View-ViewModel) as the basis for our system design. Clean Architecture is a popular design pattern for building scalable, maintainable, and testable software applications, while MVVM is a software architectural pattern that separates the presentation of data from the underlying data model.

Now roll your sleeve and join me in the flow to achieve this

We start from the inner layer which is the Domain layer

Domain Layer

The Domain layer contains the business logic of the application. This layer defines the use cases of the application and how they should be executed.

In the domain layer, we can have use case named DispatchImageUseCase, which will handle the dispatching of images to the server. This use case will interact with the UploadImageUseCase and the RetrievePendingImagesUseCase to dispatch images to the server in a sequential order while ensuring that only one image is dispatched at a time. The DispatchImageUseCase will also interact with the WorkManager to schedule the dispatching of images in the background.

Here is the updated list of use cases in the domain layer:

  1. UploadImageUseCase: This use case will handle the uploading of images from the local database to the server. It will ensure that the images are uploaded in a sequential order and handle errors that may occur during the upload process.
  2. CheckNetworkStatusUseCase: This use case will handle checking the network status of the device. It will determine if the device is connected to the internet or not and will provide this information to the Image Upload Manager.
  3. RetrievePendingImagesUseCase: This use case will retrieve any pending images from the local database that have not been uploaded to the server. It will provide this information to the DispatchImageUseCase, which will handle the dispatching of these images to the server.
  4. CancelImageUploadUseCase: This use case will handle the cancellation of an ongoing image upload process. It will remove the ongoing upload process from the queue and update the status of the image to indicate that it has been cancelled.
  5. ResumeImageUploadUseCase: This use case will handle resuming an image upload process that was interrupted due to a network failure or other errors. It will retrieve the failed uploads from the local database and attempt to upload them again once the network connection is available.
  6. DispatchImageUseCase: This use case will handle the dispatching of images to the server. It will interact with the UploadImageUseCase and the RetrievePendingImagesUseCase to dispatch images to the server in a sequential order while ensuring that only one image is dispatched at a time. It will also interact with the WorkManager to schedule the dispatching of images in the background.

Presentation Layer

The Presentation layer handles interactions with domain layer which contain the business logic. It serves as a bridge between the domain layer and the UI layer.

In the presentation layer, we will implement the view models that will provide a bridge between the UI layer and the domain layer. The view models will be responsible for retrieving data from the domain layer and preparing it for display in the UI. They will also handle user interactions and trigger the appropriate use cases in the domain layer.

In this app, we will have the following view models in the presentation layer:

  1. ImageListViewModel: This view model will retrieve a list of images from the ImageRepository in the domain layer and prepare it for display in the ImageListActivity in the UI layer. It will also handle user interactions, such as clicking on an image to view its details.
  2. ImageUploadViewModel: This view model will handle the upload process for images in the ImageUploadActivity in the UI layer. It will use the UploadImageUseCase in the domain layer to handle the upload process and provide updates on the upload status to the UI.
  3. ImageDetailViewModel: This view model will retrieve the details of an individual image from the ImageRepository in the domain layer and prepare it for display in the ImageDetailFragment in the UI layer. It will also handle user interactions, such as deleting an image or retrying a failed upload.
  4. ImageSelectionViewModel: This view model will retrieve any pending images from the ImageRepository in the domain layer and prepare them for display in the ImageSelectionView in the UI layer. It will also handle user interactions, such as selecting or deselecting images for upload.
  5. NetworkStatusViewModel: This view model will retrieve the current network status of the device from the NetworkRepository in the domain layer and provide updates to the UI through the NetworkStatusView in the UI layer.

The presentation layer will be responsible for coordinating the flow of data between the UI layer and the domain layer. The view models will retrieve and prepare data from the domain layer for display in the UI, while also handling user interactions and triggering the appropriate use cases in the domain layer.

UI Layer

The UI layer is responsible for presenting data to the user and receiving input from the user. It is kept separate from the business logic and data storage layers to allow for easy modification and testing. The UI layer communicates with the other layers through interfaces.

In the UI layer, we will implement the user interface components of the app, such as the activities, fragments, and views. The UI layer will communicate with the domain layer through the view models to retrieve and display data to the user.

In this app, we will have the following components in the UI layer:

  1. ImageListActivity: This activity will display a list of images that have been uploaded to the server. It will retrieve the images from the domain layer and display them in a RecyclerView.
  2. ImageUploadActivity: This activity will allow the user to select and upload images to the server. It will use the UploadImageUseCase in the domain layer to handle the upload process.
  3. ImageDetailFragment: This fragment will display the details of an individual image, such as its name, size, and upload status. It will retrieve the image details from the domain layer and display them in the UI.
  4. ImageSelectionView: This view will allow the user to select images from their device’s gallery to upload to the server. It will use the RetrievePendingImagesUseCase in the domain layer to retrieve any images that have not been uploaded and display them in the UI.
  5. ImageUploadStatusView: This view will display the status of an image upload process, such as the number of images that have been uploaded and the number of images that are still pending. It will retrieve this information from the domain layer and display it in the UI.
  6. NetworkStatusView: This view will display the current network status of the device, such as whether it is connected to the internet or not. It will use the CheckNetworkStatusUseCase in the domain layer to retrieve this information and display it in the UI.

The UI layer will be responsible for creating and managing these components and providing a seamless and intuitive user experience for the app. The view models will communicate with the domain layer to retrieve and update data, while the UI layer will handle the presentation and interaction with the user.

Data Layer

The data layer handles interactions with data sources, . It serves as a bridge between the domain layer and the data source layer. It isolates the domain layer from outside like api, database and other source.

In the data layer, we will implement the repositories that will provide access to the data sources and perform CRUD (Create, Read, Update, Delete) operations on the data. The repositories will communicate with the domain layer and provide the necessary data for the use cases to perform their tasks. In this app, we will have the following repositories:

  1. ImageRepository: This repository will provide access to the local database where images are stored. It will implement methods for retrieving, adding, updating, and deleting images from the database.
  2. NetworkRepository: This repository will handle network-related operations, such as checking the network status of the device and dispatching images to the server. It will interact with the WorkManager to schedule the dispatching of images in the background.

The implementation of the WorkManager will be in the NetworkRepository, where it will be used to schedule the dispatching of images to the server. The WorkManager library will handle the scheduling of background tasks, ensuring that they are executed efficiently and reliably, even when the app is not in the foreground or when the device is in a low-power state.

Datasource Layer

The data layer handles interactions with data sources, . It serves as a bridge between the data layer and the outside world. It implement data layer to communicate with api, database.

In the data source layer, we will implement the data sources that will provide the raw data for the repositories. In this app, we will have the following data sources:

  1. ImageLocalDataSource: This data source will provide access to the local database where images are stored. It will implement methods for retrieving, adding, updating, and deleting images from the database.
  2. ImageRemoteDataSource: This data source will handle communication with the server for image uploads. It will implement methods for uploading images to the server and handling network-related errors that may occur during the upload process.

The data sources will communicate with the repositories and provide the necessary data for them to perform their CRUD operations. The data source layer will abstract away the details of the data storage and communication with the server.

One last thing we need to keep in mind is having a model for each layer and a mapper helps in Clean Architecture by ensuring a separation of concerns and reducing coupling between layers. Each layer has its own specific model that reflects its business logic and requirements. The mapper then converts data between models, allowing for easy communication between layers without exposing unnecessary details or dependencies. This approach makes the code more modular, testable and maintainable.

In conclusion, by following the outlined approach of separating the application into different layers, we can build a scalable Android application that is easy to maintain, test, and extend. By separating the concerns of the application into distinct layers, we can make changes to one layer without affecting the others. This makes it easier to test individual components and make changes to the application without affecting its overall functionality.

The use of domain-driven design principles and clean architecture also helps to ensure that the application is flexible and adaptable to changing requirements. By focusing on the business logic of the application and separating it from the details of the implementation, we can build an application that is easy to modify and extend over time.

In addition, the use of the WorkManager API for uploading images to the server ensures that the application can handle large numbers of uploads while still allowing the user to interact with other parts of the application. The WorkManager API also provides the ability to resume uploads from where they left off in case of network failures, ensuring that the application is robust and reliable.

Overall, this approach to building an Android application provides a solid foundation for building scalable, maintainable, and robust applications that can adapt to changing requirements over time. we can ensure that our app will work properly today, tomorrow, and for years to come.

I tried my best to make this lesson clear and short as much as possible. Please, bear with me for any omitted steps. I am just trying to share my knowledge.

If you have any questions or corrections, I will be glad to know.

You can mail me personally — murainoy@yahoo.com

|

Thanks for reading.

--

--

Senior Mobile Engineer | Android | IOS | Flutter | Kotlin Multiplatform

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Muraino Yakubu

Senior Mobile Engineer | Android | IOS | Flutter | Kotlin Multiplatform