Android

How to Fix Wrong Git Commits in Android Studio and Safely Remove Unwanted Remote Commits

How to Fix Wrong Git Commits in Android Studio and Safely Remove Unwanted Remote Commits

If you’ve ever opened GitHub and noticed the wrong name on your commits — or spotted a commit on your main branch that never should’ve been there. These are some of the most common (and stressful) Git problems developers run into, especially when working in teams or switching between multiple accounts.

The good news is this: Git gives you the tools to fix both issues cleanly. The bad news? Using the wrong command can make things worse if you don’t understand what’s really going on.

This guide walks you through:

  • Why Android Studio shows the wrong Git user
  • How to correctly change the Git author (without breaking anything)
  • How to fix commits that are already pushed
  • When to remove commits entirely vs safely reverting them
  • Best practices to avoid these problems in the future

Everything here is based on how Git actually works under the hood — not IDE myths.

Why Android Studio Shows the Wrong Git User

Let’s clear up the most common misunderstanding first:

Android Studio does not control your Git commit author. Git does.

Android Studio is just a client. When you click “Commit,” it asks Git two questions:

  • What is the author name?
  • What is the author email?

Git answers based on its configuration files. That’s it.

So changing your GitHub account inside Android Studio affects authentication (push and pull permissions), but it does not change the commit author. That’s why this issue keeps coming back.

How Git Decides Who You Are

Git identifies authors using two values:

  • user.name
  • user.email

These can exist at two levels:

  1. Global — applies to all repositories on your machine
  2. Local (project-specific) — applies only to the current repository

Git always prefers local settings over global ones.

The Correct Way to Change Git User in Android Studio (Recommended)

If you work on multiple projects or use more than one GitHub account, this is the safest approach.

Step 1: Open the Terminal in Android Studio

Open your project, then click Terminal at the bottom.

Step 2: Set the Git user for this project only

Bash
git config user.name "Your Correct Name"<br>git config user.email "[email protected]"

Step 3: Verify

Bash
git config user.name<br>git config user.email

From this point forward, all new commits in this project will use the correct author.

Changing Git User Globally (Use With Caution)

If every repository on your machine is using the wrong user, update the global config:

Bash
git config --global user.name "Your Name"<br>git config --global user.email "[email protected]"

Verify with:

Bash
git config --global --lista

This affects all Git repositories on your system.

Why Switching GitHub Accounts Doesn’t Fix Commit Names

Android Studio’s GitHub settings only control:

  • Authentication
  • Push and pull permissions

They do not control commit authorship. That’s why you can push to one account while commits show another name entirely.

SSH Keys: Why Pushes Go to the Wrong Account

If you’re using SSH, GitHub identifies you by your SSH key, not your username.

Check which account your machine is using:

If GitHub responds with the wrong username, your SSH key is attached to the wrong account.

The Correct Fix

  • Generate a new SSH key
  • Add it to the correct GitHub account
  • Configure ~/.ssh/config to explicitly use that key

This ensures commits and permissions align correctly.

Why Existing Commits Don’t Update Automatically

Changing Git config only affects future commits.

Once a commit exists:

  • Its author is permanent
  • It cannot be changed unless history is rewritten

That’s intentional. Git values integrity over convenience.

How to Fix the Author of the Last Commit

If the most recent commit has the wrong author:

Bash
git commit --amend --author="Your Name <[email protected]>"<br>git push --force-with-lease

Only do this if rewriting history is acceptable.

Fixing Multiple Commits With the Wrong Author

Use interactive rebase:

Bash
git rebase -i HEAD~N

Change pick to edit for the commits you want to fix, then run:

Bash
git commit --amend --author="Your Name <[email protected]>"<br>git rebase --continue

Finish with:

Bash
git push --force-with-lease

Removing an Unwanted Commit From a Remote Repository

This is where many developers panic. The right solution depends on who else is affected.

Option 1: Hard Reset (Deletes History)

Use this only if:

  • It’s your personal branch, or
  • The commit contains sensitive data

Steps

Bash
git log --oneline<br>git reset --hard <last-good-commit><br>git push origin <branch> --force-with-lease

Everyone else must reset their local branch afterward.

Option 2: Revert (Safest for Teams)

If the branch is shared or protected, always revert.

Bash
git revert <bad-commit-hash><br>git push origin <branch>

This creates a new commit that undoes the change without rewriting history.

When to Use Reset vs Revert

SituationRecommended
Secrets pushedReset + force push
Mistake on mainRevert
Cleaning your feature branchReset
Team already pulledRevert

Removing Multiple Commits Cleanly (Interactive Rebase)

For mixed good and bad commits:

Bash
git rebase -i HEAD~5

Change pick to drop for unwanted commits, then:

Bash
git push --force-with-lease

Always create a backup branch first:

git branch backup-before-cleanup

Common Mistakes to Avoid

  • Force-pushing without warning your team
  • Using --force instead of --force-with-lease
  • Rewriting history on protected branches
  • Forgetting to back up before rebasing

If something goes wrong, git reflog can usually save you.

Best Practices to Prevent These Issues

  • Always verify Git user before starting a project
  • Prefer project-level Git config
  • Use separate SSH keys for different accounts
  • Protect main branches with PR rules
  • Never force-push without coordination
  • Keep commit emails matching your GitHub account

Conclusion

Android Studio often gets blamed for Git problems it doesn’t actually control. Once you understand that Git owns identity and history, everything becomes easier to reason about — and fix.

By setting the correct Git user, managing SSH keys properly, and choosing the right strategy for removing commits, you can keep your repositories clean without disrupting your team.

If you treat Git history with intention instead of panic, it becomes a powerful tool instead of a constant source of stress.

DirectoryLock Process Still Running

Fixing the “DirectoryLock / Process Still Running” Error in Android Studio Across Windows, macOS, and Linux

If Android Studio refuses to start and throws an error like:

Bash
Internal error<br>com.intellij.platform.ide.bootstrap.DirectoryLock$CannotActivateException: Process "studio64.exe" is still running and does not respond.

…you’re dealing with one of the most common (and confusing) Android Studio startup issues.

It usually appears after a crash, a forced shutdown, or reopening the IDE too quickly. The good news is that this error does not mean your installation is broken. It’s a safety mechanism that fails to recover properly in real-world conditions.

This guide explains what’s actually happening under the hood, why it shows up more often on Windows, how it behaves on macOS and Linux, and how to fix — and prevent — it permanently.

What This Error Really Means

Android Studio is built on the JetBrains IntelliJ Platform, which enforces a single-instance lock.

When Android Studio starts, it does three important things:

  1. Creates a lock file in its system directory
  2. Binds to a local socket (internal communication port)
  3. Registers itself as the active IDE instance

When you close Android Studio normally, all of that is cleaned up.

But if the IDE:

  • crashes,
  • is force-killed,
  • is interrupted by sleep or shutdown,
  • or hangs during indexing or Gradle sync,

those locks don’t always get released.

When you try to reopen Android Studio, it checks for those locks, assumes another instance is still running, and blocks startup to protect your data.

That’s when you see errors like:

  • DirectoryLock$CannotActivateException
  • Process is still running and does not respond
  • Address already in use
  • Connection refused

This behavior is intentional — but the recovery is imperfect.

Is this a particular OS problem?

No.
 This error is cross-platform and occurs on Windows, macOS, and Linux.

That said, Windows developers see it far more often, and there are clear technical reasons for that.

Why Windows Is Affected the Most

1. Fast Startup (Hybrid Shutdown) — Windows Fast Startup doesn’t fully terminate background processes. If Android Studio was open during shutdown, its locks may survive the reboot.

2. Aggressive File Locking — Windows uses mandatory file locks. If the IDE freezes or crashes, those locks are more likely to be left behind.

3. Antivirus & Defender Interference — Real-time scanners can delay or block the IDE’s socket binding, triggering false “already running” states.

4. Zombie Java Processes — If studio64.exe or its Java runtime crashes, Windows may leave it running invisibly in the background.

Result:
 Windows users encounter this error far more frequently than macOS or Linux users.

macOS: Less Common, Still Possible

macOS handles process cleanup more gracefully, which reduces the frequency — but doesn’t eliminate it.

Common macOS triggers include:

  • Force-quitting Android Studio
  • System sleep or sudden power loss
  • Corrupted IDE cache after a crash
  • Multiple user sessions sharing the same home directory

Because macOS is Unix-based, socket cleanup is usually reliable, but stale lock files can still remain after abnormal exits.

Linux: Rare, But Not Impossible

Linux is the least affected platform.

When the error appears, it’s usually caused by:

  • Killing the IDE with kill -9
  • Running Android Studio under different users
  • Permission issues in .config or .cache
  • Snap or Flatpak sandbox limitations

Linux aggressively cleans up sockets and file locks, which is why this issue is relatively rare there.

Real-World OS Comparison

Operating SystemLikelihoodOverall Stability
WindowsHighMost problematic
macOSMediumMostly stable
LinuxLowMost reliable

Step-by-Step Fix (Works on Any OS)

1. Kill the Stuck Process (Most Common Fix)

Windows

  • Open Task Manager → Details
  • End studio64.exe or java.exe

macOS

  • Open Activity Monitor
  • Force quit Android Studio or Java

Linux

Bash
pkill -f android-studio

If the error shows a PID, target that process directly.

2. Delete Leftover Lock Files

Sometimes the process is gone, but the lock file remains.

Windows

Bash
C:\Users\<User>\AppData\Local\Google\AndroidStudio<version>/

macOS

Bash
~/Library/Caches/Google/AndroidStudio<version>/

Linux

Bash
~/.cache/Google/AndroidStudio<version>/

Delete files such as:

  • port.lock
  • .lock
  • any *.lock files

Then restart Android Studio.

3. “Address Already in Use” Errors

If you see BindException or Address already in use, the fastest fix is a full system reboot.
 This clears all sockets and zombie processes instantly.

Why Android Studio Is Designed This Way

This behavior isn’t unique to Android Studio.

All JetBrains IDEs (IntelliJ IDEA, PyCharm, WebStorm) use the same architecture. The IDE prioritizes data safety over convenience:

  • It blocks startup if another instance is detected
  • It prevents concurrent access to system directories
  • It assumes clean shutdowns

The problem isn’t the design — it’s that real machines crash, sleep, and lose power.

How to Prevent This Issue Long-Term

Best Practices (All OS)

  • Always close Android Studio normally
  • Avoid force-killing unless absolutely necessary
  • Wait a few seconds before reopening after closing
  • Keep only one instance per project
  • Stay on the latest stable Android Studio release

Windows-Specific Tips

  • Disable Fast Startup
  • Add Android Studio folders to antivirus exclusions
  • Clean IDE cache directories occasionally

macOS & Linux Tips

  • Avoid force-quit
  • Check directory permissions
  • Don’t mix system installs with Snap/Flatpak
  • Kill leftover processes before relaunching

Conclusion

This error is not your fault, and it’s not a sign of a broken setup.

It happens because of:

  • How Android Studio manages single-instance locks
  • How operating systems handle crashes and shutdowns
  • Edge cases the IDE still doesn’t recover from perfectly

Once you understand what’s going on, fixing it takes minutes — and preventing it becomes easy.

If you work with Android Studio long enough, you’ll see this error at least once. Now you know exactly what to do when it happens.

ViewModel and rememberSaveable

The Truth About ViewModel and rememberSavable: Configuration Changes vs Process Death

If you’ve built Android apps with Jetpack Compose, you’ve probably run into the question: Should I use ViewModel or rememberSaveable? Both help you keep state alive, but they work very differently depending on what’s happening to your app — like when the screen rotates or when the system kills your process.

This post will break down ViewModel and rememberSaveable, explain when to use each, and show real code examples so it finally clicks.

The Basics: Why State Preservation Matters

On Android, your app doesn’t always stay alive. Two big events affect your app’s state:

  1. Configuration changes — like screen rotations, language changes, or switching dark mode. The activity is destroyed and recreated, but the process usually stays alive.
  2. Process death — when Android kills your app’s process (e.g., to reclaim memory) and later restores it when the user comes back.

If you don’t handle these correctly, your users lose whatever they were doing. That’s where ViewModel and rememberSaveable come in.

remember: The Starting Point in Compose

At the simplest level, you use remember in Jetpack Compose to keep state alive across recompositions.

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

    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}
  • Here, count won’t reset when Compose redraws the UI.
  • But if the device rotates (configuration change), the state is lost because remember only survives recompositions, not activity recreation.

That’s why we need more powerful tools.

rememberSaveable: Survives Configuration Changes and Process Death

rememberSaveable goes one step further. It automatically saves your state into a Bundle using Android’s saved instance state mechanism.

Kotlin
@Composable
fun CounterScreen() {
    var count by rememberSaveable { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("Count: $count")
    }
}

What happens here:

  • Rotate the screen? count survives.
  • App is killed and restored (process death)? count also survives, because it was written to the saved instance state.

Limitations:

  • Only works with data types that can be written to a Bundle (primitives, Strings, parcelables, etc.).
  • Not ideal for large objects or data fetched from a repository.

ViewModel: Survives Configuration Changes, Not Process Death

A ViewModel is a lifecycle-aware container designed to hold UI data. It’s tied to a LifecycleOwner like an activity or a navigation back stack entry.

Kotlin
class CounterViewModel : ViewModel() {
    var count by mutableStateOf(0)
}

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

What happens here:

  • Rotate the screen? count survives. The same ViewModel instance is reused.
  • App is killed (process death)? count is lost. The ViewModel does not persist beyond process death.

Configuration Changes vs Process Death: Who Wins?

Here’s the clear breakdown:

When to Use rememberSaveable

Use rememberSaveable for small, lightweight UI state that:

  • Must survive both rotation and process death.
  • Can easily be serialized into a Bundle.

Examples:

  • Current tab index.
  • Form text fields.
  • Simple filter/sort options.

When to Use ViewModel

Use ViewModel for more complex or long-lived state that:

  • Doesn’t need to survive process death.
  • Might involve business logic, repositories, or data streams.
  • Should be scoped to the screen or navigation graph.

Examples:

  • Data loaded from a database or network.
  • Complex business logic.
  • State shared across multiple composables in the same screen.

Can You Combine Them? Yes.

Often, the best solution is to use ViewModel and rememberSaveable together.
 For example, a ViewModel manages your main UI state, but a few critical fields use rememberSaveable so they’re restored even after process death.

Kotlin
@Composable
fun FormScreen(viewModel: FormViewModel = viewModel()) {
    var userInput by rememberSaveable { mutableStateOf("") }

    Column {
        TextField(
            value = userInput,
            onValueChange = { userInput = it }
        )

        Button(onClick = { viewModel.submit(userInput) }) {
            Text("Submit")
        }
    }
}

Here:

  • userInput is lightweight and saved with rememberSaveable.
  • The ViewModel takes care of processing and persisting the submitted data.

Conclusion

The truth about ViewModel and rememberSaveable is simple once you think in terms of configuration changes vs process death:

  • remember → Only survives recomposition.
  • rememberSaveable → Survives both rotation and process death (small, serializable state).
  • ViewModel → Survives rotation, great for business logic, but not process death.

Use them in combination, not competition. Each tool has its place, and knowing when to reach for which makes your Compose apps more resilient, smoother, and user-friendly.

CompositionLocal

CompositionLocal Deep Dive: Writing Scalable UI in Jetpack Compose

When building Android apps with Jetpack Compose, you’ll often need to share data across your UI tree. Passing parameters down every Composable quickly becomes messy. That’s where CompositionLocal comes in.

Think of CompositionLocal as a smart way to provide values (like theme, locale, or user preferences) to multiple Composables without having to manually thread them through function parameters. It’s like dependency injection — but scoped to the Compose world.

In this post, we’ll explore how CompositionLocal works, why it matters for building scalable UI, and how you can use it effectively.

What is CompositionLocal?

CompositionLocal is a mechanism that allows you to define and access values that are automatically propagated down the Composable hierarchy.

  • It provides contextual values (like theme colors or configurations).
  • It removes the need to pass arguments everywhere.
  • It helps you scale UI architecture by keeping components decoupled.

Jetpack Compose already uses CompositionLocal under the hood for things like MaterialTheme, text styles, and layout direction.

Defining a CompositionLocal

You start by creating a CompositionLocal with a default value:

Kotlin
val LocalUser = compositionLocalOf<String> { 
    error("No user provided") 
}

Here:

  • compositionLocalOf creates a CompositionLocal with a default (or error if missing).
  • We’re saying: “If no user is provided, throw an error.”

Providing a Value

To inject a value, you use CompositionLocalProvider:

Kotlin
@Composable
fun AppContent() {
    CompositionLocalProvider(LocalUser provides "amol pawar") {
        UserProfile()
    }
}

Inside AppContent, any child Composable can access LocalUser.

Consuming a CompositionLocal

To read the value, use .current:

Kotlin
@Composable
fun Dashboard() {
    Column {
        CompositionLocalProvider(LocalUser provides "akshay") {
            UserProfile() // shows "Hello, askhay!"
        }
        UserProfile() // shows "Hello, amol pawar!"
    }
}

Output:

Hello, amol pawar!

No need to pass user down as a parameter—CompositionLocal handles it.

Why Use CompositionLocal?

Let’s break it down with a practical example. Imagine a large app with:

  • Theme data (colors, typography).
  • User session info.
  • App settings like dark mode, locale, etc.

Passing these manually would be a nightmare. With CompositionLocal, you define them once and let the UI tree consume them where needed.

Scoped Values for Flexibility

One powerful feature is scoping. You can override a CompositionLocal in a subtree without affecting the rest of the app.

Kotlin
@Composable
fun Dashboard() {
    Column {
        CompositionLocalProvider(LocalUser provides "akshay") {
            UserProfile() // shows "Hello, askhay!"
        }
        UserProfile() // shows "Hello, amol pawar!"
    }
}

The value depends on where the Composable is in the hierarchy. This makes it perfect for context-specific overrides (like previewing different themes).

Best Practices for CompositionLocal

  1. Don’t abuse it. Use CompositionLocal for global or contextual data, not just to avoid passing parameters.
  2. Keep defaults meaningful. Provide safe defaults or throw an error if the value is critical.
  3. Use for ambient context. Theme, locale, user, system settings — these are ideal use cases.
  4. Avoid hidden dependencies. If a Composable always needs a value, prefer explicit parameters for clarity.

Theme System with CompositionLocal

Let’s create a mini theme system:

Kotlin
data class MyColors(val primary: Color, val background: Color)

val LocalColors = staticCompositionLocalOf<MyColors> {
    error("No colors provided")
}

@Composable
fun MyTheme(content: @Composable () -> Unit) {
    val colors = MyColors(primary = Color.Blue, background = Color.White)
    CompositionLocalProvider(LocalColors provides colors) {
        content()
    }
}

@Composable
fun ThemedButton() {
    val colors = LocalColors.current
    Button(onClick = {}) {
        Text("Click Me", color = colors.primary)
    }
}

Usage:

Kotlin
@Composable
fun App() {
    MyTheme {
        ThemedButton()
    }

Here, ThemedButton gets its styling from LocalColors without needing parameters.

CompositionLocal vs Parameters

  • Use parameters when data is essential to the Composable.
  • Use CompositionLocal when data is contextual, like theming or configuration.

This balance keeps your UI scalable and maintainable.

Conclusion

CompositionLocal is one of the most powerful tools in Jetpack Compose for writing scalable UI. It keeps your code cleaner, reduces boilerplate, and makes context handling a breeze.

By using CompositionLocal wisely, you can:

  • Share contextual data easily
  • Override values locally
  • Keep UI components decoupled and reusable

Next time you’re passing a value through five different Composables, stop and ask yourself — could CompositionLocal handle this better?

Scrollable Sticky Table/Grid UIs in Jetpack Compose

Build Scrollable Sticky Table/Grid UIs in Jetpack Compose Like a Pro

If you’ve ever built dashboards, spreadsheets, or financial apps, you know how important tables are. But a plain table isn’t enough — you often need a Scrollable Sticky Table/Grid where headers stay in place while data scrolls smoothly.

In the past, building this in Android XML layouts was painful. With Jetpack Compose, you can achieve it with clean Kotlin code, no hacks, and full flexibility.

In this guide, we’ll walk through how to build a professional Scrollable Sticky Table/Grid in Jetpack Compose — from the basics to a fully data-driven version that adapts to any dataset.

Why Scrollable Sticky Tables Matter

A Scrollable Sticky Table/Grid is more than eye candy. It solves real usability problems:

  • Sticky headers: Keep column labels visible while scrolling.
  • Row headers: Let users track rows without losing context.
  • Independent scrolls: Row IDs can scroll separately from the table, making it easier to navigate large datasets.
  • Dynamic structure: Tables should adapt to n rows and m columns—no hardcoding.

Think Google Sheets, Excel, or analytics dashboards. The same principles apply here.

A Basic Scrollable Table

Let’s start with the simplest version: rows and columns that scroll.

Kotlin
@Composable
fun ScrollableGridDemo() {
    val rowCount = 20
    val columnCount = 10

    LazyColumn {
        items(rowCount) { rowIndex ->
            Row(
                modifier = Modifier
                    .horizontalScroll(rememberScrollState())
            ) {
                repeat(columnCount) { colIndex ->
                    Box(
                        modifier = Modifier
                            .size(100.dp)
                            .border(1.dp, Color.Gray)
                            .padding(8.dp),
                        contentAlignment = Alignment.Center
                    ) {
                        Text("R$rowIndex C$colIndex")
                    }
                }
            }
        }
    }
}

This gives you a grid that scrolls vertically and horizontally. But headers vanish when you scroll.

Adding Sticky Column Headers

With stickyHeader, we can lock the top row.

Kotlin
@Composable
fun ScrollableStickyTable() {
    val rowCount = 20
    val columnCount = 10

    LazyColumn {
        // Sticky Header Row
        stickyHeader {
            Row(
                modifier = Modifier
                    .background(Color.LightGray)
                    .horizontalScroll(rememberScrollState())
            ) {
                repeat(columnCount) { colIndex ->
                    Box(
                        modifier = Modifier
                            .size(100.dp)
                            .border(1.dp, Color.Black)
                            .padding(8.dp),
                        contentAlignment = Alignment.Center
                    ) {
                        Text("Header $colIndex", fontWeight = FontWeight.Bold)
                    }
                }
            }
        }

        // Table Rows
        items(rowCount) { rowIndex ->
            Row(
                modifier = Modifier
                    .horizontalScroll(rememberScrollState())
            ) {
                repeat(columnCount) { colIndex ->
                    Box(
                        modifier = Modifier
                            .size(100.dp)
                            .border(1.dp, Color.Gray)
                            .padding(8.dp),
                        contentAlignment = Alignment.Center
                    ) {
                        Text("R$rowIndex C$colIndex")
                    }
                }
            }
        }
    }
}

Headers now stick to the top as you scroll.

Adding Row Headers

Sometimes you need a first column (row IDs) that doesn’t disappear when you scroll horizontally. The trick is to split the table into two sections:

  • Row header column → vertical scroll only.
  • Main table → vertical + horizontal scroll.

And make them sit side by side.

Making It Dynamic (n Rows, m Columns)

Hardcoding row and column counts isn’t practical. Let’s build a reusable, data-driven composable.

Kotlin
@Composable
fun DataDrivenStickyTable(
    rowHeaders: List<String>,            // Row labels
    columnHeaders: List<String>,         // Column labels
    tableData: List<List<String>>        // 2D grid of values [row][col]
) {
    Row {
        // Row Header Column
        LazyColumn(
            modifier = Modifier.width(100.dp)
        ) {
            // Sticky Row Header Title
            stickyHeader {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                        .background(Color.DarkGray)
                        .border(1.dp, Color.Black),
                    contentAlignment = Alignment.Center
                ) {
                    Text("Row#", color = Color.White, fontWeight = FontWeight.Bold)
                }
            }

            // Dynamic Row Headers
            items(rowHeaders.size) { rowIndex ->
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                        .background(Color.Gray)
                        .border(1.dp, Color.Black),
                    contentAlignment = Alignment.Center
                ) {
                    Text(rowHeaders[rowIndex], fontWeight = FontWeight.Medium)
                }
            }
        }

        // Main Table
        LazyColumn(
            modifier = Modifier.weight(1f)
        ) {
            // Sticky Column Headers
            stickyHeader {
                Row(
                    modifier = Modifier
                        .horizontalScroll(rememberScrollState())
                        .background(Color.LightGray)
                ) {
                    columnHeaders.forEach { header ->
                        Box(
                            modifier = Modifier
                                .size(width = 100.dp, height = 50.dp)
                                .border(1.dp, Color.Black),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(header, fontWeight = FontWeight.Bold)
                        }
                    }
                }
            }

            // Dynamic Rows
            items(rowHeaders.size) { rowIndex ->
                Row(
                    modifier = Modifier
                        .horizontalScroll(rememberScrollState())
                ) {
                    tableData[rowIndex].forEach { cell ->
                        Box(
                            modifier = Modifier
                                .size(width = 100.dp, height = 50.dp)
                                .border(1.dp, Color.LightGray),
                            contentAlignment = Alignment.Center
                        ) {
                            Text(cell)
                        }
                    }
                }
            }
        }
    }
}

You can generate as many rows and columns as you want dynamically:

Kotlin
@Composable
fun TableDemo() {
    val rowHeaders = List(20) { "Row $it" }
    val columnHeaders = List(10) { "Col $it" }
    val tableData = List(rowHeaders.size) { rowIndex ->
        List(columnHeaders.size) { colIndex ->
            "R$rowIndex C$colIndex"
        }
    }

    DataDrivenStickyTable(
        rowHeaders = rowHeaders,
        columnHeaders = columnHeaders,
        tableData = tableData
    )
}

Now your table works with any dataset — whether it’s 5×5 or 100×100.

Check out the complete project on my GitHub repository

Performance Tips

  • Use LazyColumn + LazyRow for large datasets—they recycle views efficiently.
  • If your dataset is small, you can simplify with Column + Row.
  • Use rememberLazyListState() and rememberScrollState() if you need to sync scrolling between row headers and table content.

Conclusion

With Jetpack Compose, building a Scrollable Sticky Table/Grid is no longer a headache. You can:

  • Show sticky headers for both rows and columns.
  • Keep row headers independent or sync them with the table.
  • Dynamically generate n rows and m columns from real datasets.

This approach is clean, scalable, and production-ready. The next time you need a spreadsheet-like UI, you’ll know exactly how to do it — like a pro.

Jetpack Compose LazyColumn Sticky Header

Jetpack Compose LazyColumn Sticky Header: Complete Implementation Guide

When you’re building long lists in Jetpack Compose, sometimes you need certain sections to stand out and stay visible while scrolling. That’s exactly where Sticky Header comes in. Imagine scrolling through a contacts app — the alphabet letter headers (A, B, C…) stick at the top while you browse through names. Jetpack Compose makes this easy with LazyColumn and stickyHeader.

In this guide, I’ll walk you through how to implement Sticky Header in Jetpack Compose with clear explanations.

What is a Sticky Header?

A Sticky Header is a UI element that “sticks” at the top of a scrollable list until the next header pushes it off. It’s commonly used in:

  • Contact lists
  • Calendar apps
  • Shopping category lists
  • News feeds with date separators

This improves navigation and makes large lists easier to scan.

Why Use Sticky Header in Jetpack Compose?

With Jetpack Compose, you don’t need RecyclerView adapters or complex custom views. The LazyColumn component handles large, scrollable lists efficiently, and stickyHeader makes adding sticky sections straightforward.

Benefits:

  • Simple syntax, no XML layouts.
  • Clean and declarative code.
  • Works seamlessly with Compose state management.

LazyColumn and stickyHeader Basics

Here’s the basic structure of a LazyColumn with a Sticky Header:

Kotlin
@Composable
fun StickyHeaderExample() {
    val sections = listOf(
        "Fruits" to listOf("Apple", "Banana", "Orange"),
        "Vegetables" to listOf("Carrot", "Potato", "Tomato"),
        "Dairy" to listOf("Milk", "Cheese", "Yogurt")
    )

    LazyColumn {
        sections.forEach { (header, items) ->
            stickyHeader {
                Text(
                    text = header,
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.LightGray)
                        .padding(8.dp),
                    style = MaterialTheme.typography.subtitle1
                )
            }

            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(8.dp)
                )
            }
        }
    }
}

Let’s break it down:

Data Setup

Kotlin
val sections = listOf(
    "Fruits" to listOf("Apple", "Banana", "Orange"),
    "Vegetables" to listOf("Carrot", "Potato", "Tomato"),
    "Dairy" to listOf("Milk", "Cheese", "Yogurt")
)

Here, each section has a header (like “Fruits”) and a list of items.

LazyColumn

Kotlin
LazyColumn { ... }

Displays the entire list efficiently. Only visible items are composed, so it’s memory-friendly.

stickyHeader

Kotlin
stickyHeader {
    Text(
        text = header,
        modifier = Modifier
            .fillMaxWidth()
            .background(Color.LightGray)
            .padding(8.dp)
    )
}

This is the star of the show. The header stays pinned at the top while scrolling through its section.

items()

Kotlin
items(items) { item -> ... }

Renders each element under the sticky header.

Customizing Sticky Headers

You can style sticky headers to fit your app’s design. For example:

  • Add icons to headers.
  • Change background color based on section.
  • Apply elevation or shadows for better separation.

Example with custom styling:

Kotlin
stickyHeader {
    Surface(
        color = Color.DarkGray,
        shadowElevation = 4.dp
    ) {
        Text(
            text = header,
            modifier = Modifier
                .fillMaxWidth()
                .padding(12.dp),
            color = Color.White,
            style = MaterialTheme.typography.h6
        )
    }
}

When to Use and When Not To

Use Sticky Header when:

  • The list is grouped (categories, dates, sections).
  • Users need quick context while scrolling.

Avoid Sticky Header if:

  • The list is flat (no categories).
  • Too many headers clutter the UI.

Performance Considerations

LazyColumn with stickyHeader is optimized, but keep these in mind:

  • Keep headers lightweight (avoid heavy Composables inside).
  • Reuse stateful items outside of the list when possible.
  • Test on lower-end devices if you have very large datasets.

Conclusion

The Sticky Header in Jetpack Compose makes complex, sectioned lists much easier to build and navigate. With just a few lines of code inside a LazyColumn, you can create polished, user-friendly experiences without dealing with RecyclerView boilerplate.

If you’re building apps with grouped data — contacts, shopping categories, or event timelines — Sticky Header is a feature you’ll definitely want to use.

Sticky Header in Jetpack Compose

How to Create a Sticky Header in Jetpack Compose

If you’ve ever scrolled through a long list in an app and noticed that the section title stays pinned at the top until the next section appears, you’ve seen a sticky header. Sticky headers make lists easier to navigate, especially when content is grouped by category.

In this post, we’ll learn step by step how to implement a Sticky Header in Jetpack Compose using LazyColumn. Don’t worry if you’re just getting started with Compose—the explanation will stay simple, with code examples and clear breakdowns.

What is a Sticky Header?

A sticky header is a UI element that remains visible at the top of a scrolling list while the content beneath it scrolls. It’s often used in apps like Contacts (where the alphabet letters stick as you scroll) or e-commerce apps (where categories like “Shoes,” “Bags,” or “Clothing” stay pinned).

Jetpack Compose makes this much easier to implement compared to the old RecyclerView approach in XML.

Why Use Sticky Headers in Jetpack Compose?

Adding a Sticky Header in Jetpack Compose improves:

  • Readability: Users instantly know which section they’re in.
  • Navigation: Helps users scan through grouped content quickly.
  • User Experience: Feels modern, polished, and professional.

The Key Composable: stickyHeader

Jetpack Compose provides a built-in modifier inside LazyColumn called stickyHeader. This allows you to define a composable item that “sticks” to the top while scrolling.

Basic Code Example

Here’s a simple example of creating a Sticky Header in Jetpack Compose:

Kotlin
@Composable
fun StickyHeaderList() {
    val groupedItems = mapOf(
        "Fruits" to listOf("Apple", "Banana", "Mango", "Orange"),
        "Vegetables" to listOf("Carrot", "Potato", "Tomato"),
        "Drinks" to listOf("Water", "Juice", "Soda")
    )

    LazyColumn {
        groupedItems.forEach { (header, items) ->
            stickyHeader {
                Text(
                    text = header,
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(Color.LightGray)
                        .padding(16.dp),
                    fontWeight = FontWeight.Bold
                )
            }

            items(items) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(12.dp)
                )
            }
        }
    }
}

Let’s break it down so it’s crystal clear:

Grouped Data

  • We created a Map with categories as keys ("Fruits", "Vegetables", "Drinks") and a list of items under each.

LazyColumn

  • Works like a RecyclerView but in Compose. It’s efficient for large lists.

stickyHeader

  • This is the magic. Whatever you put inside stickyHeader will remain stuck at the top until another header replaces it.
  • We used a Text with background color and padding so it looks like a section header.

items()

  • Displays each element in the list under its header.

Styling the Sticky Header

You don’t want your sticky header to look boring. Here are a few tweaks you can add:

Kotlin
stickyHeader {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        color = Color.DarkGray,
        shadowElevation = 4.dp
    ) {
        Text(
            text = header,
            modifier = Modifier.padding(16.dp),
            color = Color.White,
            fontSize = 18.sp,
            fontWeight = FontWeight.Bold
        )
    }
}

This adds:

  • Background color (DarkGray)
  • Shadow elevation for depth
  • White text for contrast

When to Use Sticky Headers

Sticky headers are perfect for:

  • Contact lists grouped alphabetically
  • Shopping apps with categories
  • News apps with sections (e.g., Sports, Tech, Business)
  • Music playlists grouped by artist or album

Common Mistakes to Avoid

  • Too many sticky headers: Don’t overuse them — it can feel cluttered.
  • No visual distinction: Make sure headers look different from list items.
  • Performance issues: For extremely large datasets, consider lazy loading.

Conclusion

Creating a Sticky Header in Jetpack Compose is simple, thanks to the stickyHeader API inside LazyColumn. With just a few lines of code, you can build a smooth, user-friendly list that looks polished and professional.

As Compose continues to evolve, features like these make UI development faster, cleaner, and more intuitive. Whether you’re building a contacts app, a shopping app, or just experimenting, sticky headers will give your lists a better structure and improve the user experience.

Pro Tip: Always test on different screen sizes to make sure your headers remain clear and readable.

Now it’s your turn — try adding a sticky header to your own Jetpack Compose project and see the difference!

what is JOSE

What Is JOSE and Why It Matters for Financial Android Apps

In the age of mobile banking, digital wallets, and API-driven services, securing sensitive financial data is non-negotiable. Developers building financial Android applications face strict regulatory requirements and high user expectations for privacy and trust. One of the most widely adopted frameworks for securing JSON-based data exchanges is JOSE (Javascript Object Signing and Encryption).

This article explains what JOSE is, why it matters for financial applications — especially on Android — and how developers can leverage its standards to build secure, compliant, and user-trusted apps.

What Is JOSE?

JOSE (Javascript Object Signing and Encryption) is a suite of standards defined by the IETF (Internet Engineering Task Force). It provides a structured and interoperable way to secure JSON data, making it especially relevant for APIs, microservices, and mobile applications.

The JOSE framework consists of several core components:

  • JWS (JSON Web Signature): Ensures data integrity and authenticity by digitally signing JSON objects.
  • JWE (JSON Web Encryption): Protects sensitive data through encryption.
  • JWK (JSON Web Key): A standardized format for representing cryptographic keys.
  • JWA (JSON Web Algorithms): Defines which algorithms can be used for signing and encryption.
  • JWT (JSON Web Token): A compact, URL-safe way to transmit claims (e.g., identity or permissions).

These standards work together to secure communication channels, enforce authentication, and maintain data confidentiality across distributed systems.

Why JOSE Is Crucial for Financial Android Apps

1. Regulatory Compliance

Financial institutions and fintech startups must comply with frameworks like PCI-DSS, PSD2, and GDPR. JOSE provides the encryption, signatures, and secure key management needed to meet these regulatory requirements.

2. End-to-End Security

Financial Android apps rely on constant communication between client devices and backend servers. With JOSE, data is encrypted and signed before leaving the device, ensuring it cannot be intercepted or tampered with in transit.

3. Enhanced User Trust

In financial services, trust is currency. Users are more likely to adopt and remain loyal to apps that demonstrate strong data protection. JOSE offers transparent, standards-based security that boosts user confidence.

Real-World Use Cases in Financial Android Apps

  • Transaction Security: Protect payment and transfer data using JWE encryption.
  • User Authentication: Verify sessions and identities with JWT tokens signed by JWS.
  • API Communication: Use JOSE standards to enforce secure server-to-server and client-to-server communication.
  • Mobile Wallets & Banking Apps: Secure card details, balances, and sensitive personal data.

Best Practices for Developers Implementing JOSE

  • Always use strong algorithms from JWA (e.g., RS256 or ES256 for signing).
  • Rotate and manage JSON Web Keys (JWKs) securely.
  • Avoid storing sensitive tokens in plaintext on the Android device — use Android Keystore.
  • Implement short-lived JWTs with refresh tokens for better session security.
  • Validate signatures and claims on both client and server sides.

Frequently Asked Questions (FAQ)

Q1: Is JOSE the same as JWT?
 No. JWT (JSON Web Token) is just one standard within the JOSE framework. JOSE includes multiple standards like JWS, JWE, JWK, and JWA.

Q2: Why should I use JOSE instead of just HTTPS?
 HTTPS secures communication at the transport layer, but JOSE secures the actual payload data, ensuring protection even if HTTPS is terminated at proxies or gateways.

Q3: Which algorithms are best for financial Android apps?
 For signing, RS256 (RSA with SHA-256) and ES256 (Elliptic Curve with SHA-256) are recommended. For encryption, AES-GCM is a strong choice.

Q4: Can JOSE help with PSD2 and Open Banking compliance?
 Yes. Many Open Banking APIs rely on JWTs for secure claims and signed requests, making JOSE central to compliance strategies.

Q5: How do I store JOSE keys on Android securely?
 Use the Android Keystore System, which protects private keys in hardware-backed storage.

Conclusion

For developers building financial Android apps, JOSE isn’t optional — it’s essential. By combining encryption, signing, and key management under a standardized framework, JOSE makes it easier to secure sensitive data, comply with financial regulations, and earn user trust.

Implementing JOSE correctly not only strengthens your app’s security posture but also positions your product as a trustworthy solution in a competitive financial market.

Gradle Version Catalog in Android

Gradle Version Catalog in Android: A Complete Guide

Managing dependencies efficiently is crucial for any Android project, and Gradle Version Catalog makes it much easier. It centralizes dependency versions in one place, simplifies updates, and enhances project maintainability.

In this blog, we’ll explore:

  • What is Gradle Version Catalog?
  • Why should we use it?
  • How to implement it in an Android project with clear, step-by-step explanations.

Let’s dive in!

What is Gradle Version Catalog?

Gradle Version Catalog is a feature introduced in Gradle 7.0 that allows you to manage all your dependencies in a structured and centralized way using a file called libs.versions.toml.

Traditionally, we define dependencies in build.gradle or build.gradle.kts like this:

Kotlin
dependencies {
    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
}

With Gradle Version Catalog, these versions are stored separately in a TOML file, making it easier to manage and update dependencies in large projects.

With Gradle Version Catalog

Dependencies are defined in gradle/libs.versions.toml:

Kotlin
[versions]
coreKtx = "1.7.0"
lifecycle = "2.4.0"

[libraries]
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }

And referenced in build.gradle.kts:

Kotlin
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.runtime)
}

This keeps your project clean and scalable.

Why We Use Gradle Version Catalog?

Centralized Dependency Management: All dependencies and versions are stored in one place (libs.versions.toml), making maintenance easier.

Better Readability: Instead of scattered version numbers across multiple build.gradle files, you have a single version catalog for better readability.

Avoid Version Conflicts: Using a centralized catalog reduces inconsistencies and version mismatches in different modules.

Improved Consistency: Ensures that all modules use the same dependency versions.

Reduced Duplication: No need to repeatedly define dependencies in different module files.

Easier Updates: Updating dependencies is simpler since you only change the version in one file, and it reflects everywhere in the project.

Support for Plugins: Can also be used to manage Gradle plugins efficiently.

How to Set Up Gradle Version Catalog in an Android Project

If you’re using Gradle 8+, Version Catalog is enabled by default. For older versions (Gradle 7+), follow these steps:

1. Enable the Version Catalog

Inside settings.gradle (or settings.gradle.kts):

Kotlin
enableFeaturePreview("VERSION_CATALOGS")

2. Create the Version Catalog File

Inside your project root, create gradle/libs.versions.toml.

3. Define Versions and Dependencies

Example libs.versions.toml:

Kotlin
[versions]
kotlin = "1.8.20"
coreKtx = "1.9.0"

[libraries]
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }

[plugins]
androidApplication = { id = "com.android.application", version = "8.0.0" }

4. Reference Dependencies in Build Scripts

Kotlin
plugins {
    id(libs.plugins.androidApplication.get().pluginId) version libs.plugins.androidApplication.get().version
    kotlin("android")
}

dependencies {
    implementation(libs.kotlin.stdlib)
    implementation(libs.androidx.core.ktx)
}

Best Practices for Using Gradle Version Catalog

  • Use version references instead of hardcoding values.
  • Group related dependencies logically in the TOML file.
  • Leverage aliases for clear naming conventions.
  • Keep libraries and plugins together for easier maintenance.
  • Regularly update dependencies via a single source of truth.

FAQs

Q1: What is the purpose of libs.versions.toml in Gradle?
 It centralizes all dependency versions in one place, making updates easier and preventing conflicts across modules.

Q2: Can Gradle Version Catalog manage plugins?
 Yes. You can declare both library dependencies and Gradle plugins in the TOML file.

Q3: Do I need Gradle 8 to use Version Catalog?
 No. It was introduced in Gradle 7.0. Gradle 8+ enables it by default, but you can enable it manually in Gradle 7 projects.

Q4: Is Gradle Version Catalog mandatory for Android projects?
 No, but it is highly recommended for scalability, especially in multi-module projects.

Q5: How does Gradle Version Catalog improve collaboration?
 By keeping all dependencies in one place, teams avoid mismatched versions across different modules or branches.

Conclusion

Gradle Version Catalog is a must-have tool for modern Android development. It reduces duplication, improves maintainability, and ensures consistent dependency management across projects.

If you’re still hardcoding dependencies in multiple build.gradle files, now is the perfect time to migrate. With libs.versions.toml, your Android project becomes cleaner, more maintainable, and easier to scale.

Dependency Management

Dependency Management in Android Gradle

Dependency management is a crucial aspect of Android development using Gradle. It helps in organizing external libraries, avoiding version conflicts, and improving project maintainability. In this blog, we will explore Gradle dependency management in Android, discuss best practices, and demonstrate its implementation with Kotlin code examples.

What is Dependency Management in Android?

In Android, applications rely on various third-party libraries, SDKs, and modules to add features without reinventing the wheel. These dependencies are managed using Gradle, a powerful build automation tool.

Gradle allows developers to:

  • Add dependencies from remote repositories like Maven Central or Google’s Maven.
  • Specify versions and update them easily.
  • Use dependency constraints to avoid conflicts.
  • Create reusable dependency configurations for modular projects.

Producers and Consumers in Dependency Management

In Android development, dependency management is about how libraries and modules interact. Simply put, it’s important to differentiate between producers and consumers in dependency management.

  • Producer: When you create an Android library (like a custom UI component or a utility library), you are the producer because you provide this library for others to use.
  • Consumer: When you add dependencies in your Android project (e.g., using implementation 'com.squareup.retrofit2:retrofit:2.9.0' in build.g
Kotlin
dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
}

This simple line makes your project a consumer of Retrofit while Square (the creator) is the producer.

Understanding Gradle Dependencies in Android

Android projects use Gradle as a build system, and dependencies are added inside the build.gradle.kts (Kotlin DSL) or build.gradle (Groovy DSL) files.

Types of Dependencies in Android Gradle

Gradle lets you manage different types of dependencies, each useful for specific scenarios:

1. Local Dependencies

Include .jar or .aar files placed inside the libs/ folder:

Kotlin
dependencies {
    implementation(files("libs/mylibrary.jar"))
}

2. Remote Dependencies

Fetch external libraries from repositories like Maven Central, Google’s Maven, or JitPack:

Kotlin
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
}

3. Project Dependencies

Link modules within the same Android project:

Kotlin
dependencies {
    implementation(project(":core"))
    implementation(project(":feature-login"))
}

Best Practices for Dependency Management

To keep your Gradle builds clean, stable, and efficient, follow these practices:

  • Use BOM (Bill of Materials): Align versions across related libraries.
  • Centralize versions: Store dependency versions in one place (e.g., gradle/libs.versions.toml or buildSrc).
  • Handle conflicts explicitly: Use dependencyResolutionStrategy or constraints.
  • Avoid duplicate libraries: Regularly check for unused dependencies.
  • Prefer api vs implementation wisely:
  • Use implementation for internal dependencies (faster builds).
  • Use api only when consumers need access to transitive dependencies.

Kotlin Example: Dependency Constraints

Here’s how you can enforce consistent versions across dependencies:

Kotlin
dependencies {
    constraints {
        implementation("com.squareup.okhttp3:okhttp:4.11.0") {
            because("Ensures compatibility across Retrofit and OkHttp usage")
        }
    }
}

This prevents Gradle from pulling in mismatched versions.

Conclusion

Dependency management in Android Gradle is more than just adding libraries — it’s about keeping your app maintainable, efficient, and conflict-free. By using BOMs, centralizing versions, managing conflicts, and understanding producers vs. consumers, you’ll avoid common pitfalls that slow down development.

Mastering Gradle dependency management not only improves build speed but also makes your project easier to scale and collaborate on. The payoff is an Android project that’s stable, consistent, and production-ready.

FAQ: Android Gradle Dependency Management

Q1: What’s the difference between implementation and api in Gradle?

  • implementation: Dependency is used internally; faster builds since it’s not exposed.
  • api: Exposes the dependency to consumers of your module. Use sparingly.

Q2: How do I avoid version conflicts in Gradle?
 Use dependency constraints, enforce consistent versions with a BOM, and run ./gradlew dependencies to audit conflicts.

Q3: Can I remove unused dependencies automatically?
 Yes, tools like Gradle Lint Plugin or IDE inspections can detect and remove unused libraries.

Q4: What’s the benefit of centralizing dependency versions?
 It ensures consistency across all modules, simplifies upgrades, and prevents subtle runtime issues from mismatched versions.

Q5: Should I prefer local or remote dependencies?
 Prefer remote dependencies from trusted repositories for maintainability. Use local JAR/AAR files only for custom or private libraries not available publicly.

error: Content is protected !!