If you surf the Internet on a regular basis, you have noticed for sure that there is a war between Android and iOS users. I don’t know whether you are a participant in this war or not, but I know that developing for these two operating systems needs a set of specific practices.
Thanks to them, your application will be as good as possible. Today, I would like to present the best practices in Android development that we use at SoftwareHut. I have divided the whole article into the following sections:
1. General Technological Concepts
2. Code Quality
4. Frameworks and tools
6. Other useful tips and tricks
General Technological Concepts
If you don’t want to feel like an alien on the planet Earth in the Android world, you need to master these essential concepts:
- Activity lifecycle
- Material Design
This is the point where you can decide what the user will see on his current screen. You need to know ordering, differences and the purpose of the onCreate, onStart, onResume, onPause, onStop and onDestroy methods.
Remember that if you change the configuration or layout orientation, it will recreate the activity. Be aware of the fact that viewModels and savedInstanceState are methods that are meant to preserve state.
What’s more, the activity lifecycle is a familiar source of memory leaks.
Now, let’s move on to fragments, lifecycle and fragments transactions. Forget about using fragments if you don’t get fragment lifecycle and its transactions’ asynchronicity.
If you are embedding fragments, embed them into XML. Don’t forget about unsetting fragment listeners if you have stopped using them. Also, fragments shouldn’t be stored in a top-level list. Instead, you can clear it while destroying UI.
Do you know the cost of serialising objects to convert their state? Try storing item ID, and fetch it from the local database instead.
Fragment transactions are unpredictable – never use timer delays to wait for the fragment to be inflated.
The intent is an internal messaging concept. You can read more about it here.
Of course, this depends on the client, but using Google’s guide is recommended.
- Spacing in dp: 4, 8, 16, 24, 32,
- Concept of material surfaces elevated relative to each other
- Every clickable item should provide touch feedback,
- Use shadows to distinguish between elevation levels,
- Use design widgets: Toolbar, Floating Action Bar, TabWidget.
Also, you need to have a good grasp of Java or Kotlin.
We follow the best practices already written down in Google’s Java style guide.
- Effective Java (2nd Edition) by Joshua Bloch (available on Amazon)
- Clean Code by Robert C. Martin (available on Amazon)
You can find the best practices here.
Three simple steps to boost the performance of your Android application:
- Don’t over-engineer optimisation.
- Write correct, simple and concise code.
- Run performance tests and look for bottlenecks.
- Long UI inflation due to complex layout
- Serializing/deserializing heavy objects – consider using Parcelable
- Read/write operations in UI thread
- Allocating too many objects, which causes Garbage Collector to run and slows down the main thread
- Algorithmic mistakes – iterating over large array instead of hashmap, incorrect equals/hashcode implementation, unnecessary repetition of operations
Frameworks and tools
Just two rules to remember in this section:
- Rule number one – explore the GitHub and Android arsenal in search for already written code. Stay away from libraries that are no longer supported, deprecated or unstable. To achieve this, check the latest release date and the number of reported issues,
- Rule number two – use Dependency Injection and automated builds.
- Dagger – Dependency Injection
- OkHttp – http client
- Retrofit – API adapter
- Glide/Picasso – advanced image loader
- CircleViewIndicator – indicator for swipeable views
- Design Libary – material design widgets
- LeakCanary – memory leak analysis
- Fabric – error reporting
- GSON – json/object mapping
- rxJava – reactive streams
- rxJavaAndroid – helpers for managing android threads
- Dexter – managing runtime permissions
- Google data binding – data binding library for MVVM pattern
- Butterknife – view binding framework for the pre-MVVM world
Use clean architecture, period. Unless you are working on a project that you will throw away anyway. It doesn’t matter if you use MVP, MVC or MVVM. You know them well and what to expect of them.
Focus on keeping activities/fragments dumb, because they are only responsible for stuff related to android-view. Take a look at the example below:
Fun showHistoryOrder(historyOrder: Array<HistoryOrderItem>)
Its job is to set items to the adapter and nothing more. Business logic rules should already be prepared in HistoryOrderItem. It’s not the actual API model object but rather its wrapper with additional properties – cancelVisible and deleteVisible.
The actual logic should be prepared in VM/Presenter/Controller. Using DataBinding reduces lots of boilerplate and simplifies the presentation layer. If you are using Android dependencies or third-party layers, wrap them with your class. In general, only imports visible in Controller/Presenter classes should be contextualised.
When implementing the model, separate the data source from the service class. The service’s only job is to deliver ready objects. The data source is just a dumb delivery boy doing what we ask him to do.
Other useful tips and tricks
Be aware of the fact that using Handlers or Runnables can cause memory leaks. To animate views, use GPU-supported operations (translate, scale, etc.) instead of editing layout parameters. Invalidating a view tree is expensive.
Animations should be short – 100-200ms max.
The Material Design animation specifications – momentum, speed, weight and physics of objects – are another thing you should learn.
Working with background services
What about wake-locks? Be careful when acquiring them. If only one is needed, make sure that you release it in a final block. For scheduled operations, it’s better to use the firebase job scheduler. Unless time is critical in your application, it’s better to use an inaccurate time window for operations.
Add square’s LeakCanary to the library.
Common sources of memory leaks are:
- Anonymous classes/lambdas – don’t reference LifeCycled items in these blocks
- Handlers – anonymous handler implementation and Runnable will keep a referenced object inside a block, stopping Garbage Collection from reclaiming memory
- Keeping a reference to LifeCycled items in objects without clearing it – i.e. remember to detach the view from the presenter in onDestroy method
- Lifecycle-aware components – always check if you have a registered listener. Manual deregistration is required
- Non-static inner classes
- Static references to an activity, fragment or view
- Storing activities/fragments/views inside collections without clearing them
- Timer tasks – they are similar to anonymous classes
Adjusting to the device’s size
Have UI rules in mind – what should wrap the content and what matches the parent.
Don’t set a container’s size or the content’s size in dp. Otherwise, you risk inconsistency.
Setting padding is safe enough.
Don’t use separate layout resources for a view, unless this is necessary for building a proper tablet experience. If you will forget to test a view, you risk leaving a bug behind.
The minimum size of a clickable element is 48 x 48 dp.
Remember about the maximum and minimum size of image views as well as the fact that different devices have different screen resolutions.
Working with layouts
If you are looking at UI design, think of UIS as independent components. It’s better to group controls in reusable widgets than handling their actions separately in the activity.
- Use “include” and “merge” tags,
- Keep the XML tree shallow. Measured operation cost increases geometrically when too many items are embedded. Think about using constraint layout – it’s already production-ready,
- Don’t use linear layout weights with magic numbers trying to match design – design implementation must have firm rules.
I don’t recommend doing it. The application should fail as quickly as possible during the development process. An error reporting service with different identifiers will help you with debugging and releasing builds. Nullcheck alone is not enough to fix nullpointer exceptions – think about why it’s null, which class causes it to fail, and respond accordingly.
Think about LifeCycled components – an operation may finish if the view is no longer available.
Although Java is not a functional language, the Android world works great with the reactive streams concept. First, watch the introduction to rxJava.
Then read the rules written below:
- Use rxJava2 if possible.
- Choose correct stream names for better readability – Single, Maybe, Completable, not just Flowable and Observable.
- You can’t emit null objects via stream anymore. If you subscribe for a stream in a LifeCycled component, don’t forget to unsubscribe.
- A great way to simplify its process is to use CompositeDisposable, which can be cleared, for example, in the onDestroy method.
- Remember about performance when using operators – the cost of Garbage Collector’s work for allocating objects could get huge.
- Don’t overcomplicate – always think about readability first. In the future, you and your colleagues may forget the meaning of complex operators.