Did you know that an average user checks their device more than 80 times a day? Make each time a real pleasure with beautiful HD wallpapers from WallR. Let your device be a treat to your eyes every-time you check it.
- Introduction
- Screenshots
- App Details
- App Features from an User's Perspective
- Project Setup Notes
- Acclamations
- Libraries
- About the Author
- License
WallR is an open-source Android wallpaper app written in Kotlin, keeping in mind proper coding guidelines and app architecture so that it is easy to maintain and scale.
Salient features of the app :-
- Clean Architecture With MVP
- Dagger 2
- RxJava 2
- Unsplash Api
- Firebase Realtime Database
- Room Database
- Shared Preferences
- Retrofit
- Glide
- Unit Tests
- Instrumentation Tests
- Architecture
- Dependency Injection
- Multi-threading
- Remote Data Source
- Data Persistence
- Networking
- Image Loading
- Testing
A proper app architecture implements the SOLID principles and ensures :-
- Easy
scalability
as features are independent of each other. - Easy
maintainance
since the classes are decoupled in nature thus we need to make changes only to the desired class and it would be reflected everywhere. Testability
, since the abstraction layers are easy to mock and test.
Thus, for WallR, Clean Architecture with MVP was chosen as the architecture.
In Clean Architecture, the code is separated into layers in an onion shape with one dependency rule: The inner layers should not know anything about the outer layers. Inner layers contain business logic, whereas the outer layers contain implementation and the middle layer contain Interface Adapters. Each ring represent one layer of abstraction.
A diagram representing different layers, components and how they communicate with each other in the app :-
MVP (Model View Presenter)
is suitable for the presentation layer.- Views are dumb and implement
Passive View pattern
. It is a set of interfaces that could be implemented by any Android view, such as Activities, Fragments, Adapters or Custom Views. - Presenter serve as a middleman between views (abstractions over Android specific components) and the business logic (Interactors/Use Cases). They handle user interactions, invoke appropriate business logic and send the data to the UI for rendering.
Presenter
does not depend on Android classes hence improves testability.
- A simple example of Use Case would be "Fetch new wallpapers". Each Use Case is a reusable component that executes a specific business logic. It fetches the data from a repository, executes the business logic and returns the result to the presenter.
- Repository is responsible to create an abstraction of the data sources from which the Use Cases get the data to act upon.
- Business logic shouldn’t know where the data comes from.
It is a software design pattern that implements inversion of control for resolving dependencies. Implementing proper dependency injection in our apps allows us to have :
- Testable classes as dependencies which are injected from outside the class can be easily mocked
- Re-usable and interchangeable components.
- Scoped dependencies so that classes can share the same dependency state as and when required without having to create a new instance every time.
- Dagger 2 is a dependency injection framework, which makes it easier to manage the dependencies between the classes in our app.
In this project the dependency graph is constructed via :
- An App Component which is used to bind the dependency graph to the application.
- An Android Injection Module which helps us inject into the android framework classes.
- An App Module which contains all the dependencies required at the app level.
- An Activity Builder Module which creates the various activity subcomponents.
- The Service Builder Module which creates the service subcomponent.
The Main Activity Subcomponent
has the Fragment Provider Module which in-turn creates the fragment subcomponents.
The various dependency scopes used in this app are :
- Per Application - This is similar to the Singleton scope where the dependency lasts for the entire lifetime of the application.
- Per Activity - This is the scope where the dependency lasts as long as the activity lasts.
- Per Fragment - Where the dependency is attached to the lifecycle of the fragment.
- Per Service - Where the dependency is attached to the lifecycle of the service.
A diagramatic representation od the Dependency graph for this project :-
To provide the users with a fast and responsive app, heavy work such as network calls, database operations, file operations or other background tasks need to be done on threads other than the UI Thread
. This is where multi-threading comes into play.
Schedulers like Computation
, IO
, Android Main Thread
have been used to effectively juggle between background and foreground activities. However, all of this is done using a layer of abstraction for the background and foreground schedulers so that they can be easilty tested.
RxJava is used to do all the heavy lifting in seperate background threads
and to return the result of those operations to the UI Thread
so that they can be used or displayed to the user without any stutters or lags in the app.
In this project the various reactive streams used are :
- Observables are used in cases such as fetching an image from a remote source while updating the the progress on the screen and at last setting the image as the wallpaper of the device.
- Single is used in places where there is a one time operation like retrieving a bitmap from a prevoiusly saved image file and returning the bitmap so that some operation can be done using it.
- Completable where only the result success or the error state is required such as clearing the local image cache.
In order to encash on the multi-threading capability of RxJava, the various schedulers that are used are :
- IO Scheduler - is used for tasks such as network requests, file write operations, etc.
- Computation Scheduler - is used to perform CPU intensive tasks like generating or processing an image bitmap.
- Mainthread Scheduler - represents the `UI Thread` and is used to observe the data coming from the various reactive streams.
Autodispose is used to dispose off the various streams.
In this project, two sources of wallpaper have been used :-
Unsplash API
which is used when an user searches for any specific wallpaper tagFirebase Realtime Database
which is used to provides a cached copy of the various categories of wallpapers from Unsplash due to the limited number of api requests available from unsplash derectly.
The Unsplash API is a modern JSON API that surfaces all of the info required for displaying various wallpapers to the users.
The JSON
response obtained from the Unsplash API
using Retrofit, is trimmed down to the following data model :
To convert the data into the above model, the GSON Converter Factory is used which maps the data into the model using the SerializedName
provided with each field.
The Firebase Realtime Database is used to cache images from Unsplash
as the number of API requests
available in the Unsplash API
is limited. Thus the app fetches all the wallpaper data required for shocasing the default wallpapers from Firebase
itself. The basic structure of the firebase database for this project looks like :-
The data model to which the JSON
response from the Firebase Realtime Database
is mapped, looks like :-
To convert the data into the above model, gson is used which maps the data into the model using the variable names
.
Data Persistence is used to remember the user preferences
and wether the user is pro or not using the shared preferences.
The Room database is used to run the automatic wallpaper changer which uses a locally saved collection of images and changes the device's wallpaper from time to time.
In WallR
, the database is used behind an abstraction so that it can be tested and also future changes can be easily incorporated.
The database
looks like :-
The shared preferences are used under a layer of abstraction which helps us to test the logic and also makes provision for easy integration of api changes in the future.
The layer of abstraction also allows us to easily migrate from Shared Preferences to any other storing framework as changes under the layer will be reflested all througout the app.
Network operations are heavy tasks which should be performed in background threads so that the user does not face any app not responding
errors due to the UI Thread
getting clogged up. Thus I have used Retrofit
with RxJava.
Retrofit is also used behind an abstraction so that it can be tested and easily replaced with any other framework if needed.
RxJava Call Adapter is used to return the responses
wrapped in reactive streams
.
The GSON Converter Factory is used which map the response data
into various entity models
to be consumed.
Retrofit
deep down uses the OkHttp library for HTTP requests.
Image loading can be from various sources of data like a file path
or a bitmap
or a drawable resource
or it may also happen that the image needs to be processed befoer being used and glide caters to all that with very minimal boiler-plate
code with proper caching mechanisms
and hence was suitable for this project.
Glide is used behind an image loader abstraction so that any other image loading library can be easily used instead of glide if required.
TDD (Test Driven Development) is an effective way of developing the applicaion by incrementally adding the code and writing tests. It ensures us that the addition of new features have not mistakenly broken anything else and gives us more confidence to push out code.
While writing tests we often come across certain edge cases which were not thought of while writing the normal code thus helps us build a more robust and stable application.
Unit tests in this project are written using the Mockito framework and run using the JUnit runner. These tests are very fast in nature and help us quickly test our codebase for any breaking change.
The following unit test code coverage values reflect lower than actual coverage values due to certain issues in calculation of code coverage in kotlin. More reports of such issues can be found here.
User interface (UI) testing lets you ensure that your app meets its functional requirements and achieves a high standard of quality such that it is more likely to be successfully adopted by users.
One approach to UI testing is to simply have a human tester perform a set of user operations on the target app and verify that it is behaving correctly. However, this manual approach can be time-consuming, tedious, and error-prone. A more efficient approach is to write UI tests such that user actions are performed in an automated way. The automated approach allows us to run tests quickly and reliably in a repeatable manner.
- Daily new wallpapers
- More than 10 Categories of wallpapers
- Search for wallpaper of your choice from a collection of over 100k+ images
- Minimal wallpapers
- Create your own Material or Gradient or Plasma wallpaper
- Quick set wallpaper
- Edit wallpapers before setting them
- Download wallpaper to device
- Crystallize any wallpaper
- Share wallpapers with your contacts
- Preview full screen wallpaper before setting
- Add wallpapers to collection for future use
- Enable automatic wallpaper changer to automatically change wallpapers periodically
- Add any external image to collection to use it as a wallpaper or to crystallize it
-
The google-services.json file has been purposely ignored by git due to security purposes. Please login to Firebase console and create a new project and obtain your own google-services.json file and paste it at
app/src/debug
orapp/src/release
directory as per your build flavour to set it up and running.
For more information, please refer the docs. -
This project can be directly built from the
command line
using the command :gradle build
for Windows OS../gradlew build
for Linux/Mac OS.
-
The
unit tests
can be direclty run from thecommand line
using the command :gradle test
for Windows OS../gradlew test
for Linux/Mac OS.
Pro Tip : If your build fails, please ensure that your JAVA_HOME
value in environment variables is set to JDK V8. Please refer to this gradle issue.
WallR was selected as one of the best customization apps by Android Authority, Android Police. It also recieved huge number of warm and positive feedbacks and reviews at XDA Developers Community.
RxJava 2 - helps with multi-threading.
Autodispose - used for disposing off observeables created using RxJava.
Unsplash - the source of every wallpaper used in the app.
Retrofit - used for networking.
Dagger 2 - used for dependency injection.
Mockito, - helps with testing by mocking classes.
JUnit 4 - helps in running tests.
Toasty - a library for showing fancy toasts to the user.
WaveSwipeRefreshLayout - a custom viewgroup which provides a fancy way to refreshing items on swipe.
Android-SpinKit - provides fancy loading animations.
SmartTabLayout - a custom material tab layout.
Material Dialogs - easy to use material dialogs.
Glide - used for image loading.
Recyclerview Animator - used to animate recycler view items.
Firebase - used for caching images from unsplash.
Sliding up panel - provides a sliding up bottom panel.
Circle ImageView - a custom imageview.
TapTarget View - used for showing hints to the user.
Material Spinner - a custom material spinner.
Rx ImagePicker - an image picker library wrapped with rxjava.
Photoview - a custom imageview with pinch zoom funtionality.
Ucrop - an image processing library which provides various editing options.
Guillotine-Menu - a custom navigation drawer.
Colorpicker view - a custom view which helps the user to choose a color in-order to generate custom wallpapers.
Android Developer with 2 years of experience in building apps that look and feel great. Enthusiastic towards writing clean and maintainable code. Open source contributor.
Copyright 2019 Abhriya Roy
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.