Are there best practices and things we should or should not do?
Google normally has good advice, but they tend to build to scale to a hundred engineers, and that includes "deprecated" stuff. You can go many times faster when building for only 1-2 Android engineers.
My rule of thumb is that if the example looks more convoluted than without it, it probably is. If it's good technology, the examples should make you think, "Oh shit, this is genius," not, "I'm too stupid to understand this."
Pick things up once something is actually a problem. Things with a fast ROI:
Kotlin: write 40% less code than Java
ViewModels: cleanly separates lifecycle stuff and reduces crashes
Network (Retrofit, volley): no more custom async and no crashes when the API changes a little
Compose: cut down all "recyclerview" code by 50%-80%, and have much more maintainable logic
Things with a negative ROI (feel free to argue for them):
Most architectures, e.g. MVP, VIPER, "clean". Architecture isn't a fixed pattern, it depends on what your app is trying to do. Most of the time it's like an apartment trying to emulate a hotel.
Butterknife: Replaced by Kotlin
Event bus: Replaced by Flow
RxJava: Replaced by Flow
Data binding or view binding: Replaced by Compose
Robolectric. It breaks for obscure reasons, takes forever to run (effectively locking you out of TDD). Espresso works. You shouldn't spend more time fixing tests than fixing code.
Android debugger. Idk, TDD catches plenty of bugs and plopping logs everywhere catches the rest.
Looper/threads: Replace with Coroutines
Some of the older UI things like animations, which might be obsoleted with Compose.
2. Koin is not a DI framework.
3. Avoid KAPT (at least in development builds). It's slow.
4. If you think about your UI as a state machine, this state machine _must_ be total. For all intents and purposes, any possible input can arrive in any possible state due to how the Android event loop and view callbacks work and you must handle it gracefully.
5. Your app can be killed and state restored, partially restored or not restored at any point in time (yes, even if you disable config change) and you must handle it gracefully.
6. Be mindful of energy saving and background tasks. The logic for background work changes frequently and manufacturers do not abide by the standards.
7. Do not blindly implement MVI or similar architectures because you read about it on the Spotify blog. Read research papers and in-depth material from the web dev community. They're always about 5 years ahead of the Android community.
- As a beginner I suggest narrowing your learning scope. What I mean is that focus more on developer.android.com and not some outdated material from a blogger and YouTuber. I am currently mentoring an Android beginner and it seems some YouTuber decides to teach him that every variable should be declared static. Yes, imagine the horror when I saw 20 or more static MediaPlayer variables. My point is, it might overwhelm you.
- Stick to the official JetBrains and Android channels for now.
- Keep everything updated. As soon as Android Studio or any third-party library releases an update, download and install it right away. This is much better than keeping everything mostly outdated.
- As a beginner, it's okay to start with MVC architecture. When you get comfortable as you go along, you can invest in learning the more advanced architecture. But MVC is still fine.
- I'm not sure if you want to dig right away with Jetpack Compose, if yes I suggest learn first on the XML layout. It's a good foundation regarding Android UI.
- Familiarize early in your learning the concepts of threading/coroutines.
- Learn Activity lifecycle as much as you can: https://developer.android.com/guide/components/activities/ac...
- Learn Android's Context: https://developer.android.com/reference/android/content/Cont...
I think that's it for now.
Do not do eager work at process startup (application onCreate, content provider onCreate, etc). Your process starts up for many different reasons, and work done there except for a narrow subset (crash reporting setup) is unlikely to apply to all of them. However, once the code is there it's hard to find what else might be depending on it later.
Do not use inheritance to share code. Androids APIs were designed to point you in this direction, but overrides upon overrides will make your life miserable. Once you have a base class from Hilt or AppCompat to provide composition for you (via DI or lifecycle), add no further layers of inheritance.
Avoid the event bus pattern. It was fashionable for a while but it turns the codebase to spaghetti. It tends to make error handling impossible or at least convoluted and debugging is a huge challenge.
In addition to the useful tips that others have posted, here is one. For every API function or class X that you encounter in an example, google for "X deprecated". It is insane how fast the platform has changed in the span of 2 years.
Curious as to why a new learner would go this route.
Get familiar with using Android Studio as your IDE. Read the Kotlin and Android documentation. Look at the (newer) Android example apps Google puts up on Github. Watch the (especially more recent) Android Developer videos on Youtube, especially the I/O ones (although some other deep dive ones are good too). Look at the codelabs.
If you're already programming, one thing to be aware of is how ephemeral Android lifecycle classes (Activity, Fragment and associated Views) are. At any time the phone can be shut off and they can go away and be killed, and any state will disappear. So be aware of storing state in ViewModels and this sort of thing.
Also, under the hood android has a Looper and a main thread with a message queue, and handlers. On an API level your Manifest will probably have a start Activity that is the main launcher, and which can launch other activities - but you don't launch them, you send an intent to launch them and then the system launches it. You don't have to understand all of this right off the bat but you do have to understand some of it, including how there is a main (UI) thread, and then you can launch network or disk access or computation calls on other threads.
A statement is easier to fix than a function, a function easier to fix than a class, a class easier to fix than a module, a module easier to fix than the whole app. Getting the architecture right is important, as it is more difficult to clean up later. For a big app people often put network (REST API) access into its own module, put analytics into its own module etc. Plus functions are broken down. If HN had a big app, account might have its own module, submission threads and comments might have its own module, search might have its own module etc. Whatever makes sense. For a small app which won't grow, modularizing like that may be premature.
Also within a module in its internal architecture, MVVM is the main architecture used. Something like clean takes more effort to put in and keep - what does a company do if senior people come in and do not like clean and aren't using use cases or business rules without dependencies etc.? If engineering is not already filled with good senior people committed to clean, who are not taking shortcuts to get features out etc. I would think twice about trying to use clean, for social reasons more than technical reasons.
Android is notoriously difficult to test. Where do you put your tests? There is the testing pyramid of UI/e2e at the top, features/integration in the middle and unit tests at the bottom - the bottom of the period being the most tests. Except you can't really directly unit test Android lifecycle classes like Activity or Fragment with JUnit. So what do you do? You use the Google suggested unidirectional data flow from ViewModels into the lean lifecycle classes, sending in a UI model to the lifecycle classes to create the UI from. You unit test in the ViewModel the various scenarios and edge cases going out to the UI/lifecycle classes. You can unit test in other places but this is a way of indirectly unit testing UI. There are also UI tests like Espresso higher up on the test pyramid.