After a long 2 month job search, I finally got the job I wanted. During this search, I had a few tech challenges to perform and interviews to face, each with their own unique set of challenges.
But let’s focus on the tech challenges. This is the most significant stage of the whole recruitment process. How your potential employer will see you, how the rest of your interviews will go, where you will land on the salary and progression ladder, it all boils down to how well you can prove your worth in this tech challenge.
Now don’t get me wrong. Even as a mid-level Android Developer, you need to know a whole lot. In fact, I curated this list of mid-level must-knows.
Despite knowing all of these, during some of my attempts at these challenges, I made some mistakes that stood out, and not in a good way. These ultimately made the interview process much harder for me. So I’m telling you so you don’t have to make the same mistakes as I did.
Now these might be obvious to some of you. You might read this and think Omg Eric you’re so stupid, these are beginners mistakes. Why should anyone hire you
Remember that some of these may not be at the forefront of every Android dev’s mind. They weren’t for me until recently.
So let’s just get into it.
Me as I explain my code during the interview
Testing Mistakes
How you write your tests and how you make your production code testable is a major part of the criteria, whether the job spec explicitly mentions it or not. I made a number of mistakes when it came to both of these points.
Testing Private Functions
Unit testing should follow this principle: For a given input, do I get an expected output? What are the inputs of an Android class? Public functions.
“Private functions are part of the implementation”. That’s how it should be seen. They don’t need to be exposed to classes beyond the class they exist in, even in tests. Instead, the test should aim to cover private functions by testing the public functions that make use of them.
I guess the idea here is that you should test your components in isolation. While you’d be right about that being a principle you should follow, private functions aren’t the components you should aim to be testing.
Use of @VisibleForTesting
This goes in-line with the previous point, as how would you test private functions without fully making them public? @VisibleForTesting.
This is a code smell. The fact that you’ve used this means that you had no other way to make your code testable.
And maybe it does have its place other than easy-hacky malpractices, if you think you have a good reason for using this annotation, do let me know down in the comments.
Not Identifying Integration Tests
Both unit tests and integration tests can be run exclusively on the JVM, so the line between the two may not immediately be visible.
The difference is unit tests cover your own classes’ logic in isolation. Integration tests cover your classes’ logic when interacting with other built components and libraries, or as it integrates with different levels of the stack as the official docs would put it.
An example of integration tests? Mock API testing. When you do this, you’re testing your code’s logic as it interacts with Retrofit and Moshi.
So what about testing with RxJava? It’s not your own code right? To be honest, I’m not too sure myself whether RxJava testing is considered as unit or integration, but because it operates so closely with the core of your code, I’m gonna spend happy days thinking of it as unit.
Now why was this a visible mistake when I did my coding challenges? It goes in line with the next point.
Bundling Too Many Tests in One Class
This one may seem stupid and obvious, and you’d be right. I decided to bundle in all of the testing logic from one use-case spanning all the way from the data layer to the activity.
In my mind I thought, it’s all one use-case right?
No, we need more separation than that. What really stood out here was I had the integration tests of the data layer in the same class as the unit tests of my domain layer. Keep those 2 separate at the very least.
Making Classes Open for Mockito
Kotlin classes are closed by default. In the past, this meant that they cannot be mocked with Mockito for use in tests.
Those are days of old. Mockito’s a bit better now. You no longer need to make classes open for them to be mocked.
Using Static Mapper Functions
Other than the fact that I used to call my mappers ‘ModelAdapters’, one mistake I made was putting all my mapping functions in a companion object.
Why is this a mistake? Surely, that just saves you the trouble of having to make an instance of your mapper when you use it yeah?
Testing. That’s why. You can’t mock return values for static functions. That’s the basic reason.
Production Code Mistakes
I also made a few mistakes based on what responsibilities certain classes should have, as well as not following some basic OOP principles.
Putting Data Source Logic in the ViewModel instead of the Model
Suppose the model contains functions to grab a user’s data from the local cache or the Network API, what components is meant to hold the logic that determines where the data comes from?
Apparently, it’s the Model’s job. The ViewModel’s only concern is it’s receiving the user’s data from the model. Where the data should be acquired from is the Model’s concern.
Not Encapsulating MutableLiveData
val stateLiveData = MutableLiveData<ActivityState>()
The above line of code is in the ViewModel. What’s wrong with it?
Your View can also access this as a MutableLiveData
. This means there’s nothing stopping the View from posting a new value to stateLiveData
which would be a heavy breach of architecture principles. It’s outright illegal.
private val _stateLiveData = MutableLiveData<ActivityState>() val stateLiveData: LiveData<ActivityState> get() = _stateLiveData
You should encapsulate the mutable type of the variable, and only expose it outside of the ViewModel as a regular ol’ immutable LiveData
so that the View can observe it but not post to it.
Handling onClick Logic in the ViewModel
I’m still really skeptical about whether this is a mistake, but more than one party had skeptical thoughts about having higher-order functions in your ViewModel to handle the onClick events of UI elements.
val userOnClick: (Context, User) -> Unit = { context, user -> val intent = Intent(context, UserDetailActivity::class.java) intent.putExtra(Intent.EXTRA_UID, user.id) context.startActivity(intent) }
I can see both sides of the argument. On one hand, the View should only concern itself with observing data from the ViewModel and presenting a UI based on that, whereas the ViewModel should handle the activity’s business logic.
On the other hand, you’re introducing Android objects such as Context and Intent into the ViewModel which can be considered a breach of the architecture’s principles.
I’d love to know what more people think so please, let me know in the comments.
Other Common Mistakes
Now everything from here on aren’t mistakes that I personally made, but they are mistakes I’ve heard have been the reasons for many other candidates failing to pass the tech challenge.
Not having separate data models to in-app models
Unless you’ve got a manually written type adapter for consuming your APIs, chances are you have a data class that uses the built-in type of adapter of Moshi, Gson, Jackson, whatever you got, and you’re using this to consume your API and receive the data from it.
Don’t use this same data class in your activity. Have a separate data class to act as an in-app model class, and at some point after you consume the API, map the initial data class to your new in-app model class with a mapper function.
Doing so not only allows you to have a neater set of properties in your model class, but you can also deal with nullability more flexibly.
On another note, also don’t keep your network and database data models the same either. Tailor the data models to their respective sources.
Disposables not being disposed of
I’m gonna talk about RxJava here but this can apply as well to Coroutines or any async tasky thing.
It’s easy to fall in the mistake of leaving a disposable somewhere in memory. Doing so can have consequences ranging from a slight memory overhead to crashes, the source of which may even be hard to debug.
// BAD val searchOnClick: (String) -> Unit = { searchTerm -> searchUsersDisposable = observeSearchUsers(searchTerm) } // GOOD val searchOnClick: (String) -> Unit = { searchTerm -> searchUsersDisposable?.dispose() searchUsersDisposable = observeSearchUsers(searchTerm) }
Thus, interviewers are often on the lookout for this mistake. Make sure that you dispose of your disposables properly. Be aware of how your activity’s lifecycle can affect them, and don’t replace any existing disposables before disposing of them first (above example).
Tests not compiling
I was surprised to hear this one, but it has apparently happened more than a few times. Don’t EVER submit your code where any part of it fails to compile. Double check, triple check, check it as many times as you need until you are 200% convinced that your code compiles all the way through, production code, tests, and androidtests.
Even then, that may not be enough. Android Studio does sometimes let errors sneak past its basic build process.
./gradlew lint test assembleDebug
So run the above command in your terminal on the root of your project and make sure that doesn’t fail with any errors. Bonus points if you clear your warnings too.
Conclusion
These mistakes were all based on my own experiences and what I heard about how others failed their tech challenges, but I hope you learned something from this and if you got a tech challenge you’re about to face, try to learn from the mistakes your other devs have made before you.
And know that even if you do pass your tech challenge, be ready to answer questions about all aspects of your code in the interview that follows.
I had a recent experience where I passed the tech challenge hearing that they were ‘very happy’ with what I gave them. The following interview was then a full hour of nothing but them roasting me and my code. Happy days.
Good luck in your tech challenges and interviews, and as always, happy coding ༼ つ ◕_◕ ༽つ