Android

Mastering Jetpack Compose

Mastering Jetpack Compose: The Ultimate Guide to Navigation, State Management, Animations, and More

Jetpack Compose is revolutionizing Android UI development, offering a declarative, efficient, and intuitive approach to building beautiful applications. Whether you’re just starting or looking to refine your expertise, this guide will help you understand key aspects such as navigation, state management, animations, custom composables, performance optimization, and theming in Jetpack Compose.

Navigation in Jetpack Compose

Setting Up Navigation

Jetpack Compose replaces traditional Fragment-based navigation with NavHost and NavController. First, add the dependency:

Kotlin
implementation("androidx.navigation:navigation-compose:2.7.5")

Basic Navigation Example

Kotlin
@Composable
fun MyApp() {
    val navController = rememberNavController()
    NavHost(navController, startDestination = "home") {
        composable("home") { HomeScreen(navController) }
        composable("details") { DetailsScreen() }
    }
}

@Composable
fun HomeScreen(navController: NavController) {
    Column {
        Text("Home Screen")
        Button(onClick = { navController.navigate("details") }) {
            Text("Go to Details")
        }
    }
}

@Composable
fun DetailsScreen() {
    Text("Details Screen")
}

Passing Data Between Screens

To pass arguments:

Kotlin
composable("details/{userId}") { backStackEntry ->
    val userId = backStackEntry.arguments?.getString("userId")
    DetailsScreen(userId)
}

Navigate with:

Kotlin
navController.navigate("details/123")

State Management in Jetpack Compose

Jetpack Compose follows unidirectional data flow (UDF). Here are the key approaches to managing state:

Using remember and mutableStateOf (Local State)

Kotlin
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

Using ViewModel (Recommended for Shared State)

Add ViewModel dependency:

Kotlin
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
Kotlin
class CounterViewModel : ViewModel() {
    var count by mutableStateOf(0)
        private set

    fun increment() {
        count++
    }
}

@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
    Column {
        Text("Count: ${viewModel.count}")
        Button(onClick = { viewModel.increment() }) {
            Text("Increment")
        }
    }
}

Animations in Jetpack Compose

Simple Animation Example

Kotlin
@Composable
fun AnimatedBox() {
    var expanded by remember { mutableStateOf(false) }
    val size by animateDpAsState(if (expanded) 200.dp else 100.dp, label = "")

    Box(
         modifier = Modifier
            .size(size)
            .background(Color.Blue)
            .clickable { expanded = !expanded }
    )
}

Advanced Animation with AnimatedVisibility

Kotlin
@Composable
fun AnimatedText(visible: Boolean) {
    AnimatedVisibility(visible) {
        Text("Hello, Jetpack Compose!", fontSize = 24.sp)
    }
}

Custom Composables

Reusable Button Composable

Kotlin
@Composable
fun CustomButton(text: String, onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text(text)
    }
}

@Composable
fun ExampleUsage() {
    CustomButton(text = "Click Me") {
        println("Button Clicked!")
    }
}

Performance Optimization in Compose

Avoid Unnecessary Recompositions

Use remember to store expensive calculations:

Kotlin
@Composable
fun ExpensiveOperation() {
    val result = remember { computeSomethingExpensive() }
    Text("Result: $result")
}

Use LaunchedEffect for Side Effects

Kotlin
@Composable
fun TimerEffect() {
    LaunchedEffect(Unit) {
        delay(1000L)
        println("Executed after 1 second")
    }
}

Theming and Styling with Material3

Setting Up Material3

Add the dependency:

Kotlin
implementation("androidx.compose.material3:material3:1.2.0")

Defining a Custom Theme

Define colors in Color.kt:

Kotlin
val PrimaryColor = Color(0xFF6200EE)
val SecondaryColor = Color(0xFF03DAC5)

And apply them in Theme.kt:

Kotlin
val myColorScheme = lightColorScheme(
    primary = PrimaryColor,
    secondary = SecondaryColor
)

@Composable
fun MyTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = myColorScheme,
        typography = Typography(),
        content = content
    )
}

Conclusion

Jetpack Compose is an exciting evolution in Android UI development, simplifying layouts, interactions, and performance optimizations. Mastering navigation, state management, animations, and theming allows you to build more efficient, maintainable, and beautiful Android applications.

Scaffold in Jetpack Compose

Using Scaffold in Jetpack Compose: A Comprehensive Guide

Jetpack Compose has completely revolutionized Android UI development by providing a more declarative and functional approach to building user interfaces. One of the most useful composables in Jetpack Compose is Scaffold, which serves as a foundation for structuring your app’s UI. It allows you to easily incorporate common UI elements such as the Top App Bar, Bottom Navigation, Floating Action Button (FAB), and a Navigation Drawer while maintaining proper layout management.

If you’re new to Scaffold or want to understand it in-depth, this guide will walk you through everything you need to know, from its purpose and usage to best practices and real-world examples.

What is Scaffold in Jetpack Compose?

Scaffold is a high-level composable that provides a consistent layout structure for your app’s screen. It helps developers efficiently organize essential UI elements without having to manually handle padding, margins, and overlapping views. By default, Scaffold ensures that UI components do not interfere with each other, making layout management much simpler.

Key Features of Scaffold

  • Top App Bar: Displays a title, actions, and navigation button.
  • Bottom App Bar: Provides a space for bottom navigation or actions.
  • Floating Action Button (FAB): A prominent button for primary actions.
  • Drawer Content: A slide-out navigation drawer.
  • Content Slot: The main screen content, properly adjusted to accommodate other elements.

How to Use Scaffold in Jetpack Compose?

Now, let’s dive into the implementation of Scaffold and see how it helps structure an app’s UI.

Basic Scaffold Implementation

Here’s a simple example of how to use Scaffold in Jetpack Compose:

Kotlin
@Composable
fun MyScaffoldExample() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("My App") },
                backgroundColor = MaterialTheme.colorScheme.primary
            )
        },
        bottomBar = {
            BottomAppBar {
                Text("Bottom Bar", modifier = Modifier.padding(16.dp))
            }
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { /* Handle click */ }) {
                Icon(Icons.Default.Add, contentDescription = "Add")
            }
        },
        floatingActionButtonPosition = FabPosition.End, // Positions FAB at the end
        isFloatingActionButtonDocked = true, // Dock FAB in BottomAppBar if present
        drawerContent = {
            Column(modifier = Modifier.fillMaxSize()) {
                Text("Drawer Item 1", modifier = Modifier.padding(16.dp))
                Text("Drawer Item 2", modifier = Modifier.padding(16.dp))
            }
        }
    ) { paddingValues ->
        // Main content
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues),
            contentAlignment = Alignment.Center
        ) {
            Text("Hello, Compose!")
        }
    }
}

Understanding Each Parameter in Scaffold

Let’s break down each component used in the Scaffold setup:

1. Top App Bar

The TopAppBar provides a header for the screen, which usually includes the app title, action buttons, and a navigation button (like a hamburger menu for drawers).

Kotlin
TopAppBar(
    title = { Text("My App") },
    backgroundColor = MaterialTheme.colorScheme.primary
)

2. Bottom App Bar

The BottomAppBar is useful when you need navigation elements or action buttons at the bottom.

Kotlin
BottomAppBar {
    Text("Bottom Bar", modifier = Modifier.padding(16.dp))
}

3. Floating Action Button (FAB)

The FAB is used for primary actions like adding a new item.

Kotlin
FloatingActionButton(onClick = { /* Handle click */ }) {
    Icon(Icons.Default.Add, contentDescription = "Add")
}

4. Drawer Content

The drawerContent parameter allows adding a navigation drawer, which can be accessed by swiping from the left or clicking a menu button.

Kotlin
Column(modifier = Modifier.fillMaxSize()) {
    Text("Drawer Item 1", modifier = Modifier.padding(16.dp))
    Text("Drawer Item 2", modifier = Modifier.padding(16.dp))
}

5. Content Slot

The content lambda is where your main UI resides. It automatically adjusts padding to prevent overlapping with the top bar, bottom bar, or FAB.

Kotlin
Box(
    modifier = Modifier
        .fillMaxSize()
        .padding(paddingValues),
    contentAlignment = Alignment.Center
) {
    Text("Hello, Compose!")
}

Best Practices When Using Scaffold

  • Use paddingValues correctly: Always apply paddingValues in your content to prevent UI elements from being obstructed.
  • Optimize for different screen sizes: Ensure the layout adapts well on both phones and tablets.
  • Keep the UI simple: Avoid cluttering the screen with too many elements.
  • Use Material Design principles: Stick to design guidelines for a better user experience.

When to Use Scaffold?

You should use Scaffold when your app requires:

  • A consistent layout structure with a TopAppBar, BottomAppBar, and FAB.
  • A navigation drawer for better user experience.
  • An adaptive UI that properly handles padding and layout overlaps.

Conclusion

Jetpack Compose’s Scaffold is a powerful tool for structuring your app’s UI effortlessly. By using Scaffold, you can ensure a clean, organized, and well-structured layout while handling common UI components efficiently. Whether you are building a simple app or a complex UI, Scaffold simplifies the process and aligns with modern Material Design guidelines.

Jetpack Compose: A Beginner's Guide

Getting Started with Jetpack Compose: A Beginner’s Guide

Jetpack Compose is a modern UI toolkit designed to simplify Android app development. It replaces the traditional XML-based UI approach with a declarative programming model, making it easier to create interactive and dynamic user interfaces. If you’re new to Compose, this guide will walk you through everything you need to know—from setup to building a simple UI.

Why Jetpack Compose?

Jetpack Compose brings several advantages over traditional Android UI development:

  • Less Boilerplate Code: No need to write complex XML layouts.
  • Declarative UI: Define UI components based on state, making them easier to update.
  • Improved Performance: Compose draws everything directly, removing unnecessary layout passes.
  • Seamless Integration with Kotlin: Designed to work natively with Kotlin and Coroutines.
  • Better Maintainability: UI components are modular and reusable.

Setting Up Compose in Your Project

Before you start, ensure that you have:

  • The latest version of Android Studio (recommended: Giraffe or later)
  • Kotlin support enabled in your project

Add Dependencies

In your project’s build.gradle (Module: app), add the necessary Jetpack Compose dependencies:

Kotlin
android {
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.4"
    }
}

dependencies {
    implementation("androidx.compose.ui:ui:1.5.4")
    implementation("androidx.compose.material:material:1.5.4")
    implementation("androidx.compose.ui:ui-tooling-preview:1.5.4")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
}

Once added, sync your project to fetch dependencies.

Understanding Composable Functions

At the core of Jetpack Compose are @Composable functions, which define UI components.

Kotlin
@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}

To display this function in an activity, use setContent inside onCreate:

Kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Greeting("Compose")
        }
    }
}

Previewing UI in Android Studio

Jetpack Compose allows you to preview UI components without running the app. Use the @Preview annotation:

Kotlin
@Preview(showBackground = true)
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}

Click on Build & Refresh in the preview window to see your UI instantly.

Common UI Components in Jetpack Compose

Jetpack Compose offers built-in UI elements like Text, Button, Column, Row, Image, etc. Here are some of the most used ones:

Text Component

Kotlin
@Composable
fun SimpleText() {
    Text(text = "Hello, Compose!", fontSize = 24.sp)
}

Button Component

Kotlin
@Composable
fun ClickableButton() {
    Button(onClick = { /* Handle click */ }) {
        Text("Click Me")
    }
}

Layouts: Column & Row

Jetpack Compose provides flexible layout components.

Column Layout (Vertical List)

Kotlin
@Composable
fun ColumnExample() {
    Column {
        Text("Item 1")
        Text("Item 2")
    }
}

Row Layout (Horizontal List)

Kotlin
@Composable
fun RowExample() {
    Row {
        Text("Item 1")
        Text("Item 2")
    }
}

Managing State in Jetpack Compose

Jetpack Compose uses State to manage dynamic content. The UI automatically updates when the state changes.

Kotlin
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text(text = "Count: $count")
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
}
  • remember { mutableStateOf(0) } stores and updates the UI state.
  • Clicking the button increases the counter, and the UI updates instantly.

Theming with Material Design

Jetpack Compose supports Material Design principles out of the box.

Kotlin
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    MaterialTheme {
        content()
    }
}

You can further customize colors, typography, and shapes using the MaterialTheme system.

Conclusion

Jetpack Compose is a game-changer for Android UI development, offering a cleaner, more efficient way to build interactive applications. With its declarative approach, built-in state management, and seamless integration with Kotlin, it’s an excellent choice for modern Android apps.

  • Less boilerplate
  • More readable code
  • Better performance
  • Faster development

Now that you have a solid understanding of the basics, why not start building your own Compose UI?

android studio meerkat

Android Studio Meerkat Is Here: 10 Game-Changing Features You Must Know

Android Studio Meerkat is officially here — and it’s more than just a cute name. This release introduces powerful updates that aim to speed up development, enhance debugging, and improve the overall Jetpack Compose experience. Whether you’re building complex apps or just starting out, Meerkat brings features that can seriously boost your productivity.

Let’s explore the 10 most game-changing features you absolutely must know.

1. Live Edit for Jetpack Compose Is Now Stable

Live Edit is no longer experimental! You can now see real-time changes in Compose previews or even on a device/emulator without restarting the app. This drastically speeds up your UI development workflow.

No more rebuilding. Just tweak and watch it update live.

2. IntelliJ Platform Upgrade (2023.3)

Meerkat includes the IntelliJ IDE platform version 2023.3, bringing in:

  • Smarter code completion
  • Better navigation tools
  • Performance enhancements

This makes the entire experience smoother and more intelligent, especially with Kotlin and Gradle support.

3. Device Mirroring (Now on Stable)

You can now mirror a physical device’s screen directly inside Android Studio — wirelessly or via USB. Use it to:

  • Interact with your app in real time
  • Drag and drop files
  • Take screenshots and screen recordings

Super useful for quick testing and demos.

4. Improved Compose State Inspection

The Compose State Inspector is now more powerful, letting you:

  • View recompositions
  • Understand state relationships
  • Debug UI performance issues with clarity

No more guessing which state triggered a recomposition!

5. Updated Profiler UI

Performance profiling got a visual refresh with:

  • Cleaner timeline views
  • Easier navigation between CPU, memory, and network usage
  • Better flame graphs

Perfect for finding those sneaky bottlenecks.

6. Version Catalog Support in New Project Wizard

Using libs.versions.toml? You’re in luck. Meerkat’s New Project Wizard now supports Version Catalogs out of the box. This helps:

  • Keep dependencies consistent
  • Share versions across modules
  • Simplify upgrades

7. Updated Layout Inspector for Compose

Compose developers can now enjoy:

  • Enhanced real-time tree views
  • Better performance when inspecting deep hierarchies
  • A cleaner UI for navigating views and modifiers

This turns debugging complex UIs into a visual breeze.

8. AI-Powered Code Suggestions (Experimental)

Meerkat brings in early-stage AI code assistance features — like smart suggestions, code completions, and intent-aware quick fixes.

Experimental, but promising! Great for productivity bursts or learning APIs.

9. Gradle Sync & Build Speed Improvements

Google has optimized how Meerkat interacts with Gradle:

  • Faster sync times
  • Reduced memory usage
  • Improved project indexing

The result? Less waiting, more coding.

10. Enhanced Privacy Sandbox Tools

If you’re developing apps that integrate with the Privacy Sandbox on Android, Meerkat introduces new SDK Runtime tools to:

  • Inspect runtime behavior
  • Debug SDK communications
  • Ensure privacy-first design patterns

Conclusion

Android Studio Meerkat isn’t just a minor update — it’s a leap forward in usability, performance, and Jetpack Compose support. From Live Edit going stable to device mirroring and AI-assisted coding, this release is packed with value.

So if you haven’t upgraded yet, now is the time to install Meerkat and explore these productivity-boosting features.

Device Mirroring

Why You Can’t Interact with Device Mirroring in Android Studio Meerkat (And How to Fix It)

With the arrival of Android Studio Meerkat, Google has brought some exciting quality-of-life improvements for Android developers — including Device Mirroring, a sleek way to display your physical device screen directly inside the IDE.

But there’s one frustrating hiccup many developers are running into:

“The screen mirrors just fine, but I can’t interact with it! No clicks, no touches — nothing works.”

If that’s happening to you, don’t worry. You’re not alone, and this post breaks down why it’s happening, what you can (and can’t) do about it, and the best alternatives if you need full input control.

Device Mirroring in Meerkat: What’s the Deal?

Device Mirroring in Android Studio Meerkat lets you see your physical Android device’s screen directly in the IDE, without any third-party tools or extra setup. It’s a welcome addition, especially if you frequently run and test builds on physical devices.

However, many developers are discovering a limitation: it’s currently passive mirroring only — you can see your device screen, but you can’t interact with it via the mouse or keyboard in many environments.

This limitation is not a bug in your setup — it’s a limitation in the feature itself (for now).

Why Interaction Doesn’t Work

Here’s why you’re unable to touch, swipe, or type into the mirrored screen:

1. ADB Permissions May Be Misconfigured

Even if mirroring appears to work, input control requires proper authorization between your device and ADB.

Fix:
  • Disconnect and reconnect your device.
  • On your Android device, go to: Settings → Developer Options → Revoke USB debugging authorizations
  • Reconnect the device and tap “Allow” when prompted.

This ensures ADB has full access.

2. Device Mirroring is Currently Read-Only in Some Builds

Not all versions of Android Studio Meerkat allow full interaction. Even though the screen is mirrored, Google is still rolling out input support gradually, and many stable builds do not yet include full touch or input simulation.

In short: You’re not doing anything wrong — this feature just isn’t fully baked yet in some builds.

How to Work Around It (and Actually Interact)

If you absolutely need to interact with your device screen from your computer, there’s a better tool made for that job: scrcpy.

Solution: Use scrcpy for Full Interaction

scrcpy is an open-source tool that mirrors your Android device screen with full keyboard and mouse input support, including:

  • Clicks and gestures
  • Text input
  • Copy/paste between device and computer
  • Drag-and-drop APK installation
  • Even wireless mirroring

It’s fast, lightweight, and works beautifully.

Installation (Pick Your Platform):

On macOS:

Bash
brew install scrcpy

On Ubuntu/Debian:

Bash
sudo apt install scrcpy

On Windows (with Chocolatey):

Bash
choco install scrcpy

Running It:

Just plug in your device and type:

Bash
scrcpy

Boom. Fully interactive Android device on your screen.

Extra Tips to Make Sure Mirroring Works Smoothly

Even if you stick with Android Studio’s built-in mirroring (and wait for interaction support), here are a few things to check:

1. Update Android Studio Meerkat

Make sure you’re running the latest build — Google is rolling out changes rapidly.

  • Go to Help → Check for Updates (on macOS: Android Studio → Check for Updates)
  • Or download the latest version directly from: developer.android.com/studio

2. Revisit Developer Options on Device

  • USB debugging must be enabled
  • Consider disabling “Restrict USB access” (available on some newer Android versions)
  • Turn off features like Pointer location and Show touches if they interfere

Frequently Asked Question: Is Device Mirroring Meant to Replace scrcpy?

Not yet. Android Studio’s Device Mirroring is still evolving. It’s great for previewing, but not quite ready to replace scrcpy if you need:

  • Full touch simulation
  • Input from mouse and keyboard
  • Drag-and-drop support

Google may eventually match those features, but scrcpy is the gold standard right now.

Conclusion

Device Mirroring in Android Studio Meerkat is a solid step forward, but it’s not yet a complete solution for interactive testing. If you’re seeing the screen but can’t touch it, that’s not your fault — it’s just where the feature currently stands.

In the meantime, scrcpy is your best friend for full control, and it works side-by-side with Android Studio just fine.

Keep your tools updated, watch for changes in Canary builds, and you’ll be the first to benefit when full interaction support lands.

Circular Dependencies

Resolving Circular Dependencies in Gradle: A Complete Guide

If you’re working on a multi-module Gradle project, encountering circular dependencies can be a frustrating roadblock. You may see an error like this:

Kotlin
Circular dependency between the following tasks:
:javaModule:compileJava
+--- :kotlinModule:compileJava
|    +--- :javaModule:compileJava (*)
|    \--- :kotlinModule:compileKotlin
|         \--- :javaModule:compileJava (*)
\--- :kotlinModule:compileKotlin (*)

This error occurs when two modules depend on each other, creating a loop that prevents Gradle from determining the correct build order.

Note- To recreate this scenario, we created two modules (javaModule and kotlinModule) and intentionally introduced a circular dependency.

In this guide we will cover the root cause of circular dependencies in Gradle and practical solutions to fix them.

Understanding the Problem: What Causes Circular Dependencies in Gradle?

A circular dependency occurs when two or more modules depend on each other, forming a loop that Gradle cannot resolve. In the error above:

  1. javaModule needs kotlinModule to compile.
  2. kotlinModule depends on javaModule to compile its Java code.
  3. kotlinModule:compileKotlin also requires javaModule, reinforcing the cycle.

Common Causes of Circular Dependencies

  • Bi-Directional Module Dependencies: If javaModule and kotlinModule both reference each other using implementation project(":eachOtherModule"), Gradle gets stuck in an infinite loop.
  • Transitive Dependencies: A third-party dependency might be causing an indirect loop between your modules.
  • Improper Task Dependencies: Gradle’s build order may be incorrectly configured, forcing modules to compile in a conflicting sequence.

How to Fix Circular Dependencies in Gradle

1. Remove Direct Bi-Directional Dependencies

The most common cause of circular dependencies is when two modules depend on each other directly.

Check your build.gradle or build.gradle.kts files:

Kotlin
// javaModule's build.gradle.kts
dependencies {
    implementation(project(":kotlinModule")) // Java module depends on Kotlin module
}

// kotlinModule's build.gradle.kts
dependencies {
    implementation(project(":javaModule")) // Kotlin module depends on Java module (Creates a cycle)
}

Since both modules reference each other, Gradle cannot determine the correct build order.

Solution: Remove one of the dependencies or refactor the project.

2. Use api Instead of implementation for Interface Dependencies

If a module only requires interfaces or abstract classes from another module, use api instead of implementation.

Kotlin
// build.gradle.kts
dependencies {
    api(project(":javaModule")) // Instead of implementation
    // ... other dependencies
}

This allows dependent modules to access the necessary classes without forcing a full compile-time dependency.

3. Introduce a Common Module

If both javaModule and kotlinModule share some code, it’s best to move that code into a separate module (e.g., commonModule).

New Project Structure:

Kotlin
commonModule
├── javaModule (depends on commonModule)
├── kotlinModule (depends on commonModule)

Update build.gradle.kts files accordingly:

javaModule/build.gradle.kts

Kotlin
// build.gradle.kts
dependencies {
    implementation(project(":commonModule"))
    // ... other dependencies
}

kotlinModule/build.gradle.kts

Kotlin
// build.gradle.kts
dependencies {
    implementation(project(":commonModule"))
    // ... other dependencies
}

Now, both javaModule and kotlinModule rely on commonModule, breaking the circular dependency.

4. Use Gradle Dependency Constraints

If you suspect a transitive dependency is causing the issue, use constraints in dependencies:

Kotlin
dependencies {
    constraints {
        implementation(project(":javaModule")) {
            because("Avoids circular dependency with kotlinModule")
        }
    }
}

This helps Gradle prioritize dependencies correctly.

5. Exclude Unnecessary Transitive Dependencies

If kotlinModule indirectly depends on javaModule due to a third-party library, exclude it:

Kotlin
dependencies {
    implementation(project(":javaModule")) {
        exclude(module = "kotlinModule") // Prevents circular dependency
    }
}

This prevents Gradle from resolving unnecessary dependencies that might create loops.

6. Adjust Compilation Task Dependencies

By default, Kotlin and Java modules in Gradle may have incorrect build order assumptions. You can explicitly set task dependencies:

Kotlin
tasks.named("compileKotlin") {
    dependsOn(tasks.named("compileJava").get())
}

This ensures Kotlin compilation happens after Java compilation, resolving potential conflicts.

Final Checks: Debugging Dependencies

After making changes, run the following command to inspect your project’s dependency tree:

Kotlin
./gradlew dependencies

Lists all dependencies in the project, including:

  • Direct dependencies
  • Transitive dependencies
  • Dependency versions
  • Resolved dependency graph
  • Helps debug dependency issues, such as conflicts or unexpected transitive dependencies.

If a circular dependency still exists, this command will highlight the problematic modules.

Conclusion

Circular dependencies in Gradle can cause major build issues, but they can be resolved using the right strategies. The best approach depends on the cause:

  • If modules depend on each other directly, remove one of the dependencies.
  • If shared code is needed, move it to a commonModule.
  • If the issue is caused by transitive dependencies, use exclude or constraints.
  • If compilation order is incorrect, manually adjust tasks.named("compileKotlin").

By following these steps, you can eliminate circular dependencies and improve the maintainability of your Gradle projects.

Gradle Dependency Configurations

Android Gradle Dependency Configurations: 8 Key Types, Differences & Best Practices

Gradle is an essential build tool for Android development, managing dependencies and automating the build process. One of the critical aspects of Gradle is its dependency configurations, which control how dependencies are included in a project. In this article, we will explore different Gradle dependency configurations in Android, explaining their usage with Kotlin DSL (build.gradle.kts) examples.

What Are Gradle Dependency Configurations?

In Gradle, dependencies have specific scopes, meaning they are used in different phases of your project.

  • Some dependencies are needed during compilation to build your source code.
  • Others are required only at runtime when the application is running.
Kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.12.0")   // Needed for compiling and running the app
    runtimeOnly("com.squareup.okio:okio:3.3.0")      // Needed only at runtime (e.g. for file I/O)
}

Gradle manages these scopes using Configurations, which define when and how dependencies are used in the build process. Dependency configurations help organize dependencies based on their purpose, such as:

  • Compiling source code
  • Running the application
  • Testing the project

These configurations ensure that dependencies are available at the right stage of development, helping Gradle resolve them efficiently.

Commonly Used Gradle Dependency Configurations in Android

For Main Code (Application/Library Code)

  • api – Used for both compilation and runtime. Also included in the published API (for libraries).
  • implementation – Used for compilation and runtime, but not exposed in the published API.
  • compileOnly – Used only for compilation, not available at runtime.
  • compileOnlyApi – Like compileOnly, but included in the published API.
  • runtimeOnly – Used only at runtime, not needed for compilation.

For Tests

  • testImplementation – Used for compiling and running tests.
  • testCompileOnly – Used only for compiling tests, not available at runtime.
  • testRuntimeOnly – Used only at test runtime, not needed for compilation.

Have you noticed this — what does ‘published API’ mean?

When we say ‘published API,’ we are talking about the public API of our module or library — the part that other modules or projects can see and use.

For example, if we are creating a library (myLibrary) that will be used by another project (otherApp), some dependencies need to be exposed to the users of our library, while others should remain internal.

Now, let’s break down each configuration with explanations and see how we will use it in our code.

implementation

The implementation configuration is the most commonly used dependency type. It ensures that a module can access the dependency but does not expose it to other modules.

Kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}

Here,

  • The implementation keyword ensures that the dependency is only available to the module where it is declared.
  • Other modules cannot access these dependencies, reducing compilation time and improving modularity.
  • Also, dependencies declared with implementation are available at compile time and runtime.

api

The api configuration behaves similarly to implementation, but it exposes the dependency to other modules that depend on that particular module. This means we should use api when we want to expose a dependency to other modules that depend on our module.

Kotlin
// Adding a dependency that should be exposed to other modules
dependencies {
    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
}
  • If Module A includes this dependency with api, then Module B (which depends on Module A) can also access lifecycle-viewmodel-ktx.
  • Use api for both compilation and runtime, but only when the dependency is part of your public API.
  • This is useful for creating libraries or shared modules.

compileOnly

The compileOnly configuration includes dependencies only during compilation but excludes them at runtime.

Kotlin
// Adding a dependency that is required only at compile time
// Lombok is a Java library, but we can also use it in Android to reduce boilerplate code by providing annotations
dependencies {
    compileOnly("org.projectlombok:lombok:1.18.36")
    annotationProcessor("org.projectlombok:lombok:1.18.36")
    kapt("org.projectlombok:lombok:1.18.36") // kotlin project(Kotlin KAPT plugin)
}
  • compileOnly is typically used for annotation processors or compile-time dependencies that are not needed when the application is running.
  • Useful for lightweight dependencies that assist in code generation.
  • The dependency will not be included in the final APK.

compileOnlyApi

The compileOnlyApi configuration behaves like compileOnly, but it also exposes the dependency to modules that depend on this module.

Kotlin
// Adding a compile-only dependency that should be exposed
dependencies {
    compileOnlyApi("org.jetbrains:annotations:20.1.0")
}
  • The module itself uses this dependency only during compilation.
  • Other modules that depend on this module can also access the dependency.
  • Useful when developing shared libraries where compile-time dependencies need to be propagated.

runtimeOnly

The runtimeOnly configuration ensures that the dependency is available only at runtime, not during compilation.

Kotlin
dependencies {
    implementation("com.jakewharton.timber:timber:5.0.1") // Available at compile-time
    runtimeOnly("com.github.tony19:logback-android:3.0.0") // Only used at runtime for logging, logback-android is a backend logging framework that processes logs at runtime.
}
  • The code is compiled without logback-android because it is not available during compilation.
  • runtimeOnly ensures that the dependency is available only at runtime and is not included in the compilation classpath.
  • Helps in keeping the compilation classpath clean.

testImplementation

testImplementation is used when you need a dependency for both compiling and running test cases. This is the most commonly used configuration for test dependencies.

Kotlin
// Adding a testing library
dependencies {
    testImplementation("junit:junit:4.13.2")
}
  • testImplementation ensures that the JUnit library is only available in the test environment.
  • It does not get bundled into the final application.

testCompileOnly

testCompileOnly is used when a dependency is required only at test compile-time but is not needed at runtime. This is useful when a dependency provides compile-time annotations or APIs but doesn’t need to be included during test execution.

Kotlin
dependencies {
    testCompileOnly 'junit:junit:4.13.2'  // Available at compile-time but not included at runtime
}

This means,

  • The dependency is only available during compilation for unit tests.
  • It is not included in the test runtime classpath.
  • Use it when you need a library at compile time (e.g., compile-time annotations) but don’t want it in the runtime environment.

testRuntimeOnly

testRuntimeOnly is used when a dependency is needed only at test runtime and is not required at compile-time.

Kotlin
dependencies {
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.9.1")
}

Here,

  • The junit-vintage-engine is used only for executing JUnit 4 tests under the JUnit 5 framework.
  • Means, junit-vintage-engine is used to bridge the gap when you are using the latest version, JUnit 5, but still need to test some functionality that primarily relies on JUnit 4 or JUnit 3. In such cases, it allows you to run these older JUnit 3 and JUnit 4 tests alongside your new JUnit 5 tests within the same JUnit 5 test runner.
  • It is not needed for compilation but must be available when running tests.

Bonus

androidTestImplementation

Similar to testImplementation, but specifically for Android instrumentation tests that run on a device or emulator.

Kotlin
dependencies {
    androidTestImplementation("androidx.test.ext:junit:1.1.3")
}
  • The JUnit extension for Android testing is only available for instrumented tests (which run on an actual device or emulator).
  • Helps in isolating dependencies specific to Android UI tests.

annotationProcessor

Used for annotation processors that generate code at compile time.

Kotlin
dependencies {
    annotationProcessor("androidx.room:room-compiler:2.4.2")
}

// Note: Even though I defined it in a Kotlin file, it is actually in the Gradle file (build.gradle).  
// Also, annotationProcessor is mostly used for Java-based projects; its alternative in Kotlin is KAPT, 
// which we will see just after this.
  • Room’s compiler processes annotations at compile time and generates necessary database-related code.
  • Typically used with libraries like Dagger, Room, and ButterKnife.

kapt (Kotlin Annotation Processing Tool)

Since annotationProcessor does not work with Kotlin, we use kapt instead.

Kotlin
dependencies {
    kapt("androidx.room:room-compiler:2.4.2")
}
  • kapt handles annotation processing in Kotlin.
  • Required for libraries that rely on annotation processing.

Conclusion

Understanding Gradle dependency configurations is crucial for managing dependencies efficiently in Android development. By using the right configurations, you can improve build performance, maintain a modular project structure, and avoid unnecessary dependencies. By following best practices, you can make your Gradle build system cleaner and more maintainable.

With these insights and examples, you should now have a clear understanding of Gradle dependency configurations in Android and how to use them effectively!

Material 3

Android UI Themes with Material 3 Design: A Comprehensive Guide

Material 3 (also referred to as Material You) is Google’s latest design system that focuses on creating adaptable, personalized, and visually consistent interfaces. At its core, Material 3 introduces enhanced theming capabilities, allowing developers to craft user interfaces that dynamically adapt to the user’s preferences, device settings, and environments. In this blog, we will dive deep into Android UI themes in Material 3 Design, covering every aspect in detail to ensure you have all the tools you need to leverage it effectively.

What is a Theme in Android UI?

A theme in Android UI is a structured collection of design attributes that control the appearance and behavior of various UI elements across an app. Themes allow developers to define global styling rules that ensure consistency across the application.

In Material 3, a theme encompasses several key components:

  • Color System: Defines a harmonious set of colors.
  • Typography: Configures font styles and sizes.
  • Shapes: Controls the geometry of components like buttons, cards, and more.
  • Component Styles: Sets consistent appearances for Material components such as AppBars, Buttons, TextFields, and more.

Themes eliminate the need for repetitive styling, enabling you to focus on crafting intuitive and visually appealing interfaces.

Material 3 Themes: Key Features and Innovations

Material 3 brings several enhancements and innovations to theming, making it a major step forward from its predecessor, Material 2. Here are the key features of Material 3 themes:

1. Dynamic Color System (Material You)

Dynamic color is one of the most defining features of Material 3. It allows your app to automatically generate a harmonious color palette based on the user’s wallpaper or manually chosen colors. This creates a personalized and adaptive user experience.

Dynamic color utilizes the Monet color engine, which extracts colors from a source image (like a wallpaper) and maps them to Material color roles:

  • Primary: The app’s main color, used for prominent elements.
  • Secondary: A complementary color for accents.
  • Tertiary: Optional color for additional emphasis.
  • Surface: Background color for components like cards and menus.
  • Background: General background of the app.
  • On-Colors: Colors for text and icons displayed on top of colored surfaces (e.g., onPrimary, onSurface).

Dynamic color ensures your app’s UI feels cohesive with the rest of the Android ecosystem while offering users a unique and personalized interface.

2. Expanded Color Roles

Material 3 introduces an expanded set of color roles to give developers more control over how colors are applied:

  • PrimaryContainer and SecondaryContainer: Variants of primary and secondary colors for less prominent UI elements.
  • Error and onError: Colors for error states.
  • Outline: Colors for borders and outlines of components.

These additional roles enable more nuanced control over color application, improving accessibility and aesthetic quality.

3. Updated Typography System

Material 3 provides a refreshed typography system to improve readability and scalability across devices. Typography is categorized into three major groups:

  • Display Styles: DisplayLarge, DisplayMedium, DisplaySmall
  • Headline Styles: HeadlineLarge, HeadlineMedium, HeadlineSmall
  • Body Styles: BodyLarge, BodyMedium, BodySmall
  • Label Styles: LabelLarge, LabelMedium, LabelSmall

Each style is fine-tuned for its purpose, ensuring consistent text hierarchy and visual flow in your app.

4. Shape System

Material 3’s shape system defines the geometry of UI components, including their corners and elevations. It organizes shapes into three categories:

  • Small Components: Buttons, chips, and small elements.
  • Medium Components: Cards and modals.
  • Large Components: Containers and dialogs.

By customizing shapes, you can add a unique touch to your app’s UI.

Implementing Material 3 Themes in Android

Material 3 themes can be applied using either XML styles or Jetpack Compose. Let’s explore both approaches in detail.

1. Applying Themes Using XML

In traditional Android development, themes are defined in XML files. Here’s how you can set up a Material 3 theme:

Step 1: Update Dependencies Ensure you are using the latest Material 3 library:

Kotlin
implementation "com.google.android.material:material:1.12.0"

Step 2: Define the Theme in XML Create a themes.xml file in your res/values directory:

XML
<resources>
    <style name="Theme.MyApp" parent="Theme.Material3">
        <!-- Define color roles -->
        <item name="colorPrimary">@color/my_primary_color</item>
        <item name="colorSecondary">@color/my_secondary_color</item>

        <!-- Typography -->
        <item name="textAppearanceHeadline6">@style/TextAppearance.Material3.Headline6</item>

        <!-- Shapes -->
        <item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.Material3.Medium</item>
    </style>
</resources>

Step 3: Set the Theme in Manifest Apply the theme globally by referencing it in your AndroidManifest.xml:

Kotlin
<application
    android:theme="@style/Theme.MyApp">
</application>

2. Applying Themes Using Jetpack Compose

Jetpack Compose offers a modern, programmatic way to define themes.

Step 1: Add Material 3 Dependency Ensure you have the Compose Material 3 library in your project:

Kotlin
implementation "androidx.compose.material3:material3:1.3.1"

Step 2: Create a Compose Theme Define your custom theme in a Kotlin file:

Kotlin
@Composable
fun MyAppTheme(
    useDarkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (useDarkTheme) {
        dynamicDarkColorScheme(LocalContext.current)
    } else {
        dynamicLightColorScheme(LocalContext.current)
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

Step 3: Apply the Theme Wrap your app’s content in the theme:

Kotlin
@Composable
fun MyApp() {
    MyAppTheme {
        // App content goes here
    }
}

Best Practices for Material 3 Themes

  1. Leverage Dynamic Colors: Adopt Material You’s dynamic color capabilities for personalized user experiences.
  2. Ensure Accessibility: Use high-contrast colors for text and backgrounds to meet accessibility guidelines.
  3. Utilize Material Theme Builder: Test and preview your themes with the Material Theme Builder.
  4. Keep It Consistent: Maintain uniformity across all UI components to reinforce your brand identity.
  5. Test on Multiple Devices: Ensure your theme works seamlessly across different devices, orientations, and screen sizes.

Conclusion

Material 3 themes in Android provide powerful tools to design modern, adaptive, and user-centric applications. By embracing dynamic color, expanded color roles, updated typography, and a flexible shape system, you can create visually compelling and highly functional user interfaces that resonate with users.

Start integrating Material 3 themes today to elevate your app’s UI to the next level!

testImplementation in Gradle

Mastering testImplementation in Gradle: 3 Key Differences & Best Practices

Gradle provides different dependency configurations to manage libraries and dependencies for building and testing applications. When working with tests in a Gradle-based project, three key dependency configurations are commonly used:

  • testImplementation – Used for compiling and running tests.
  • testCompileOnly – Used only for compiling tests, not available at runtime.
  • testRuntimeOnly – Used only at test runtime, not needed for compilation.

Let’s explore each of these in detail with examples and understand when to use them.

testImplementation

testImplementation is used when you need a dependency for both compiling and running test cases. This is the most commonly used configuration for test dependencies.

Kotlin
// Adding a testing library in build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.13.2")
}
  • testImplementation ensures that the JUnit library is only available in the test environment.
  • It does not get bundled into the final application.

When to Use testImplementation

  • When the library is needed for both writing and executing tests.
  • Common for testing frameworks like JUnit, TestNG, and mocking libraries like Mockito.

testCompileOnly

testCompileOnly is used when a dependency is required only at test compile-time but is not needed at runtime. This is useful when a dependency provides compile-time annotations or APIs but doesn’t need to be included during test execution.

Kotlin
dependencies {
    testCompileOnly 'junit:junit:4.13.2'  // Available at compile-time but not included at runtime
}

This means,

  • The dependency is only available during compilation for unit tests.
  • It is not included in the test runtime classpath.
  • Use it when you need a library at compile time (e.g., compile-time annotations) but don’t want it in the runtime environment.

When to Use testCompileOnly

  • When a dependency provides compile-time features (such as annotation processing) but is not required at runtime.
  • To minimize the runtime classpath and avoid unnecessary dependencies.

testRuntimeOnly

testRuntimeOnly is used when a dependency is needed only at test runtime and is not required at compile-time.

Kotlin
dependencies {
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.9.1")
}

Here,

  • The junit-vintage-engine is used only for executing JUnit 4 tests under the JUnit 5 framework.
  • Means, junit-vintage-engine is used to bridge the gap when you are using the latest version, JUnit 5, but still need to test some functionality that primarily relies on JUnit 4 or JUnit 3. In such cases, it allows you to run these older JUnit 3 and JUnit 4 tests alongside your new JUnit 5 tests within the same JUnit 5 test runner.
  • It is not needed for compilation but must be available when running tests.

When to Use testRuntimeOnly

  • When a dependency is required only at runtime, such as database drivers, logging frameworks, or test execution engines.
  • When you want to keep the compile-time classpath clean and only include dependencies that are absolutely necessary for execution.

Conclusion

Understanding testImplementation, testCompileOnly, and testRuntimeOnly helps in optimizing test dependencies and ensuring efficient builds.

  • Use testImplementation for dependencies needed both at compile-time and runtime.
  • Use testCompileOnly for dependencies required only during compilation.
  • Use testRuntimeOnly for dependencies that are only needed when running tests.

By applying these configurations effectively, you can keep your test environment lightweight and efficient while avoiding unnecessary dependencies in the build process.

Compose Preview

Jetpack Compose Preview & Hilt Dependency Injection: Common Issues, Solutions, and Best Practices

In modern Android development, Jetpack Compose has simplified UI development, and Hilt has made dependency injection more streamlined. However, when working with Jetpack Compose Previews, you may encounter a common issue: Hilt dependency injection does not work in the context of Compose Previews.

In this detailed blog, we’ll break down the problem, explore why PreviewActivity doesn’t support Hilt, and show the best practices for managing dependencies during Compose Previews. We’ll also explore alternatives to using Hilt in previews while maintaining a smooth development experience.

What Are Compose Previews and Why Do We Use Them?

Before diving into the problem and solution, let’s first clarify what Compose Previews are and why they are useful:

  • Compose Previews allow developers to see their UI components directly in Android Studio without needing to run the entire app on a device or emulator.
  • Previews are a design-time tool for visualizing how your Composables will look under different states, layouts, and conditions.
  • The goal is to quickly iterate on your UI, test multiple configurations (like different themes, device sizes), and make changes to the UI without running the full app.

Compose Preview Limitations

While Compose Previews are powerful, they have some limitations:

  • Hilt Injection is Not Supported: By design, Hilt requires a dependency graph that is only created during runtime. Since Compose Previews are rendered before the app starts, there is no running application context where Hilt can inject dependencies.
  • No ViewModel Injection: Since Previews don’t have the full Android lifecycle, they also don’t support @HiltViewModel or other lifecycle-dependent mechanisms.

The Problem: PreviewActivity and Hilt

What is PreviewActivity?

  • PreviewActivity is a special activity used by Android Studio’s tooling to render Compose Previews.
  • It is part of the Compose Tooling and does not run as part of your actual application.
  • Since Hilt dependency injection relies on the app’s runtime environment to manage dependency graphs, and PreviewActivity does not have access to that runtime context, it cannot inject dependencies using Hilt.

Why the Error Occurs:

When you try to use Hilt in a preview and attempt to inject dependencies (such as ViewModels or services), Hilt encounters the following issue:

“Given component holder class androidx.compose.ui.tooling.PreviewActivity does not implement interface dagger.hilt.internal.GeneratedComponent

This error arises because PreviewActivity is not part of the app’s dependency graph, and Hilt cannot find any components to inject during preview rendering.

How to Handle Dependency Injection in Compose Previews

Now that we understand the problem, let’s look at the best practices for working around this limitation. Since Hilt cannot be used directly in Compose Previews, we will focus on methods that allow you to test your UI components effectively without Hilt.

Best Practice 1: Use Mock Dependencies

The most effective way to handle dependencies in Compose Previews is to use mock data or mock dependencies instead of relying on real Hilt dependencies. Since Compose Previews are for UI visualization and not real runtime behavior, mocking allows you to bypass the need for Hilt.

Mocking Dependencies in Previews

1. Create Mock Dependencies: For each service, ViewModel, or data source you need in your Composables, create a mock or simplified version of it.

Kotlin
class MockViewModel : ViewModel() {
    val sampleData = "Mock data for preview"
}

2. Use the Mock in Your Composable: When writing your Composables, pass the mocked data or services to the composable function.

Kotlin
@Composable
fun MyComposable(viewModel: MockViewModel) {
    Text(text = viewModel.sampleData)
}

3. Use @Preview with Mock Data: In the Preview function, instantiate the mock data directly.

Kotlin
@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable(viewModel = MockViewModel()) // Use mock data
}

Here,

  • Previews are meant for UI design, not for running real business logic or testing interactions.
  • By passing mock data, you can visualize the UI without needing real data or services.
  • This approach keeps previews lightweight and fast.

Best Practice 2: Use Default Arguments for Dependencies

Another approach is to use default arguments for dependencies in your Composables. This way, you can make sure that your Composables work both in the preview environment (with mock data) and in the app’s runtime (with Hilt-injected dependencies).

Default Arguments for Dependencies

1. Update Your Composables: Modify your Composables to use default arguments where appropriate.

Kotlin
@Composable
fun MyComposable(viewModel: MyViewModel = MockViewModel()) {
    Text(text = viewModel.sampleData)
}

2. Use @Preview with the Default Argument: In the Preview function, you don’t need to provide any dependencies explicitly because the MockViewModel will be used by default

Kotlin
@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable() // Use the default (mock) ViewModel
}

Here,

  • You can keep the same Composable for both Preview and Runtime by passing mock dependencies for previews.
  • In runtime, Hilt will inject the real ViewModel.

Best Practice 3: Use a Conditional DI Approach

If you are working with dependencies that are required for runtime but should be mocked in the preview, you can use a conditional DI approach where you check if you’re in the preview mode and inject mock data accordingly.

Kotlin
@Composable
fun MyComposable(viewModel: MyViewModel = if (BuildConfig.DEBUG) MockViewModel() else viewModel()) {
    Text(text = viewModel.sampleData)
}

@Preview
@Composable
fun MyComposablePreview() {
    MyComposable() // Will use MockViewModel in Preview
}

Best Practice 4: Avoid Hilt in Previews Entirely

Another strategy is to decouple your ViewModels or services from Hilt for the purposes of previews. This can be done by using interfaces or abstract classes for dependencies, which can then be mocked for preview.

Kotlin
interface DataProvider {
    fun getData(): String
}

class RealDataProvider : DataProvider {
    override fun getData(): String {
        return "Real Data"
    }
}

class MockDataProvider : DataProvider {
    override fun getData(): String {
        return "Mock Data for Preview"
    }
}

@Composable
fun MyComposable(dataProvider: DataProvider) {
    Text(text = dataProvider.getData())
}

@Preview(showBackground = true)
@Composable
fun MyComposablePreview() {
    MyComposable(dataProvider = MockDataProvider()) // Use mock provider for preview and for actual real provider
}

The last two approaches are quite self-explanatory, so I’ll skip further explanation and insights. Let’s directly jump to the final conclusion.

Conclusion

Hilt cannot be used in Jetpack Compose Previews because Previews don’t have access to the runtime dependency graph that Hilt creates. To work around this limitation, you can:

  1. Use Mock Dependencies: Simplify your Composables to accept mock data or services for previews.
  2. Use Default Arguments: Make your dependencies optional, allowing mock data to be injected in previews.
  3. Conditional Dependency Injection: Use a flag to determine whether to use mock data or real dependencies.
  4. Decouple Hilt Dependencies: Abstract dependencies behind interfaces so they can be easily mocked during previews.

By following these best practices, you can effectively handle dependencies in Compose Previews without running into issues with Hilt.

happy UI composeing..!

error: Content is protected !!