Amol Pawar

custom exceptions

Custom Exceptions vs. Standard Exceptions in Java: When to Extend and When Not To

Java provides a robust exception handling mechanism that helps developers write reliable and maintainable code. While Java’s standard exceptions cover many common error scenarios, sometimes you need something more specific to your application’s needs. This is where custom exceptions in Java come into play. But when should you create a custom exception, and when is it unnecessary? Let’s explore this in depth.

What Are Standard Exceptions in Java?

Java has a rich hierarchy of built-in exceptions that developers can use to handle different errors. These standard exceptions fall into two main categories:

1. Checked Exceptions — Must be handled using try-catch or declared using throws.

  • Example: IOException, SQLException

2. Unchecked Exceptions (Runtime Exceptions) — Do not require explicit handling.

  • Example: NullPointerException, IndexOutOfBoundsException

Using standard exceptions is often the best choice because they are well-documented and understood by developers. However, they might not always convey specific application-related issues effectively.

When to Use Custom Exceptions in Java

Custom exceptions are useful when you need to represent domain-specific errors that are not covered by standard exceptions. Here are some scenarios where custom exceptions make sense:

1. When Standard Exceptions Are Too Generic

Standard exceptions may not always provide enough clarity. For instance, if your application processes payments, throwing a generic Exception or IllegalArgumentException isn’t informative. A PaymentProcessingException makes the error clearer.

2. When You Need to Add Extra Information

A custom exception allows you to include additional details about an error, such as error codes, messages, or even metadata.

3. When You Want to Enforce Business Rules

Custom exceptions help enforce specific business logic. For example, if a user tries to withdraw more money than available, you might throw an InsufficientFundsException instead of a generic RuntimeException.

4. When You Need to Handle Exceptions Differently

If your application has a centralized error-handling mechanism, custom exceptions can be helpful in distinguishing different types of errors.

How to Create a Custom Exception in Java

Creating a custom exception in Java is simple. You can extend either Exception (for checked exceptions) or RuntimeException (for unchecked exceptions).

Creating a Checked Custom Exception

Java
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}

public class CustomExceptionExample {
    public static void validateAge(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException("Age must be 18 or above.");
        }
    }
    public static void main(String[] args) {
        try {
            validateAge(16);
        } catch (InvalidAgeException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
    }
}

Here,

  • InvalidAgeException extends Exception, making it a checked exception.
  • The constructor passes a custom message to the superclass (Exception).
  • The validateAge method throws InvalidAgeException if age is below 18.
  • The exception is caught in main and handled gracefully.

Creating an Unchecked Custom Exception

Java
class DatabaseConnectionException extends RuntimeException {
    public DatabaseConnectionException(String message) {
        super(message);
    }
}

public class UncheckedCustomExceptionExample {
    public static void connectToDatabase(boolean connectionStatus) {
        if (!connectionStatus) {
            throw new DatabaseConnectionException("Failed to connect to the database.");
        }
    }
    public static void main(String[] args) {
        connectToDatabase(false);
    }
}

Here,

  • DatabaseConnectionException extends RuntimeException, making it unchecked.
  • No need to declare it using throws since unchecked exceptions don’t require explicit handling.
  • If connectToDatabase(false) is called, an exception is thrown.

When NOT to Use Custom Exceptions

While custom exceptions in Java are useful, overusing them can lead to unnecessary complexity. Here are cases where they may not be needed:

1. When a Standard Exception Suffices

If a standard exception like IllegalArgumentException or NullPointerException properly conveys the issue, using a custom exception is redundant.

Java
public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age cannot be negative.");
    }
}

There’s no need for a NegativeAgeException when IllegalArgumentException works perfectly.

2. When They Add Unnecessary Complexity

If an exception doesn’t add meaningful information or handling logic, it might not be worth creating.

3. When Logging and Debugging Are Not Improved

If a custom exception doesn’t make debugging easier or doesn’t offer additional insights, it may not be necessary.

Best Practices for Custom Exceptions

  1. Keep Custom Exceptions Specific — Avoid generic names like MyAppException; use names that reflect the issue, such as UserNotFoundException.
  2. Extend the Right Class — Use Exception for checked exceptions and RuntimeException for unchecked exceptions.
  3. Include Helpful Messages — Provide meaningful messages to help with debugging.
  4. Document Your Exceptions — Ensure other developers understand when and why to use them.
  5. Avoid Creating Too Many Exceptions — Use them only when they add real value.

Conclusion

Custom exceptions in Java are powerful when used appropriately. They provide clarity, enforce business logic, and enhance maintainability. However, standard exceptions should be preferred when they adequately describe an error. The key is to strike the right balance — use custom exceptions only when they genuinely improve code readability, debugging, and error handling.

Checked Exceptions

Checked Exceptions in Java: What They Are and How They Work

When writing Java programs, handling errors is an essential part of creating robust and reliable applications. One important concept in Java’s error-handling mechanism is checked exceptions. If you’re new to Java or need a refresher, this guide will walk you through what checked exceptions are, how they work, and how to handle them effectively.

What Are Checked Exceptions in Java?

Checked exceptions in Java are a category of exceptions that must be either caught or declared in the method signature using the throws keyword. They are part of Java’s mechanism to enforce error handling at compile-time, ensuring that developers acknowledge and manage potential problems before running the program.

Unlike unchecked exceptions, which arise due to programming errors (such as NullPointerException or ArrayIndexOutOfBoundsException), checked exceptions typically indicate recoverable conditions like missing files, failed network connections, or invalid user input.

Let’s understand this with a simple example.

Imagine you are booking a flight online. There are two possible situations:

  1. You enter correct details, and the ticket is booked successfully.
  2. You enter an incorrect credit card number, and the system stops the booking process, showing an error.

In Java terms:

  • Booking the flight successfully is like a normal method execution.
  • Entering an invalid card number is like a checked exception, because the system knows this issue could happen and forces you to handle it (e.g., by showing an error message).

How Does Java Enforce Checked Exceptions?

When writing Java code, some operations have a high chance of failing, like:

  • Reading a file (the file may not exist) → IOException
  • Connecting to a database (the database might be down) → SQLException
  • Waiting for a thread to completeInterruptedException

Since these errors are expected, Java forces you to either:

  • Handle them using try-catch
  • Declare them using throws in the method signature

Let’s say we want to read a file. There’s a chance the file doesn’t exist, so Java forces us to handle this situation.

Without Handling (Compilation Error)

Java
import java.io.FileReader;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        FileReader file = new FileReader("path\data.txt"); // Compilation Error!
    }
}

Error: Unhandled exception: java.io.FileNotFoundException
 Java stops compilation because we didn’t handle the exception. Also the compiler suggests two options: the first one is to surround the code with a try-catch block, and the second is to declare the exception using the throws keyword in the method signature.

Handling with try-catch

We handle the error inside the method using try-catch:

Java
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionExample {
    public static void main(String[] args) {
        try {
            FileReader file = new FileReader("path\data.txt"); 
            System.out.println("File opened successfully.");
        } catch (IOException e) {
            System.out.println("Error: File not found.");
        }
    }
}

Output if file exists: File opened successfully.
Output if file is missing: Error: File not found.

Handling with throws (Delegating the Exception)

Instead of handling it inside the method, we can let the caller handle it by declaring throws in the method signature.

Java
import java.io.FileReader;
import java.io.IOException;

public class CheckedExceptionExample {
    public static void main(String[] args) throws IOException {
        FileReader file = new FileReader("path\data.txt"); 
        System.out.println("File opened successfully.");
    }
}

This approach is useful for propagating exceptions to higher-level methods where they can be handled appropriately.

Why Are Checked Exceptions Important?

Checked exceptions serve an important role in Java by enforcing better error handling. Here’s why they matter:

  1. Compile-Time Safety: They prevent runtime failures by ensuring errors are anticipated and handled during development.
  2. Encourages Robust Code: Developers are forced to think about possible failure scenarios and how to deal with them.
  3. Improves Code Maintainability: Explicit exception declarations make it clear which methods can fail, improving readability and maintainability.

Conclusion

Checked exceptions in Java play a crucial role in enforcing proper error handling at compile-time. By understanding how they work and following best practices, you can write cleaner, more reliable Java code. Whether you use try-catch blocks or declare exceptions with throws, handling checked exceptions properly ensures your applications run smoothly and recover gracefully from potential issues.

By integrating these techniques into your Java development workflow, you’ll be better prepared to handle unexpected situations, making your applications more robust and user-friendly.

Unchecked Exceptions in Java

Unchecked Exceptions in Java: What They Are

Java is a powerful programming language that provides robust error handling mechanisms through exceptions. Exceptions in Java are classified into checked exceptions and unchecked exceptions. In this blog post, we’ll dive deep into unchecked exceptions in java, focusing on RuntimeException, and explore how they work, when to use them, and best practices.

What Are Unchecked Exceptions in Java?

Unchecked exceptions in Java are exceptions that occur during the execution of a program and do not need to be explicitly declared or handled. They are subclasses of RuntimeException, which itself extends Exception. Unlike checked exceptions, the compiler does not force you to handle unchecked exceptions, giving developers more flexibility.

Imagine you are driving a car:

  • If you run out of fuel before starting, you already know you’ll need to refill (like a checked exception, where Java warns you in advance).
  • If you suddenly get a flat tire while driving, it’s unexpected (like an unchecked exception, because Java doesn’t force you to check for it).

Unchecked exceptions usually happen due to coding mistakes like dividing by zero, accessing an invalid index, or dereferencing null.

Key Characteristics of Unchecked Exceptions:

  • They occur at runtime.
  • They are not required to be handled using try-catch or declared with throws
  • They indicate programming errors, such as logical flaws or improper API usage.
  • Examples include NullPointerException, ArrayIndexOutOfBoundsException, and IllegalArgumentException.

Common Causes of Unchecked Exceptions

Unchecked exceptions often arise from:

  1. Null references — Trying to access methods or fields of a null object leads to a NullPointerException.
  2. Invalid array access — Accessing an index beyond the array’s length results in ArrayIndexOutOfBoundsException.
  3. Illegal operations — Dividing by zero throws an ArithmeticException.
  4. Invalid casting — Trying to cast an object to an incompatible type leads to ClassCastException.
  5. Improper argument usage — Passing an invalid argument to a method can trigger IllegalArgumentException.

How to Handle Unchecked Exceptions in Java?

Although unchecked exceptions don’t require explicit handling, it is good practice to write defensive code to avoid them. Here are some best practices:

1. Use Null Checks

Before using an object, always ensure it is not null to avoid NullPointerException.

2. Validate Input Arguments

Check method parameters before processing them.

3. Use Try-Catch Blocks Sparingly

Try-catch blocks should not be overused for unchecked exceptions but can be useful in specific cases.

Difference Between Checked and Unchecked Exceptions

Understanding the distinction between checked and unchecked exceptions is crucial for writing efficient Java code.

FeatureChecked ExceptionsUnchecked Exceptions
InheritanceExtends Exception (except RuntimeException)Extends RuntimeException
Compile-time CheckingChecked by the compilerNot checked by the compiler
Handling RequirementMust be handled or declaredNo mandatory handling
Use CaseRepresent recoverable conditions (e.g., IOException)Indicate programming errors (e.g., NullPointerException)

Should You Catch Unchecked Exceptions in Java?

Generally, it’s best to avoid catching unchecked exceptions unless you’re implementing a global exception handler. Instead, focus on writing clean, error-free code by using input validation and proper null checks. However, in web applications or frameworks, handling unchecked exceptions globally can enhance user experience by providing clear error messages rather than allowing the application to crash.

Conclusion

Unchecked exceptions in Java, particularly those derived from RuntimeException, provide flexibility but also require responsible usage. They indicate programming mistakes that should be fixed rather than caught. By following best practices like validating inputs, using meaningful messages, and logging exceptions properly, developers can write robust and maintainable Java applications.

Mastering MVVM Architecture

Mastering MVVM Architecture in Android: A Complete Guide

Modern Android development demands scalable, maintainable, and testable architectures, and MVVM (Model-View-ViewModel) has emerged as the gold standard. It helps in structuring code in a way that ensures a clean separation of concerns, making it easier to manage UI, business logic, and data operations.

In this guide, we’ll take an in-depth look at MVVM, its benefits, how to implement it using Jetpack Compose, and advanced concepts like dependency injection, UI state handling, and testing. Let’s dive in!

What is MVVM?

MVVM (Model-View-ViewModel) is an architectural pattern that separates the presentation layer (UI) from the business logic and data handling. This separation enhances modularity, making the app easier to maintain and test.

MVVM Components

  1. Model: Represents the data layer (API, database, repositories) and business logic.
  2. View: The UI layer (Activity, Fragment, or Composable functions in Jetpack Compose).
  3. ViewModel: Acts as a bridge between View and Model, holding UI-related data and surviving configuration changes.

How MVVM Works

  1. The View observes data from the ViewModel.
  2. The ViewModel fetches data from the Model.
  3. The Model retrieves data from an API, database, or local cache.
  4. The ViewModel exposes the data, and the View updates accordingly.

Why Use MVVM?

  • Separation of Concerns — Keeps UI and business logic separate.
  • Better Testability — ViewModel can be unit tested without UI dependencies.
  • Lifecycle Awareness — ViewModel survives configuration changes.
  • Scalability — Works well with large-scale applications.
  • Compatibility with Jetpack Compose — Supports modern UI development in Android.

Implementing MVVM in Android with Jetpack Compose

Let’s implement a simple MVVM architecture using Jetpack Compose.

Step 1: Model (Repository Layer)

Kotlin
class UserRepository {
    fun getUsers(): List<String> {
        return listOf("Amol", "Akshay", "Swapnil")
    }
}

Step 2: ViewModel

Kotlin
class UserViewModel : ViewModel() {
    private val repository = UserRepository()
    private val _users = MutableStateFlow<List<String>>(emptyList())
    val users: StateFlow<List<String>> = _users

    init {
        fetchUsers()
    }

    private fun fetchUsers() {
        _users.value = repository.getUsers()
    }
}

Step 3: View (Jetpack Compose UI)

Kotlin
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val users by viewModel.users.collectAsState()
    LazyColumn {
        items(users) { user ->
            Text(text = user, fontSize = 20.sp, modifier = Modifier.padding(16.dp))
        }
    }
}

This basic example sets up MVVM with a repository, ViewModel, and a UI that observes data changes.

Dependency Injection in MVVM (Using Hilt)

To make the architecture scalable, we use Hilt for dependency injection.

Step 1: Add Dependencies

Kotlin
dependencies {
    implementation "androidx.hilt:hilt-navigation-compose:1.1.0"
    implementation "com.google.dagger:hilt-android:2.44"
    kapt "com.google.dagger:hilt-compiler:2.44"
}

Step 2: Enable Hilt in the Application Class

Kotlin
@HiltAndroidApp
class MyApp : Application()

Step 3: Inject Repository into ViewModel

Kotlin
@HiltViewModel
class UserViewModel @Inject constructor(private val repository: UserRepository) : ViewModel() {
    private val _users = MutableStateFlow<List<String>>(emptyList())
    val users: StateFlow<List<String>> = _users

    init {
        fetchUsers()
    }

    private fun fetchUsers() {
        _users.value = repository.getUsers()
    }
}

Step 4: Inject ViewModel into Composable

Kotlin
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
    val users by viewModel.users.collectAsState()
    
    LazyColumn {
        items(users) { user ->
            Text(text = user, fontSize = 20.sp, modifier = Modifier.padding(16.dp))
        }
    }
}

LiveData vs StateFlow: Which One to Use?

Best Practice: Use StateFlow with Jetpack Compose because it integrates better with collectAsState().

Handling UI State in MVVM

To manage loading, success, and error states:

Kotlin
sealed class UIState<out T> {
    object Loading : UIState<Nothing>()
    data class Success<T>(val data: T) : UIState<T>()
    data class Error(val message: String) : UIState<Nothing>()
}

Modify ViewModel:

Kotlin
class UserViewModel : ViewModel() {
    private val _users = MutableStateFlow<UIState<List<String>>>(UIState.Loading)
    val users: StateFlow<UIState<List<String>>> = _users

    fun fetchUsers() {
        viewModelScope.launch {
            try {
                val data = repository.getUsers()
                _users.value = UIState.Success(data)
            } catch (e: Exception) {
                _users.value = UIState.Error("Failed to load users")
            }
        }
    }
}

In UI:

Kotlin
when (state) {
    is UIState.Loading -> CircularProgressIndicator()
    is UIState.Success -> LazyColumn { items(state.data) { user -> Text(user) } }
    is UIState.Error -> Text(state.message, color = Color.Red)
}

Unit Testing MVVM Components

Unit testing is important in MVVM to ensure reliability.

Test ViewModel

Add testing dependencies:

Kotlin
testImplementation "junit:junit:4.13.2"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
testImplementation "io.mockk:mockk:1.13.3"

Create a UserViewModelTest file:

Kotlin
@ExperimentalCoroutinesApi
class UserViewModelTest {

    private lateinit var viewModel: UserViewModel
    private val repository = mockk<UserRepository>()

    @Before
    fun setUp() {
        every { repository.getUsers() } returns listOf("Amol", "Akshay", "Swapnil")

        viewModel = UserViewModel(repository)
    }

    @Test
    fun `fetchUsers updates users state correctly`() {
       // We can also call the body of setUp() here, which is useful for individual test functions that need more customization.
        assert(viewModel.users.value is UIState.Success)
    }
}

This tests that fetchUsers() properly updates the UI state.

Conclusion

MVVM architecture enhances modularity, testability, and scalability in Android development. By using Jetpack Compose, Hilt for DI, StateFlow for state management, and UI state handling, we can build robust and maintainable applications.

The Ultimate Guide to Android Basics

The Ultimate Guide to Android Basics: Architecture, Components, Development, and More

Android is the world’s most popular mobile operating system, powering billions of devices worldwide. Whether you’re an aspiring developer or just curious about how Android works, understanding the fundamentals is crucial. This in-depth guide covers everything from Android’s architecture to app development essentials and best practices. Let’s dive in!

What is Android?

Android is an open-source operating system developed by Google, based on the Linux kernel. It provides a flexible ecosystem for developers to build mobile applications and supports a wide range of devices, including smartphones, tablets, smart TVs, and even wearables.

Android Architecture

Android’s architecture consists of multiple layers, each playing a critical role in its functionality. Here’s a breakdown:

1. Linux Kernel

At the core of Android is the Linux kernel, which manages low-level operations like memory management, process scheduling, security, and hardware communication.

2. Hardware Abstraction Layer (HAL)

HAL provides standard interfaces that allow the Android OS to communicate with different hardware components like cameras, sensors, and Bluetooth.

3. Native Libraries

These libraries include essential components like OpenGL (for graphics rendering), SQLite (database storage), and WebKit (browser engine).

4. Android Runtime (ART)

Android Runtime (ART) is responsible for executing applications. It uses Just-In-Time (JIT) and Ahead-Of-Time (AOT) compilation to optimize app performance.

5. Application Framework

This layer provides APIs and services for developers to build and manage applications, including:

  • Activity Manager: Controls app lifecycle and navigation.
  • Content Providers: Manages shared data between apps.
  • Resource Manager: Handles UI elements like layouts and strings.

6. Applications

At the top of the stack, we have the user-facing applications, including built-in Google apps (Phone, Messages, Maps) and third-party apps from the Play Store.

Core Android Components

Android applications are built using four main components:

1. Activities

An activity represents a single screen in an app. It contains the UI elements that users interact with. Activities follow a lifecycle, managed through methods like onCreate(), onResume(), and onDestroy().

2. Services

Services run in the background without a user interface. They are used for tasks like playing music or fetching data.

3. Broadcast Receivers

These listen for system-wide broadcast messages like battery low alerts or network connectivity changes.

4. Content Providers

Content providers manage shared data and allow different apps to access it securely, such as the Contacts or MediaStore databases.

Getting Started with Android Development

To start building Android applications, you need the right tools and languages.

Programming Languages

  • Kotlin: The preferred language for Android development, offering concise and expressive syntax.
  • Java: The traditional language, still widely used and supported.

Development Tools

  • Android Studio: The official IDE for Android development.
  • Android SDK (Software Development Kit): Provides the tools and libraries needed to build Android apps.
  • Gradle: Manages project dependencies and build automation.

AndroidManifest.xml

This file declares essential app information like activities, permissions, and services.

Building User Interfaces in Android

Android provides various UI components to design engaging applications.

Layouts

  • LinearLayout: Arranges elements in a single row or column.
  • ConstraintLayout: A flexible layout with constraints for responsive design.
  • RelativeLayout: Allows positioning elements relative to each other.

Common UI Elements

  • TextView: Displays text.
  • EditText: Accepts user input.
  • Button: Triggers actions when clicked.
  • RecyclerView: Efficiently displays large lists or grids.

Fragments

Fragments are modular UI components that allow flexible designs, especially for tablets and large-screen devices.

Understanding Android Lifecycle

Activities and fragments follow a structured lifecycle to manage user interactions efficiently. Key methods include:

  • onCreate(): Called when the activity is first created.
  • onStart(): When the activity becomes visible.
  • onResume(): When the user interacts with the activity.
  • onPause(): When the activity goes into the background.
  • onStop(): When the activity is no longer visible.
  • onDestroy(): When the activity is destroyed.

Data Storage in Android

Android provides multiple storage options based on application needs:

1. Shared Preferences

Used to store small key-value pairs, ideal for settings and preferences.

2. SQLite Database

A lightweight, local database for structured data storage.

3. Room Database

An abstraction layer over SQLite, making database management easier with an ORM approach.

4. Cloud & Firebase Storage

For cloud-based data storage and real-time updates.

Networking in Android

Most apps require network communication. Popular libraries include:

  • Retrofit: A type-safe HTTP client for interacting with APIs.
  • Volley: A fast networking library for handling multiple requests.
  • OkHttp: A low-level HTTP client for efficient network calls.

Security and Permissions

Android enforces strict security measures to protect user data.

Runtime Permissions

Apps must request permissions at runtime for sensitive actions like accessing the camera, location, or contacts.

Encryption

Ensures data security during storage and transmission.

ProGuard & R8

Used to minify and obfuscate code, making reverse engineering difficult.

Publishing Your Android App

Once your app is ready, follow these steps to publish it:

1. Google Play Console

Register as a developer and upload your app.

2. App Signing

Securely sign your app to ensure authenticity.

3. App Monetization

Options include ads (Google AdMob), in-app purchases, and subscriptions.

Conclusion

Android development is an exciting and ever-evolving field. By understanding its architecture, components, and best practices, you can create powerful applications that provide excellent user experiences. Whether you’re a beginner or an experienced developer, mastering these fundamentals will set you on the path to success in Android development.

Kotlin Multiplatform Mobile (KMM)

What Is Kotlin Multiplatform Mobile (KMM) and Why Developers Are Switching in 2025

In the rapidly evolving world of mobile app development, one question keeps popping up in 2025:
“Is Kotlin Multiplatform Mobile (KMM) finally ready for prime time?”

The answer is a resounding yes.

Kotlin Multiplatform Mobile (KMM) has matured into a powerful tool that allows developers to share code across Android and iOS while still delivering a native user experience. With growing community support, enhanced tooling, and major production apps going multiplatform, it’s clear why many developers are making the switch.

Let’s break it all down in a simple way.

What Is Kotlin Multiplatform Mobile (KMM)?

Kotlin Multiplatform Mobile (KMM) is a feature of JetBrains’ Kotlin language that enables code sharing between Android and iOS apps. Unlike other cross-platform solutions like Flutter or React Native that render UI across platforms, KMM focuses on sharing business logic, not UI.

This means:

  • You write platform-independent code in Kotlin (like data models, business rules, network calls).
  • You write platform-specific UI with SwiftUI on iOS and Jetpack Compose on Android.

Here’s a visual breakdown:

               ┌──────────────────────────────┐
               │   Shared Kotlin Code (KMM)   │
               │  (Network, DB, Logic, etc.)  │
               └──────────────────────────────┘
                      ▲                 ▲
                      │                 │
        ┌─────────────┘                 └──────────────┐
        │                                              │
┌────────────┐                               ┌────────────┐
│ Android UI │                               │  iOS UI    │
│ Jetpack    │                               │ SwiftUI    │
└────────────┘                               └────────────┘

Why Are Developers Switching to KMM in 2025?

There are several reasons why Kotlin Multiplatform Mobile is trending in 2025. Let’s unpack the big ones:

1. Save Time, Save Money

Instead of writing the same logic twice (once in Kotlin and once in Swift), you write it once and share it. Teams can move faster without compromising on performance or UX.

2. Native Experience, No Compromise

KMM doesn’t touch your UI code. You still get fully native interfaces using the best platform-specific tools (SwiftUI and Jetpack Compose). This means your app feels right at home on both platforms.

3. First-Class Kotlin Support

Kotlin is now officially backed by Google for Android development and tightly integrated into JetBrains’ ecosystem. KMM benefits from constant language updates, better IDE tooling (especially in Android Studio and IntelliJ IDEA), and strong community support.

4. Flexible Adoption

You don’t have to rewrite your entire app. KMM allows gradual adoption. You can start with one shared module and expand as needed. It’s perfect for teams who want to test the waters without a full migration.

How KMM Works — A Simple Code Example

Let’s take a real-world example: fetching user data from an API and displaying it.

Step 1: Define the Shared Code

Inside the shared module:

commonMain/kotlin/UserRepository.kt

expect class HttpClientEngine()

class UserRepository(private val client: HttpClient = HttpClient(HttpClientEngine())) {
    suspend fun fetchUser(): User {
        val response = client.get("https://api.softaai.com/user") // just an example
        return Json.decodeFromString(response.bodyAsText())
    }
}

Here, expect means “I need a platform-specific implementation.” Kotlin will look for it in androidMain and iosMain.

Step 2: Android Implementation

androidMain/kotlin/PlatformHttpClient.kt

actual class HttpClientEngine {
    fun getEngine(): HttpClientEngine = Android.create()
}

Use Android-specific networking, like Ktor’s Android engine.

Step 3: iOS Implementation

iosMain/kotlin/PlatformHttpClient.kt

actual class HttpClientEngine {
    fun getEngine(): HttpClientEngine = Ios.create()
}

Same logic, but for iOS. You keep platform differences isolated and the rest of your business logic remains untouched.

What Makes KMM Developer-Friendly?

  • IDE Support: JetBrains has invested heavily in IntelliJ and Android Studio plugins that make working with shared code intuitive.
  • Official Libraries: Ktor (networking), Kotlinx Serialization, SQLDelight, and Coroutines work seamlessly with KMM.
  • Robust Testing: You can write unit tests for shared logic and run them across platforms.

Is KMM Production-Ready in 2025?

Yes — and it’s not just startups using it anymore.

Companies like VMware, Netflix, and Philips have integrated KMM into their production apps. JetBrains themselves use KMM in their own apps.

With Kotlin 2.0 officially supporting KMM and Kotlin/Native seeing major improvements in performance and stability, developers can now trust it for large-scale, production-grade apps.

When Should You Use Kotlin Multiplatform Mobile?

KMM is a great fit if:

  • You want code sharing between Android and iOS.
  • You want to retain full native UI control.
  • You already have Android developers familiar with Kotlin.
  • You prefer gradual migration over rewriting from scratch.

When NOT to Use KMM?

It might not be ideal if:

  • You want shared UI (in which case Flutter or React Native may suit better).
  • Your team lacks experience with Kotlin.
  • You’re targeting multiple platforms beyond mobile (e.g., Web + Desktop + Mobile).

Conclusion

Kotlin Multiplatform Mobile (KMM) has truly come of age in 2025. It’s no longer a niche experiment — it’s a production-ready, efficient, and modern way to build mobile apps with shared business logic and native performance.

RemoteViews

RemoteViews in Android: What It Is, How It Works, and Why It Matters

RemoteViews might not be something you think about often, but it plays a big role in many Android experiences you use every day. From home screen widgets to media controls in your notifications — and even the UI in Android Automotive dashboards — RemoteViews is quietly working behind the scenes.

But what exactly is RemoteViews? Why does Android need it in the first place?

In this post, we’ll break it all down. We’ll explore what RemoteViews is, how it works under the hood, where it’s commonly used, its limitations, and how it’s changing with modern Android development.

TL;DR: What Is RemoteViews?

RemoteViews is a mechanism in Android that lets you define and update UI components across different processes. It’s primarily used in:

  • Home screen app widgets
  • Custom notifications
  • Android Auto / Automotive OS UI templates
  • Legacy lock screen widgets

Because apps in Android run in isolated sandboxes (i.e., processes), RemoteViews offers a way to package simple view updates and deliver them to another process, like the system UI or launcher.

Why Does Android Need RemoteViews?

Android enforces strict process separation for security and performance. Each app runs in its own process, and the system UI — like the launcher or notification shade — runs in another. This design prevents one app from injecting arbitrary UI or code into another.

But what if your app needs to show a widget on the home screen? Or customize a rich notification with buttons and thumbnails?

That’s where RemoteViews comes in.

It acts like a deferred UI recipe: your app says, “I want to show this layout, with this text and image,” and Android takes care of rendering it safely in another process.

How RemoteViews Works (Under the Hood)

Let’s break down what happens behind the scenes:

1. You define a layout in XML, typically using a subset of supported views (TextView, ImageView, etc.).

2. You create a RemoteViews object in your app: 

val views = RemoteViews(context.packageName, R.layout.my_widget)
views.setTextViewText(R.id.title, "Hello World")
views.setImageViewResource(R.id.icon, R.drawable.my_icon)

3. You send it to the host process, like the AppWidgetManager or NotificationManager, using something like:

appWidgetManager.updateAppWidget(widgetId, views)

4. The system applies the layout and updates the UI in the context of the host process, ensuring it can’t be tampered with or crash the system UI.

Where RemoteViews Is Used

1. App Widgets (Home Screen Widgets)

The most classic use case. These are interactive UI snippets on your launcher that update periodically or in response to events (weather updates, calendars, to-do lists, etc.).

2. Custom Notifications

RemoteViews powers notification templates that include custom layouts — like music player controls, media thumbnails, and action buttons.

3. Android Automotive OS

Automotive dashboards require safe rendering of media apps. RemoteViews is used to define UI layouts that can be shown in car displays without exposing full UI control.

4. Lock Screen Widgets (Pre-Android 5.0)

Before Android Lollipop, apps could show widgets directly on the lock screen using RemoteViews.

Supported Views in RemoteViews

Not all views work with RemoteViews. Since everything must be serialized and sent across processes, only a subset of safe, lightweight views are supported:

Supported ViewsSupported Actions
TextViewsetTextViewText()
ImageViewsetImageViewResource()
ProgressBarsetProgressBar()
LinearLayout(limited, for stacking views)
ViewFlippershowNext() or showPrevious()
Chronometer, AnalogClockPartially supported

You can attach PendingIntents to certain views using setOnClickPendingIntent() for basic interactivity.

Limitations of RemoteViews

While RemoteViews solves a specific problem well, it’s not without trade-offs:

LimitationDescription
No custom viewsYou can’t use your own View subclasses or Compose UI
No animationsNo property animations, transitions, or motion effects
No real-time interactionCan’t handle gestures or live two-way data binding
Limited click supportOnly supports triggering PendingIntents on click
Tedious to update frequentlyYou must manually update RemoteViews and push to the host

Because of this, RemoteViews isn’t a full replacement for an activity or fragment—it’s a lightweight display mechanism.

RemoteViews in the Modern Android World

While RemoteViews is still widely used, especially for backward compatibility and automotive use cases, Google has introduced modern alternatives to make UI composition easier and more declarative:

Jetpack Glance (Recommended for Widgets)

Jetpack Glance is a modern Kotlin-based library for building app widgets using a Compose-like DSL. It abstracts RemoteViews under the hood but gives you a much more developer-friendly experience.

Notification Styles

If you’re building rich notifications, use Android’s built-in notification styles (MediaStyle, MessagingStyle, etc.) before falling back to custom RemoteViews.

When to Use RemoteViews (And When Not To)

Use It When…Avoid It When…
Building widgets or media-style notificationsBuilding full-featured or interactive UIs
Working with Android AutomotiveNeeding real-time feedback from users
You need safe cross-process UI renderingYou want fluid animations or dynamic layouts

Conclusion

Even in 2025, RemoteViews is still a core part of the Android ecosystem. It’s reliable, secure, and ideal for situations where your UI needs to run in a different process — like home screen widgets or notifications — and stay lightweight and sandboxed.

That said, Android’s UI development is evolving quickly. With modern tools like Jetpack Glance and Jetpack Compose, building UIs has become much more flexible and developer-friendly.

So, stick with RemoteViews when you need to — but don’t hesitate to adopt these newer, more powerful APIs when you can

FAQ

Q: Can I use Jetpack Compose with RemoteViews?
A: Not directly. However, Jetpack Glance offers a Compose-like way to build RemoteViews-compatible layouts.

Q: Is RemoteViews deprecated?
A: No, it’s still fully supported and maintained, especially in system-level use cases like Android Automotive and app widgets.

Q: Can RemoteViews support animations or gestures?
A: No. RemoteViews supports only limited click interactions via PendingIntent. Animations and complex gestures aren’t allowed.

Jetpack Glance

Jetpack Glance: A Modern Way to Build App Widgets in Android

Widgets are one of the oldest and most-loved features of Android. They let users access app content directly from the home screen, offering convenience, quick interactions, and persistent information.

But let’s be honest — building widgets used to be a pain. If you’ve ever dealt with RemoteViews, you probably remember how clunky and restrictive it felt. From limited layout options to awkward state updates, it often felt like using outdated tools in a modern development world.

Enter Jetpack Glance— a fresh, modern, and composable way to build Android app widgets.

In this blog, we’ll explore what Jetpack Glance is, how it works, why it matters, and how you can start building beautiful widgets that feel right at home in 2025.

What is Jetpack Glance?

Jetpack Glance is an Android Jetpack library that lets developers build app widgets using a declarative Kotlin API, inspired by Jetpack Compose. It’s designed to bring the modern development experience of Compose to Android widgets — without completely replacing the underlying AppWidget system.

Glance provides a Compose-like DSL for creating UI elements, handling interactions, and managing widget state. Under the hood, it still translates your code to the RemoteViews used by the Android system, ensuring full compatibility with homescreens and launchers.

Why Jetpack Glance Matters

If you’ve ever built widgets with XML and RemoteViews, you know how painful it can be. Layout constraints, limited interactivity, and poor testability were common complaints.

Jetpack Glance solves this by offering:

  • Declarative UI: Say goodbye to boilerplate XML. Use familiar composables like Text, Image, Row, Column, and Box.
  • Kotlin-first Development: Leverage the benefits of modern Kotlin and Compose practices.
  • Theming Support: Build widgets that match your app’s Material design and even adapt to dynamic colors.
  • Improved Readability and Maintainability: Code that’s easier to read, test, and update.
  • Compatibility with AppWidget System: Works seamlessly with Android’s native widget infrastructure.

Building UI with Glance Composables

Jetpack Glance offers a minimal set of UI elements modeled after Compose. Here’s what you can expect:

  • Text, Button, Image — for basic elements.
  • Row, Column, Box — for layouts.
  • LazyColumn — for scrollable lists (with limitations).
  • Spacer, Modifier, Padding, Alignment — to structure your layout.

These components are optimized for the widget framework, so you can’t use every Compose API, but it’s still a huge leap forward compared to traditional methods.

Kotlin
@Composable
fun MyWidgetContent() {
    Column(
        modifier = GlanceModifier.padding(16.dp)
    ) {
        Text("Hello Widget!")
        Button(
            text = "Open App",
            onClick = actionStartActivity<MainActivity>()
        )
    }
}

Handling User Interactions

Widgets aren’t just for display — they should be interactive. Glance makes this easier by providing built-in Action APIs.

You can define actions such as:

  • Launching Activities
  • Triggering Services
  • Sending Broadcasts
  • Executing Callbacks (via actionCallback)
Kotlin
Button(
    text = "Launch",
    onClick = actionStartActivity<MyActivity>()
)

Handling interactions has never been this straightforward in a widget context.

Theming and Styling

Glance supports both Material 2 and Material 3 themes, including dynamic color integration on supported devices. You can wrap your UI in a GlanceTheme to ensure consistent styling.

Kotlin
@Composable
fun ThemedWidget() {
    GlanceTheme {
        Text("Styled with Material 3")
    }
}

Glance adapts to light/dark mode and the device’s color palette, ensuring your widget looks native and polished.

State Management and Updates

Unlike Compose, which is reactive by default, Glance widgets are stateless and passive. That means you control when and how the widget updates.

You’ll typically use:

  • GlanceAppWidget.update(context) — to trigger a UI refresh.
  • GlanceStateDefinition — to persist data across updates.

It’s your job to observe changes (e.g., LiveData, WorkManager, or repository data) and update the widget manually when needed.

How to Get Started

Let’s walk through a simple step-by-step guide to help you get started with building widgets using Jetpack Glance.

1. Add Dependencies

Kotlin
dependencies {
    implementation "androidx.glance:glance-appwidget:1.1.1"
    implementation "androidx.glance:glance-material3:1.1.1"
}

2. Enable Compose Support

Kotlin
android {
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }
}

3. Define Your Widget

Kotlin
class MyGlanceWidget : GlanceAppWidget() {
    @Composable
    override fun Content() {
        MyWidgetContent() 
    }
}

4. Register the Widget

Update your AndroidManifest.xml and provide metadata via a resource XML file like res/xml/my_widget_info.xml.

Kotlin
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:resizeMode="horizontal|vertical"
    android:minWidth="250dp"
    android:minHeight="100dp"
    android:initialLayout="@layout/placeholder"
    android:updatePeriodMillis="1800000" />

Known Limitations

While Glance is a significant improvement, it’s still bound by the limitations of Android’s RemoteViews system:

  • No complex animations
  • Limited custom drawing or gestures
  • Limited real-time data updates

Expect more support in future releases, but for now, design within these constraints.

Conclusion

Jetpack Glance is a game-changer for Android widget development. It modernizes an aging system with a declarative approach that feels right at home in the Jetpack ecosystem.

Whether you’re updating legacy widgets or building new ones from scratch, Glance provides a smoother, more maintainable path forward.

So go ahead — give your users beautiful, functional widgets without the pain of XML and RemoteViews.

system design interview in 2 hours

How to Prepare for a System Design Interview in Just 2 Hours (Without the Panic)

System design interviews can feel like a maze — especially if you’re racing against the clock. Maybe you’ve been coding for years but never built large-scale distributed systems. Or maybe your interview is… today, and you’ve only got two hours to prepare.

Good news? That’s enough time to cover the most critical concepts and give a confident, structured answer that impresses any interviewer.

Whether you’re targeting top tech companies like FAANG or startups, this crash course blog is your realistic, no-fluff roadmap to nailing your next system design round.

What Is a System Design Interview, Really?

At its core, system design is about how you architect scalable, reliable, and maintainable software systems. Interviewers aren’t just checking if you can code — they want to know:

  • How do you break down a vague product requirement?
  • Can your design scale to millions of users?
  • What trade-offs are you aware of (latency vs consistency, cost vs performance)?
  • Do you communicate your ideas clearly?

You don’t need to design Google’s infrastructure in 45 minutes — but you do need to show structured thinking.

The 5-Step Framework That Works Every Time

Every successful system design interview follows this rough structure. Memorize it. Practice it. Make it second nature:

1. Clarify Requirements

Ask smart questions like:

  • Who are the users?
  • What are the core use cases
  • Do we care about latency, consistency, or availability more?

Example: In a URL shortener, do links expire? Do we track click analytics?

2. Define High-Level Architecture

Sketch the key components: client, API gateway, backend services, database, cache, CDN, etc.

3. Design Data Models

  • What data do we store?
  • What queries do we need to support?
  • Should we go SQL or NoSQL?

4. Handle Scale and Traffic

Now talk real-world:

  • Use load balancers to handle traffic spikes
  • Add caching to reduce DB load (Redis/Memcached)
  • Use sharding to horizontally scale your database
  • Use message queues to decouple services

5. Discuss Trade-offs & Bottlenecks

This is where you show maturity:

  • “To reduce latency, I’d introduce a cache — but stale data could be a concern.”
  • “We could shard by user ID, but we’ll need a lookup service to find the right shard.”

30 Minutes to Learn Key System Components (You Must Know These)

Before jumping into real-world problems, lock in these foundational building blocks:

ComponentWhat It DoesReal-World Use
Load BalancerDistributes incoming requests to serversNGINX, HAProxy, AWS ELB
CacheSpeeds up frequent reads by avoiding DB hitsRedis, Memcached
DatabaseStores structured dataSQL (Postgres, MySQL), NoSQL (MongoDB, Cassandra)
ShardingSplits DB across nodes for horizontal scalingCommon in high-scale apps
QueueDecouples async tasksKafka, RabbitMQ, Amazon SQS
CDNSpeeds up static content deliveryCloudflare, Akamai

If you understand these six, you can talk confidently about most design problems.

Now Let’s Design: 2 Must-Know Systems

You only have 2 hours. So focus on the most frequently asked designs. Learn the structure, not just the answer.

1. Design a URL Shortener (Like Bit.ly)

Functional Requirements:

  • Generate short links
  • Redirect to original URL
  • Track click analytics (optional)

High-Level Architecture:

  • API server
  • Database (SQL or NoSQL)
  • Cache for popular links (e.g., Redis)
  • Optional analytics pipeline

Design Details:

  • Use base62 encoding of auto-incrementing IDs
  • Or use hashing (MD5/SHA) + collision resolution
  • Store mappings in a key-value DB (e.g., DynamoDB)

Scale Consideration:

  • Add read replicas
  • Cache hot URLs
  • Use CDN for redirection page

2. Design a Messaging System (Like WhatsApp)

Functional Requirements:

  • Send/receive messages
  • Delivery receipts
  • Message history

Architecture:

  • User Service
  • Messaging Queue (e.g., Kafka)
  • Notification Service
  • NoSQL DB for chat history (e.g., Cassandra)

Scalability Tips:

  • Use message queues to decouple send/receive
  • Store messages in time-series DB or partition by user ID
  • Use WebSockets for real-time delivery

How to Talk During the Interview (Very Important!)

Interviewers care just as much about how you explain as what you explain. Use clear transitions:

  • “Let’s start with a high-level overview…”
  • “To scale this component, I’d use horizontal sharding…”
  • “Here’s a potential bottleneck and how I’d solve it…”

Speak confidently, and don’t get stuck in code-level details.

Final Checklist: What to Say & Do in the Interview

  • Ask clarifying questions
  • Think out loud
  • Use diagrams (draw boxes + arrows!)
  • Talk about scalability, latency, trade-offs
  • Keep it structured, not rambling
  • Finish with bottlenecks & improvements

Want to Go Deeper? (After the Interview)

If you get more time later, here’s what to study next:

Final Words: Confidence Beats Perfection

You don’t need to know everything. But with this 2-hour plan, you’ll walk into your interview:

  • With a framework
  • With working knowledge of key components
  • With 1–2 solid design examples ready

Remember: structure, clarity, and trade-offs are what interviewers love most.

Mastering Lua

Mastering Lua: A Beginner’s Guide to This Lightweight Yet Powerful Language

If you’re stepping into programming or looking for a scripting language that is lightweight, fast, and powerful, Lua is an excellent choice. Used in game development, embedded systems, and even AI, Lua offers simplicity without sacrificing capability. This guide will walk you through the essentials, helping you master Lua with ease.

What Is Lua?

Lua is a high-level, lightweight scripting language designed for speed and efficiency. It was created in 1993 by a team of Brazilian developers and has since gained popularity in game development (Roblox, World of Warcraft mods) and embedded applications.

Why Learn Lua?

  • Easy to Learn: Lua has a simple syntax, making it beginner-friendly.
  • Lightweight and Fast: Lua is designed for speed, consuming minimal system resources.
  • Highly Flexible: It supports procedural, object-oriented, and functional programming.
  • Widely Used in Game Development: Many games and engines, like Unity and Love2D, use Lua.

Setting Up Lua

Before diving into Lua programming, you’ll need to install it.

Installing Lua

  1. Windows: Download Lua from https://www.lua.org/download.html and install it.
  2. Mac: Use Homebrew:
Lua
brew install lua

Linux: Use the package manager:

Lua
sudo apt-get install lua5.4

Online Lua Interpreter: If you don’t want to install Lua, try an online interpreter like Replit.

Verify installation by running:

Lua
lua -v

This will display the installed Lua version.

Lua Basics

1. Hello, Lua!

Let’s start with the classic “Hello, World!” program.

Lua
print("Hello, Lua!")

Simply run this script, and you’ll see Hello, Lua! printed on the screen.

2. Variables and Data Types

Lua has dynamic typing, meaning variables do not require explicit type definitions.

Lua
name = "Amol"
age = 25
height = 5.9
isLearning = true

Lua supports:

  • Strings: "Hello"
  • Numbers: 10, 3.14
  • Booleans: true, false
  • Tables (similar to arrays and dictionaries)
  • Nil (represents an absence of value)

3. Conditional Statements

Lua uses if-then statements for decision-making.

Lua
score = 85
if score >= 90 then
    print("Excellent!")
elseif score >= 70 then
    print("Good job!")
else
    print("Keep practicing!")
end

4. Loops

For Loop

Lua
for i = 1, 5 do
    print("Iteration: ", i)
end

While Loop

Lua
count = 1
while count <= 5 do
    print("Count: ", count)
    count = count + 1
end

Functions in Lua

Functions help in structuring code efficiently.

Defining and Calling Functions

Lua
function greet(name)
    print("Hello, " .. name .. "!")
end

greet("Amol")

Here, the .. operator is used for string concatenation.

Returning Values

Lua
function add(a, b)
    return a + b
end

result = add(5, 3)
print("Sum: ", result)

Working with Tables (Arrays and Dictionaries)

Lua tables function as both arrays and dictionaries.

Array-like Table

Lua
fruits = {"Apple", "Banana", "Cherry"}
print(fruits[1])  -- Output: Apple

Dictionary-like Table

Lua
person = {name = "Amol", age = 30}
print(person.name)  -- Output: Amol

Object-Oriented Programming in Lua

While Lua doesn’t have built-in OOP, it can be implemented using tables and metatables.

Creating a Simple Class

Lua
Person = {}
Person.__index = Person

function Person:new(name, age)
    local obj = {name = name, age = age}
    setmetatable(obj, Person)
    return obj
end

function Person:greet()
    print("Hi, I am " .. self.name .. " and I am " .. self.age .. " years old.")
end

p1 = Person:new("Amol", 25)
p1:greet()

Error Handling

Use pcall (protected call) to catch errors gracefully.

Lua
function divide(a, b)
    if b == 0 then
        error("Cannot divide by zero!")
    end
    return a / b
end

status, result = pcall(divide, 10, 0)
if status then
    print("Result: ", result)
else
    print("Error: ", result)
end

Conclusion

Mastering Lua opens doors to game development, scripting, and embedded systems. With its simple syntax, high efficiency, and flexibility, it’s a fantastic choice for beginners and experienced developers alike. Keep practicing, build projects, and explore Lua’s potential further!

error: Content is protected !!