Android Interview Prep Details: Part 1
Published: 2024-02-24 22:27:00

Q: When creating an immutable class in Java, how do you handle mutable fields like objects?

A: It's advisable to return a deep copy or an immutable copy of the mutable field. This approach helps prevent users from modifying the original data, ensuring the immutability of the class.

---

Q: How can exceptions be handled in Java aside from using the try-catch block?

A: For unchecked exceptions, such as ArrayIndexOutOfBoundsException, where using a try-catch block might consume significant resources, an alternative approach is to employ an if-else check to preemptively prevent the exception. Additionally, for NullPointerException, consider utilizing the Optional class as a more structured way to handle potentially null values.

---

Q: When creating a thread in Java, which method do you prefer?

A: It depends on the scenario. If you are solely utilizing the run method without the need for additional customization of the thread's behavior, it's advisable to implement the Runnable interface. This approach adheres to the principle of favoring composition over inheritance in object-oriented programming (OOP). When a class extends the Thread class, it breaks the is-a relationship, as the new class is not truly a type of thread but rather a thread itself. By implementing Runnable, you can achieve the desired behavior while maintaining a cleaner and more flexible design, promoting code reuse through composition. If, however, you need to customize the thread behavior extensively, extending the Thread class might be a suitable choice.

---

Q: In industry-level code, how do we handle threads?

A: In the context of industry-level code, a widely adopted practice is to use the ExecutorService in Java for handling threads. ExecutorService provides a higher-level replacement for managing threads compared to manually creating and managing threads. It abstracts away the complexity of thread creation, pooling, and lifecycle management.

Moreover, ExecutorService offers several benefits such as thread pooling, efficient task scheduling, and easier resource management. By utilizing executor services, you can improve the scalability and performance of your application, ensuring better resource utilization.

---

Q: What are some common issues in multi-threaded programs?

A: One common issue in multi-threaded programs is data racing. Data racing occurs when two or more threads concurrently access shared data, and at least one of them modifies the data. This can lead to unpredictable behavior, as the order of execution becomes non-deterministic.

To mitigate data racing, synchronization mechanisms are often employed. Locks, such as the `synchronized` keyword in Java, are used to ensure that only one thread can access the shared data at a time. However, introducing locks raises another potential issue: deadlock.

Deadlock occurs when two or more threads are blocked forever, each waiting for the other to release a lock. This can halt the progress of the entire program. To prevent deadlocks, it's essential to follow best practices in lock acquisition order. If multiple locks are needed, ensure that all threads acquire the locks in the same order to avoid circular dependencies.

Additionally, using timeouts when acquiring locks is a recommended strategy. This involves specifying the maximum time a thread is willing to wait for a lock. If the lock is not acquired within the specified time, the thread can take appropriate action, such as releasing resources and retrying or notifying the user of the issue.

( data racing => lock => deadlock => how to avoid deadlock )

---

Q: For a sealed class in Kotlin, can you extend it in the same file? How about in the same package?

A: In Kotlin, a sealed class is a class that restricts the inheritance of its subclasses to a predefined set within the same file. Therefore, you can extend a sealed class within the same file but not outside of it.

When it comes to extending a sealed class in the same package but different files, Kotlin allows this as well. As long as the file is within the same package as the sealed class, you can create new subclasses of the sealed class in different files within that package.

This encapsulation of sealed classes to the same file provides a level of control over the hierarchy of subclasses, making it easier to manage and understand the complete set of subclasses that are meant to inherit from the sealed class.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjlv1rxba9xg17k2hz1g.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hrnuocw4ov1ysdhz3czv.png)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wn6nayfx2ut8wufsckqm.png)

---

Q: How can you overload the primary constructor in Kotlin?

A: You can achieve overloading in the primary constructor of a Kotlin class by providing default values to parameters. This allows you to create multiple constructor variations with different sets of parameters, making the class more flexible and accommodating various use cases.

{% embed https://dev.to/lydiayuan/understanding-parameter-order-in-kotlins-primary-constructors-12ch %}

---

Q: What's the advantage of coroutine in Kotlin?

A: Coroutines in Kotlin offer several advantages:

1. Scope Management:
   Coroutines provide a built-in way to manage the scope of asynchronous operations. This ensures that coroutines launched within a specific scope are cancelled when that scope is cancelled. This is particularly valuable for structured concurrency, making it easier to handle the lifecycle of concurrent operations.

2. Lightweight:
   Coroutines are more lightweight compared to traditional threads. They are well-suited for resource-intensive environments like Android, where creating and managing numerous threads can be costly. Coroutines use fewer system resources and are more scalable, making them a preferable choice for asynchronous programming.

3. Non-blocking:
   Coroutines allow non-blocking execution, meaning they don't block the main thread. This is crucial for maintaining a responsive user interface in applications, especially on platforms like Android. With coroutines, you can perform asynchronous tasks without freezing the UI, leading to a smoother and more responsive user experience.

4. Suspend Functions:
   Coroutines introduce the concept of suspend functions, which can be used within a coroutine to perform asynchronous tasks without blocking the thread. This simplifies asynchronous code, making it more readable and maintainable compared to traditional callback-based approaches.

---

Q: List three common permissions in the Android manifest.

A: Three common permissions in the Android manifest are:

1. android.permission.ACCESS_FINE_LOCATION: This permission is used to access precise location information, such as GPS coordinates.

2. android.permission.INTERNET: This permission allows the application to open network sockets and use the internet.

3. android.permission.POST_NOTIFICATIONS: This permission is related to notifications and allows the application to post notifications on the device.

---


Q: How do you securely store a token in Android?

A: To securely store a token in Android, it's recommended to utilize the Android Keystore system. The Android Keystore provides a secure and hardware-backed environment for key and token storage, offering protection against unauthorized access and extraction. 

---

Q: How do you handle navigation in Android?

A: Navigation in Android can be accomplished in various ways, depending on the context:

1. To a New Activity:
   - Start a new activity by using an intent. You can use the `Intent` class to navigate from one activity to another. This is a common approach for transitioning between different screens or functionalities within an Android application.

2. To a New Fragment (Jetpack Compose):
   - In Jetpack Compose, you can use a `NavHost` for navigation. The `NavHost` is a container for hosting navigation within Compose applications. It works in conjunction with the Navigation component to handle navigation between different composables. This allows for a declarative and efficient way to manage UI navigation in a Compose-based Android app.

3. To a New Fragment (XML):
   - In XML-based layouts, you can use a Navigation Graph (`navgraph`) to define the navigation flow between fragments. The Navigation component in Android provides a visual way to represent and manage the navigation paths within an application. This is particularly useful when dealing with complex navigation patterns.

---
Q: How do you avoid adding a new activity to the back stack in Android?

A: To prevent adding a new activity to the back stack in Android, you can use the `finish()` method. When you call `finish()` on an activity, it signals that the current activity should be closed, and it won't be added to the back stack.

---

Q: How do you override the back button in Android?

A: To override the back button in Android, you can use the OnBackPressedDispatcher provided by ComponentActivity, the base class for FragmentActivity and AppCompatActivity. The OnBackPressedDispatcher allows you to control the behavior of the back button by managing OnBackPressedCallback objects. 

```kotlin
class MyFragment : Fragment() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // This callback is only called when MyFragment is at least started
        val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
            // Handle the back button event
        }

        // The callback can be enabled or disabled here or in the lambda
    }
    ...
}
```

---

Q: What are some view groups in Android XML?

A: In Android XML, view groups are containers that hold and organize other UI elements. Some commonly used view groups include:

1. LinearLayout:
   - Arranges child views in a single row or column. You can specify the orientation as either horizontal or vertical.

2. RelativeLayout:
   - Positions child views relative to each other or the parent. It allows you to define the layout rules based on the position of sibling views.

3. FrameLayout:
   - Places child views on top of each other, with the last child added appearing at the top of the stack. It's often used for simple overlays.

4. ConstraintLayout:
   - A flexible layout that allows you to create complex UIs by setting constraints between views. It's particularly useful for responsive and dynamic designs.

5. ScrollView:
   - A special type of view group that allows its content to be scrolled vertically or horizontally. It's often used when the content is too large to fit on the screen.

...