Ace your live coding interview: Android

Karishma Agrawal
Level Up Coding
Published in
8 min readNov 7, 2023

--

I have written a series on android interview common questions. Which you can see from below:

Android Interview Question Part 1

Android Interview Question Part 2

Android Interview Question Part 3

Android Interview Question Part 4

In most companies when you apply for an Android position, it includes several interview rounds. Starting from DSA, then basic Android questions, then a live coding round where you have to implement an application in front of the interviewer showing your architecture skills and problem understanding, Then a System design interview(This depends on your experience level, it gets conducted only when you are applying for SDE 4 or senior positions).

I have also written a few system design articles, you can check them out from below. I’ll be writing more in the future.

In this article, we will talk about how you can prepare for live coding interviews, what are the challenges you can face, and some brownie points.

Let’s take a problem statement and solve it step by step:

Problem: Design an application using any architecture you have worked on in which there is a paginated list of Star War characters. Each item shows a name and profile image. Use Dependency injection and also write test cases. You can use any image-loading library you are comfortable with.

NOTE: You might be allowed to google things, but you can’t search for obvious things like what is pagination, or how to implement dependency injection in project. These are red flags for interviewer. Always search for things which people can’t remember like PagingLoadStateAdapter class basic block or something like retrofit initialization. and please don’t copy paste from browser , always just read few words from here and there and then write them yourself. I did the mistake or copy pasting and then refactoring the code. I was rejected.

The most important thing keep communicating with your interviewer about what you are doing, and why you are doing that, don’t get silent.

Ok so let’s dive into the solution. Decide your resources first:

Which architecture: MVVM (Always explain why you are choosing a particular architecture, and how it’s testable)

DI library: I’ll be using Hilt

Image Loading Library: Glide

Pagination: I’ll be using Pagination 3

Step 1: Define dependencies in Gradle

( Now at this point 2 things happen, either interviewer already provides a repo and asks you to take a pull and set it up, in this step confirm with them, What gradle version and studio version they are expecting. Otherwise, sometimes we can get stuck in a list of gradle issues which will consume your time. Otherwise, they will ask you to create your own. In that case, always keep an empty project with lib set)

dependencies {

implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

implementation "androidx.paging:paging-common-ktx:3.1.1"
implementation 'androidx.paging:paging-runtime-ktx:3.1.1'

implementation 'androidx.fragment:fragment-ktx:1.6.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1"
implementation 'androidx.databinding:databinding-runtime:8.0.2'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'

implementation("com.google.dagger:hilt-android:2.44")
kapt("com.google.dagger:hilt-android-compiler:2.44")

implementation 'com.github.bumptech.glide:glide:4.15.1'

implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.0"
implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'

testImplementation 'androidx.arch.core:core-testing:2.2.0@aar'
testImplementation 'org.mockito:mockito-core:3.12.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.arch.core:core-testing:2.2.0'

}

Step 2: Setup your networking layer and domain

Create your RetrofitClientInstance. Check HERE

Create a Network module, so instead of accessing the retrofit instance directly, we do it through DI.

@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {

@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)

@Provides
@Singleton
fun provideOkHttpClient(
@ApplicationContext context: Context, httpLoggingInterceptor: HttpLoggingInterceptor
): OkHttpClient = OkHttpClient.Builder().apply {
connectTimeout(2, TimeUnit.MINUTES).readTimeout(2, TimeUnit.MINUTES)
.writeTimeout(2, TimeUnit.MINUTES).retryOnConnectionFailure(true)
.addInterceptor(httpLoggingInterceptor)
}.build()

@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder().apply {
baseUrl(BASE_URL)
addConverterFactory(GsonConverterFactory.create())
client(okHttpClient)
}.build()
}

Create your apiService class where you are writing api queries. Check here for reference.

Create a service class in NetworkModule

   @Provides
@Singleton
fun provideStartWarApi(retrofit: Retrofit): StartWarApiService =
retrofit.create(StartWarApiService::class.java)

This way you can inject StartWarApiService in repo.

Create Repo and RepoImpl class. I am not gonna write the whole code. Check out the MVVM architecture guide for it.

But basically, the flow is like

ApiService -> Repo -> RepoImpl -> ViewModel -> UI

You can check the implementation from HERE

Step 3: ViewModel

I like to use abstraction to separate the concerns and have one base class doing all the common work, And then child classes are just doing what they are expected to do.

For that reason, I am creating a baseViewModel class. In BaseViewModel we will maintain loading, success, and error state. Just creating a basic building block in which we will be triggering the event on the basis of API response.

abstract class BaseViewModel : ViewModel() {
var progressLiveEvent = SingleLiveEvent<Boolean>()
var errorMessage = SingleLiveEvent<String>()

inline fun <T> launchAsync(
crossinline execute: suspend () -> Response<T>,
crossinline onSuccess: (T) -> Unit,
showProgress: Boolean = true
) {
viewModelScope.launch {
if (showProgress)
progressLiveEvent.value = true
try {
val result = execute()
if (result.isSuccessful)
onSuccess(result.body()!!)
else
errorMessage.value = result.message()
} catch (ex: Exception) {
errorMessage.value = ex.message
} finally {
progressLiveEvent.value = false
}
}
}

inline fun <T> launchPagingAsync(
crossinline execute: suspend () -> Flow<T>,
crossinline onSuccess: (Flow<T>) -> Unit
) {
viewModelScope.launch {
try {
val result = execute()
onSuccess(result)
} catch (ex: Exception) {
errorMessage.value = ex.message
}
}
}
}

Now extend this in View Model and perform your action in ViewModel, which is getting all-star war characters for now.

In Hilt, we annotate viewModel with @HiltViewModel

@HiltViewModel
class StarWarsViewModel @Inject constructor(private val repo: StarWarRepo) : BaseViewModel() {

init {
getAllCharacters()
}

private lateinit var _starWarFlow: Flow<PagingData<StarWarsPeopleData>>
val starWarFlow: Flow<PagingData<StarWarsPeopleData>>
get() = _starWarFlow

private fun getAllCharacters() = launchPagingAsync({
repo.getStarWarsCharacter().cachedIn(viewModelScope)
}, {
_starWarFlow = it
})
}

Now instead of passing the repo in the constructor from view. we are injecting it using DI. And why we are passing in the constructor so we can make them testable, so now when you are writing test on ViewModel you can pass a fake repo with testable data.

Here I am using flow, but the interviewer can ask you why flow and not just liveData. Remember why we use liveData and how flows are better in what cases. Here we can achieve it using live data as well. Because flows are mainly used when data is getting updated using sync services or in the database from somewhere, but in this case, we are calling API each time and receiving data changes so flow might not be needed, But at the same place, if you were getting data from the database, the flow will observe all the changes happen in the database without you triggering them.

Step 4: Pagination

You can understand all about the paging 3 library from here.

This will explain basic of pagination benefit and how to implement it.

I’ll also be writing more about pagination from a system design perspective, Where I’ll be explaining why pagination is used in api’s and what types of paginations are available as well as when to use which.

There are 2 main steps to work with pagination one is creating PagingDataSource , and 2nd is PagingAdapter.

  1. Paging data source
class StarWarDataSource @Inject constructor(private val service: StartWarApiService) :
PagingSource<Int, StarWarsPeopleData>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, StarWarsPeopleData> {
val pageNumber = params.key ?: 1
return try {
val response = service.getStarWarCharacterResponse(pageNumber)
val pagedResponse = response.body()
val data = pagedResponse?.starWarsPeopleData

var nextPageNumber: Int? = null
if (pagedResponse?.next != null) {
val uri = Uri.parse(pagedResponse.next)
val nextPageQuery = uri.getQueryParameter("page")
nextPageNumber = nextPageQuery?.toInt()
}

LoadResult.Page(
data = data.orEmpty(), prevKey = null, nextKey = nextPageNumber
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, StarWarsPeopleData>): Int = 1
}

2. PagingAdapter

We will extend PagingDataAdapter and will pass Data class and ViewHolder types. getItem(position) will give you StarWarData for single item, You just need to bind that data to ui.

class StarWarPeopleAdapter @Inject constructor(@ActivityContext private val context: Context) :
PagingDataAdapter<StarWarsPeopleData, StarWarPeopleAdapter.MyViewHolder>(CharacterComparator) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// Inflate the item layout and create a new ViewHolder
val itemView: View =
LayoutInflater.from(parent.context).inflate(R.layout.item_start_war, parent, false)
return MyViewHolder(itemView, context)
}

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
getItem(position)?.let {
holder.bind(it)
}
}

class MyViewHolder(itemView: View, val context: Context) : RecyclerView.ViewHolder(itemView) {
private val name: TextView
private val userImage: ImageView

init {
name = itemView.findViewById(R.id.txt_character_name)
userImage = itemView.findViewById(R.id.iv_user)
}

fun bind(item: StarWarsPeopleData?) {
// Bind the item to the view
name.text = item?.name
val url: Int =
(item?.url?.removePrefix("https://swapi.dev/api/people/")?.removeSuffix("/")
?: "1").toInt()
Glide.with(context).load("https://i.pravatar.cc/150?img=${url}").circleCrop()
.placeholder(R.drawable.baseline_supervised_user_circle_24).into(userImage)

}
}
}

Now there is one more thing that you can do, when we scroll up and down we show a loading bar representing more data is getting loaded for that there is something called withLoadStateAdapters where you can pass header and footer. The header can be your error state if a network error comes and the footer can be progress bar represents loading state.

Header and footer adapter will be created by extending LoadStateAdapter<MyViewHolder> . Everything else will be the same.

Step 5: Define the adapter in Activity and observe data changes and notify them to adapter.

This is how we can set the header and footer adapter.

starWarPeopleAdapter = StarWarPeopleAdapter(this)
binding.rvStarWarPeople.adapter = starWarPeopleAdapter.withLoadStateAdapters(
header = PagingHeaderLoadStateAdapter(starWarPeopleAdapter),
footer = PagingLoadStateAdapter(starWarPeopleAdapter)
)

This is how we can submit observed changes in the adapter.

   viewModel.starWarFlow.collectLatest {
starWarPeopleAdapter.submitData(it)
}

Voila! You will see a running app. Great work. Always remember whatever you do, you should have an explanation for why are you doing it?

Step 6: Tests

I promised to give you bonus points here. So let’s write some test cases.

We will use the test folder to write unit test cases.

First create a FakeRepo which will implement StarWarRepo. And override all the methods. One method that we have is getStarWarsCharacter , Create fake data using Json or string format, parse it into StarWarsPeopleData and return it asFlow().

Create a viewModel instance in Test class by passing this fake repo and then compare your fake data with data which you observe from viewModel. Easy Enough.

Check out this to understand how this all works.

Conclusion

Being aware of the latest techniques and architecture will help you a lot in this interview round. Read about it, understand it, question it why it is better, and what else can be better than this. Once you start questioning yourself, you will get all the answers.

Talk about modularization and Dependency injection. These can help you scale the system. Always talk about what is user strength, how it can be more scalable, what the will be fallback mechanism, and how will you handle crashes without making a new release. What logging system you will use and why?

Let me know if you have any doubt by writing me back at karishmaagr04@gmail.com

Hope this article was helpful for you. Your claps are really appreciated to help others find this article 😃 .

--

--

Android Developer @Eventbrite | Wanted to be a writer so I write code now | Reader