Skip to content
HackerRank Launches Two New Products: SkillUp and Engage Read now
Join us at the AI Skills & Tech Talent Summit in London! Register now
The 2024 Developer Skills Report is here! Read now
Stream HackerRank AI Day, featuring new innovations and industry thought leaders. Watch now
Mobile Development

7 Android Interview Questions Every Developer Should Know

Written By April Bohnert | August 17, 2023

Abstract, futuristic image generated by AI

In a world now dominated by smartphones and wearables, Android stands as a titan, powering billions of devices and shaping the mobile tech landscape. From budget phones to luxury devices, from smartwatches to TVs, Android’s versatility and adaptability have made it the OS of choice for countless manufacturers and developers. It’s no surprise, then, that Android development skills are in high demand

But with great demand comes some competition. To stand out, Android developers will need to be intimately familiar with the platform’s intricacies and challenges. And what better way to demonstrate that expertise than through a technical interview? This guide is here to help developers prepare for their  mobile development interviews, and to arm hiring teams with the tools they need to identify their next hire.

What is Android?

Dive into any bustling city, and you’ll likely find a common sight: people engaged with their devices. Many of these devices — be it smartphones, tablets, watches, or even car dashboards — run on Android. But to truly appreciate its prominence, we must delve deeper.

Android is an open-source operating system, primarily designed for mobile devices. Birthed by Android Inc. and later acquired by Google in 2005, it’s built on top of the Linux kernel. While originally centered around a Java interface for app development, Android’s horizon expanded with the introduction of Kotlin, a modern alternative that’s fast becoming a favorite among developers.

Over the span of its existence, Android has undergone numerous evolutions. From its early days with dessert-themed code names like Cupcake and Pie to its recent, more functionally named updates, the OS has consistently pushed the envelope in innovation, security, and performance. 

What an Android Interview Looks Like

An Android coding interview often mirrors the complexities and nuances of the platform itself. Candidates might be presented with challenges ranging from designing efficient UI layouts that adapt to multiple screen sizes to ensuring seamless data synchronization in the background, all while maintaining optimal battery performance.

One fundamental area often tested is a developer’s grasp of the Android lifecycle. Understanding how different components (like activities or services) come to life, interact, and, perhaps more importantly, cease to exist, can be the key to crafting efficient and bug-free apps. Additionally, topics such as intents, broadcast receivers, and content providers frequently find their way into these discussions, highlighting the interconnected nature of Android apps and the system they operate within.

But it’s not all about coding. System design questions can pop up, gauging a developer’s ability to architect an app that’s scalable, maintainable, and user-friendly. Debugging skills, a critical asset for any developer, can also be under the spotlight, with interviewees sometimes having to identify, explain, and solve a piece of buggy code.

So, whether you’re a seasoned developer gearing up for your next role or a recruiter aiming to refine your interview process, remember that an Android interview is more than a test — it’s an opportunity. An opportunity to showcase expertise, to identify potential, and to ensure that as Android continues to evolve, so do the professionals driving its innovation.

1. Implement a Custom ListAdapter

One of the foundational skills for any Android developer is understanding how to display lists of data efficiently. The `ListView` and its successor, the `RecyclerView`, are commonly used components for this purpose. A custom `ListAdapter` or `RecyclerView.Adapter` lets you control the look and functionality of each item in the list.

Task: Create a simple `RecyclerView.Adapter` that displays a list of user names and their ages. Each item should show the name and age side by side.

Input Format: You will be given an ArrayList of User objects. Each User object has two fields: a `String` representing the user’s name and an `int` representing their age.

Constraints:

  • The list will contain between 1 and 1000 users.
  • Each user’s name will be non-empty and will have at most 100 characters.
  • Age will be between 0 and 120.

Output Format: The adapter should bind the data such that each item in the `RecyclerView` displays a user’s name and age side by side.

Sample Input:

“`java

ArrayList<User> users = new ArrayList<>();

users.add(new User(“Alice”, 28));

users.add(new User(“Bob”, 22));

Sample Code:

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHolder> {

    private ArrayList<User> users;

    public UserAdapter(ArrayList<User> users) {

        this.users = users;

    }

 

    @NonNull

    @Override

    public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_item, parent, false);

        return new UserViewHolder(itemView);

    }

    @Override

    public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {

        User currentUser = users.get(position);

        holder.nameTextView.setText(currentUser.getName());

        holder.ageTextView.setText(String.valueOf(currentUser.getAge()));

    }

    @Override

    public int getItemCount() {

        return users.size();

    }

    static class UserViewHolder extends RecyclerView.ViewHolder {

        TextView nameTextView;

        TextView ageTextView;

        public UserViewHolder(@NonNull View itemView) {

            super(itemView);

            nameTextView = itemView.findViewById(R.id.nameTextView);

            ageTextView = itemView.findViewById(R.id.ageTextView);

        }

    }

}

 

Explanation:

The `UserAdapter` extends the `RecyclerView.Adapter` class, defining a custom ViewHolder, `UserViewHolder`. This ViewHolder binds to the `nameTextView` and `ageTextView` in the user item layout.

In the `onBindViewHolder` method, the adapter fetches the current User object based on the position and sets the name and age to their respective TextViews. The `getItemCount` method simply returns the size of the users list, determining how many items the `RecyclerView` will display.

2. Manage Activity Lifecycle with Configuration Changes

The Android Activity Lifecycle is fundamental to creating apps that behave correctly across different user actions and system events. One common challenge is ensuring that during configuration changes, such as screen rotations, the app doesn’t lose user data and effectively preserves its current state.

Task: Implement the necessary methods in an Activity to handle configuration changes (like screen rotation) and preserve a counter. The Activity has a button that, when pressed, increments a counter. The current value of the counter should be displayed in a TextView and should not reset upon screen rotation.

Constraints:

  • The counter can range from 0 to a maximum of 1,000.
  • Only the screen rotation configuration change needs to be handled.

Output Format: The TextView should display the current counter value, updating every time the button is pressed. This value should persist across configuration changes.

Sample Code:

“`java

public class CounterActivity extends AppCompatActivity {

 

    private static final String COUNTER_KEY = “counter_key”;

    private int counter = 0;

    private TextView counterTextView;

    private Button incrementButton;

 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_counter);

 

        counterTextView = findViewById(R.id.counterTextView);

        incrementButton = findViewById(R.id.incrementButton);

 

        if (savedInstanceState != null) {

            counter = savedInstanceState.getInt(COUNTER_KEY);

        }

 

        displayCounter();

 

        incrementButton.setOnClickListener(v -> {

            counter++;

            displayCounter();

        });

    }

 

    @Override

    protected void onSaveInstanceState(@NonNull Bundle outState) {

        super.onSaveInstanceState(outState);

        outState.putInt(COUNTER_KEY, counter);

    }

 

    private void displayCounter() {

        counterTextView.setText(String.valueOf(counter));

    }

}

 

Explanation:

This `CounterActivity` displays a counter that can be incremented with a button. The critical part is the `onSaveInstanceState` method, which is called before an Activity might be destroyed, like before a configuration change. In this method, we save the current counter value in the `Bundle` using the key `COUNTER_KEY`.

Then, in the `onCreate` method, which is called when the Activity is created or recreated (e.g., after a screen rotation), we check if there’s a saved instance state. If there is, it means the Activity is being recreated, and we restore the counter value from the `Bundle`. By doing this, we ensure that the counter value is preserved across configuration changes.

Explore verified tech roles & skills.

The definitive directory of tech roles, backed by machine learning and skills intelligence.

Explore all roles

3. Implement LiveData with ViewModel

The modern Android app architecture recommends using `ViewModel` and `LiveData` to build robust, maintainable, and testable apps. `LiveData` is an observable data holder class that respects the lifecycle of app components, ensuring that UI updates are made only when necessary and avoiding potential memory leaks.

Task: Create a `ViewModel` that holds a `LiveData` integer value representing a score. The ViewModel should have methods to increment and decrement the score. Implement an Activity that observes this `LiveData` and updates a TextView with the current score. The Activity should also have buttons to increase and decrease the score.

Input Format: Initial score starts at 0.

Constraints: The score can range between 0 and 100.

Output Format: The TextView in the Activity should display the current score, updating every time the increment or decrement buttons are pressed. This value should remain consistent across configuration changes.

Sample Code:

“`java

public class ScoreViewModel extends ViewModel {

    private MutableLiveData<Integer> score = new MutableLiveData<>(0);

    public LiveData<Integer> getScore() {

        return score;

    }

    public void incrementScore() {

        score.setValue(score.getValue() + 1);

    }

    public void decrementScore() {

        if (score.getValue() > 0) {

            score.setValue(score.getValue() – 1);

        }

    }

}

public class ScoreActivity extends AppCompatActivity {

    private ScoreViewModel viewModel;

    private TextView scoreTextView;

    private Button increaseButton, decreaseButton;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_score);

        viewModel = new ViewModelProvider(this).get(ScoreViewModel.class);

        scoreTextView = findViewById(R.id.scoreTextView);

        increaseButton = findViewById(R.id.increaseButton);

        decreaseButton = findViewById(R.id.decreaseButton);

        viewModel.getScore().observe(this, score -> scoreTextView.setText(String.valueOf(score)));

        increaseButton.setOnClickListener(v -> viewModel.incrementScore());

        decreaseButton.setOnClickListener(v -> viewModel.decrementScore());

    }

}

Explanation:

The `ScoreViewModel` class extends the `ViewModel` class and contains a `MutableLiveData` object representing the score. There are methods to get the score (which returns a non-modifiable `LiveData` object), increment the score, and decrement the score (ensuring it doesn’t go below 0).

The `ScoreActivity` sets up the UI and initializes the `ScoreViewModel`. It observes the `LiveData` score, so any changes to that score will automatically update the TextView displaying it. The buttons in the Activity invoke the increment and decrement methods on the `ViewModel`, altering the score.

The beauty of this architecture is the separation of concerns: the Activity manages UI and lifecycle events, while the ViewModel manages data and logic. The LiveData ensures that UI updates respect the lifecycle, avoiding issues like memory leaks or crashes due to updates on destroyed Activities.

4. Implement a Room Database Query

The Room persistence library provides an abstraction layer over SQLite, enabling more robust database access while harnessing the full power of SQLite. It simplifies many tasks but still requires a deep understanding of SQL when querying the database.

Task: Create a Room database that has a table named `Book` with fields `id`, `title`, and `author`. Implement a DAO (Data Access Object) method that fetches all books written by a specific author.

Input Format: The `Book` table will have a primary key `id` of type `int`, a `title` of type `String`, and an `author` of type `String`.

Constraints:

  • `id` is unique.
  • Both `title` and `author` fields have a maximum length of 100 characters.

Output Format: The DAO method should return a List of `Book` objects written by the specified author.

Sample Code:

“`java

@Entity(tableName = “book”)

public class Book {

    @PrimaryKey

    private int id;

    @ColumnInfo(name = “title”)

    private String title;

    @ColumnInfo(name = “author”)

    private String author;

    // Constructors, getters, setters…

}

@Dao

public interface BookDao {

    @Query(“SELECT * FROM book WHERE author = :authorName”)

    List<Book> getBooksByAuthor(String authorName);

}

@Database(entities = {Book.class}, version = 1)

public abstract class AppDatabase extends RoomDatabase {

    public abstract BookDao bookDao();

}

Explanation:

The `Book` class is annotated with `@Entity`, indicating that it’s a table in the Room database. The `id` field is marked as the primary key with `@PrimaryKey`. The other fields, `title` and `author`, are annotated with `@ColumnInfo` to specify their column names in the table.

The `BookDao` interface contains a method `getBooksByAuthor` which uses the `@Query` annotation to run an SQL query to fetch all books by a given author.

Finally, `AppDatabase` class is an abstract class that extends `RoomDatabase`, and it contains an abstract method to get an instance of the `BookDao`. This class is annotated with `@Database`, specifying the entities it comprises and the version of the database.

With this setup, any Android component can get an instance of `AppDatabase`, retrieve the `BookDao`, and use it to fetch books by a specific author from the underlying SQLite database.

5. Implement RecyclerView with DiffUtil

Using `RecyclerView` is a common task in Android development. It’s efficient, especially when displaying large lists or grids of data. To further enhance its efficiency, `DiffUtil` can be used to calculate differences between old and new lists, ensuring only actual changes get animated and rendered.

Task: Create a `RecyclerView` adapter that displays a list of strings. The adapter should use `DiffUtil` to efficiently handle updates to the list.

Input Format: The adapter will take in a list of strings.

Constraints: The list can contain up to 500 strings, with each string having a maximum length of 200 characters.

Output Format: A `RecyclerView` displaying the strings, efficiently updating its content whenever there’s a change in the input list.

Sample Code:

“`java

public class StringAdapter extends RecyclerView.Adapter<StringAdapter.ViewHolder> {

    private List<String> data;

    public StringAdapter(List<String> data) {

        this.data = data;

    }

    public void updateList(List<String> newData) {

        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new StringDiffCallback(data, newData));

        this.data.clear();

        this.data.addAll(newData);

        diffResult.dispatchUpdatesTo(this);

    }

    @NonNull

    @Override

    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_string, parent, false);

        return new ViewHolder(view);

    }

    @Override

    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {

        holder.textView.setText(data.get(position));

    }

    @Override

    public int getItemCount() {

        return data.size();

    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView textView;

        public ViewHolder(@NonNull View itemView) {

            super(itemView);

            textView = itemView.findViewById(R.id.textView);

        }

    }

    static class StringDiffCallback extends DiffUtil.Callback {

        private final List<String> oldList;

        private final List<String> newList;

        public StringDiffCallback(List<String> oldList, List<String> newList) {

            this.oldList = oldList;

            this.newList = newList;

        }

        @Override

        public int getOldListSize() {

            return oldList.size();

        }

        @Override

        public int getNewListSize() {

            return newList.size();

        }

        @Override

        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {

            return oldList.get(oldItemPosition).equals(newList.get(newItemPosition));

        }

        @Override

        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {

            String oldString = oldList.get(oldItemPosition);

            String newString = newList.get(newItemPosition);

            return oldString.equals(newString);

        }

    }

}

Explanation:

The `StringAdapter` class extends the `RecyclerView.Adapter` and displays a list of strings. Its `updateList` method allows efficient updates using the `DiffUtil` utility. When new data is provided, `DiffUtil` calculates the difference between the old and new lists. The results, containing information about which items were added, removed, or changed, are then applied to the RecyclerView to ensure efficient updates.

The `StringDiffCallback` class, which extends `DiffUtil.Callback`, is responsible for determining the differences between two lists. The `areItemsTheSame` method checks if items (based on their position) in the old and new lists are the same, while the `areContentsTheSame` method checks if the content of items at specific positions in the old and new lists is the same.

Together, this setup ensures the `RecyclerView` updates efficiently, animating only actual changes, and avoiding unnecessary redraws.

6. Dependency Injection with Hilt

Dependency injection (DI) is a software design pattern that manages object creation and allows objects to be decoupled. In Android, Hilt is a DI library that is built on top of Dagger and simplifies its usage, making it more Android-friendly. 

Task: Use Hilt to inject a repository class into an Android ViewModel. Assume the repository provides a method `getUsers()`, which fetches a list of user names.

Input Format: A ViewModel class requiring a repository to fetch a list of user names.

Constraints:

  • Use Hilt for Dependency Injection.
  • The repository fetches a list of strings (user names).

Output Format: A ViewModel with an injected repository, capable of fetching and holding a list of user names.

Sample Code:

“`java

// Define a repository

public class UserRepository {

    public List<String> getUsers() {

        // Assume this method fetches user names, either from a local database, API, or other data sources.

        return Arrays.asList(“Alice”, “Bob”, “Charlie”);

    }

}

// Define a ViewModel

@HiltViewModel

public class UserViewModel extends ViewModel {

    private final UserRepository userRepository;

    @Inject

    public UserViewModel(UserRepository userRepository) {

        this.userRepository = userRepository;

    }

    public List<String> fetchUserNames() {

        return userRepository.getUsers();

    }

}

// Setting up Hilt Modules

@Module

@InstallIn(SingletonComponent.class)

public class RepositoryModule {

    @Provides

    @Singleton

    public UserRepository provideUserRepository() {

        return new UserRepository();

    }

}

Explanation:

In the given code, we start by defining a basic `UserRepository` class that simulates fetching a list of user names. 

Next, we define a `UserViewModel` class. The `@HiltViewModel` annotation tells Hilt to create an instance of this ViewModel and provides the required dependencies. The `@Inject` annotation on the constructor indicates to Hilt how to provide instances of the `UserViewModel`, in this case by injecting a `UserRepository` instance.

Lastly, a Hilt module (`RepositoryModule`) is defined using the `@Module` annotation. This module tells Hilt how to provide instances of certain types. In our example, the `provideUserRepository` method provides instances of `UserRepository`. The `@InstallIn(SingletonComponent.class)` annotation indicates that provided instances should be treated as singletons, ensuring that only one instance of `UserRepository` exists across the whole application lifecycle.

By following this setup, developers can effortlessly ensure dependencies (like the `UserRepository`) are provided to other parts of the application (like the `UserViewModel`) without manually creating and managing them.

7. Custom View with Measure and Draw

Custom views are a fundamental part of Android, allowing developers to create unique UI elements tailored to specific needs. Creating a custom view often requires understanding of the measure and draw process to ensure the view adjusts correctly to different screen sizes and resolutions.

Task: Create a simple custom view called `CircleView` that displays a colored circle. The view should have a customizable radius and color through XML attributes.

Input Format: Custom XML attributes for the `CircleView`: `circleColor` and `circleRadius`.

Constraints:

  • Implement the `onMeasure` method to ensure the view adjusts correctly.
  • Override the `onDraw` method to draw the circle.

Output Format: A custom view displaying a circle with specified color and radius.

Sample Code:

In `res/values/attrs.xml`:

“`xml

<declare-styleable name=”CircleView”>

    <attr name=”circleColor” format=”color” />

    <attr name=”circleRadius” format=”dimension” />

</declare-styleable>

In `CircleView.java`:

“`java

public class CircleView extends View {

    private int circleColor;

    private float circleRadius;

    private Paint paint;

    public CircleView(Context context, AttributeSet attrs) {

        super(context, attrs);

        paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleView);

        circleColor = ta.getColor(R.styleable.CircleView_circleColor, Color.RED);

        circleRadius = ta.getDimension(R.styleable.CircleView_circleRadius, 50f);

        ta.recycle();

        paint.setColor(circleColor);

    }

    @Override

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int desiredWidth = (int) (2 * circleRadius + getPaddingLeft() + getPaddingRight());

        int desiredHeight = (int) (2 * circleRadius + getPaddingTop() + getPaddingBottom());

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width, height;

        if (widthMode == MeasureSpec.EXACTLY) {

            width = widthSize;

        } else if (widthMode == MeasureSpec.AT_MOST) {

            width = Math.min(desiredWidth, widthSize);

        } else {

            width = desiredWidth;

        }

        if (heightMode == MeasureSpec.EXACTLY) {

            height = heightSize;

        } else if (heightMode == MeasureSpec.AT_MOST) {

            height = Math.min(desiredHeight, heightSize);

        } else {

            height = desiredHeight;

        }

        setMeasuredDimension(width, height);

    }

    @Override

    protected void onDraw(Canvas canvas) {

        float cx = getWidth() / 2f;

        float cy = getHeight() / 2f;

        canvas.drawCircle(cx, cy, circleRadius, paint);

    }

}

Explanation:

The process of crafting a custom view in Android often involves a synergy between XML for configuration and Java/Kotlin for implementation. Let’s break down how the `CircleView` operates across these two realms:

XML Custom Attributes (`attrs.xml`):

  • Purpose: When creating a customizable view in Android, it’s imperative to define how it can be configured. Custom XML attributes allow the developer or designer to set specific properties directly in the layout XML files.
  • In Our Example: We defined two custom attributes in `attrs.xml`: `circleColor` and `circleRadius`. These dictate the color and size of the circle respectively when the view is used in an XML layout.

Java Implementation (`CircleView.java`):

    • Purpose: This is where the rubber meets the road. The Java (or Kotlin) code handles the logic, processing, and rendering of the custom view.
  • In Our Example: 
    • The constructor fetches the values of the custom attributes from the XML layout using `obtainStyledAttributes`. This means when you use the view in an XML layout and specify a color or radius, this is where it gets picked up and used.
    • The `onMeasure` method ensures the view adjusts its size according to the circle’s radius, also accounting for any padding.
    • The `onDraw` method takes care of the actual drawing of the circle, centered in the view, with the specified color and radius.

By mastering the interplay between XML attributes and Java/Kotlin logic, developers can craft custom UI elements that aren’t just visually appealing but also flexible and adaptive to various design specifications.

Resources to Improve AWS Knowledge

This article was written with the help of AI. Can you tell which parts?

Abstract, futuristic image generated by AI

What Is Kotlin? Inside the Android Programming Language