# Pragmatic AI-Driven Workflows: Refactoring Legacy Kotlin Code with Gemini in Android Studio

> Source: <https://dev.to/mikekelvin/pragmatic-ai-driven-workflows-refactoring-legacy-kotlin-code-with-gemini-in-android-studio-3p3l>
> Published: 2026-09-27 08:20:27+00:00

Maintaining legacy Android codebases often feels like taking care of an ancient engine while driving on a highway. Thousands of production apps still rely on a mix of Java/Kotlin code, imperative XML layouts, and complex RxJava reactive streams. While Jetpack Compose, Kotlin Coroutines, and StateFlow have become the modern standard for Android development, refactoring millions of lines of legacy code manually is time-consuming, repetitive, and prone to regressions.

With the integration of Gemini in Android Studio and modern coding agents, Android engineers have a practical assistant to accelerate codebase modernization. However, handing full control to AI often leads to unmaintainable code or subtle runtime bugs.

In this guide, we will explore a practical, step-by-step workflow for leveraging Gemini and AI coding agents to refactor legacy RxJava and XML View components into clean, idiomatic Jetpack Compose and Coroutines — without sacrificing safety or application stability.

The Refactoring Strategy: Human Specs, AI Translates

A common mistake developers make when using AI coding assistants is pasting an entire file and prompting: “Convert this to Compose and Coroutines.”

This approach often results in hallucinated state hoisting, broken lifecycle handling, or dropped business logic. A far more effective workflow follows a multi-stage migration pipeline:

```
Isolate Business Logic: Convert RxJava observables to Coroutines and Flow.
Isolate UI State: Extract imperative View states into a single immutable UI state object.
Migrate UI Layer: Convert Layout XML to Jetpack Compose components.
Automate Verification: Use AI to generate comparative unit tests for the before-and-after logic.
```

Let’s walk through this process using a representative legacy component: a user profile loader with search filtering and error handling.

Step 1: Converting Legacy RxJava to Kotlin Coroutines and Flow

Consider this typical legacy ViewModel snippet written with RxJava 2/3 and BehaviorSubject:

// Legacy ViewModel using RxJava

class LegacyUserProfileViewModel(

    private val userRepository: UserRepository

) : ViewModel() {

```
private val compositeDisposable = CompositeDisposable()
val userStateSubject = BehaviorSubject.create<UserProfileState>()

fun loadUserProfile(userId: String) {
    userStateSubject.onNext(UserProfileState.Loading)

    val disposable = userRepository.getUserById(userId)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(
        { user -> userStateSubject.onNext(UserProfileState.Success(user)) },
        { error -> userStateSubject.onNext(UserProfileState.Error(error.localizedMessage)) }
    )

    compositeDisposable.add(disposable)
}

override fun onCleared() {
    compositeDisposable.clear()
    super.onCleared()
}
```

}

Prompting Gemini in Android Studio

Instead of asking Gemini to convert the entire file, focus specifically on stream transformation and thread management:

Prompt:

```
“Act as a Principal Android Engineer. Refactor this RxJava BehaviorSubject and CompositeDisposable setup to use Kotlin Coroutines, StateFlow, and viewModelScope. Preserve state immutability, ensure exception handling is idiomatic, and remove all RxJava dependencies.”
```

The Generated Output (Refactored)

// Refactored ViewModel using Coroutines and StateFlow

class ModernUserProfileViewModel(

    private val userRepository: UserRepository

) : ViewModel() {

```
private val _uiState = MutableStateFlow<UserProfileState>(UserProfileState.Loading)
val uiState: StateFlow<UserProfileState> = _uiState.asStateFlow()

fun loadUserProfile(userId: String) {
    viewModelScope.launch {
        _uiState.value = UserProfileState.Loading
        runCatching {
            userRepository.getUserById(userId)
        }.onSuccess { user ->
            _uiState.value = UserProfileState.Success(user)
        }.onFailure { error ->
            _uiState.value = UserProfileState.Error(error.localizedMessage ?: "Unknown Error")
        }
    }
}
```

Key Improvements Caught by Review:

```
viewModelScope automatically handles cancellation on ViewModel disposal, eliminating manual cleanup logic.
StateFlow ensures thread-safe, state-retaining reactivity designed specifically for modern Android UI rendering.
```

Step 2: Translating Legacy Layout XML to Jetpack Compose

Legacy Android layouts often suffer from deep view hierarchies, explicit visibility toggles (View.VISIBLE vs. View.GONE), and verbose binding code.

Here is a snippet of a legacy XML layout:

    xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    android:padding="16dp">

```
<ProgressBar
    android:id="@+id/progressBar"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:visibility="gone" />

<TextView
    android:id="@+id/tvUserName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:textStyle="bold" />

<Button
    android:id="@+id/btnRetry"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Retry" />
```

Prompting for Compose Conversion

When prompting Gemini to convert XML layouts to Jetpack Compose, instruct it to follow Material 3 guidelines and proper state hoisting patterns.

```
“Convert this Layout XML into a declarative Jetpack Compose Composable function named UserProfileScreen. Accept UserProfileState as a parameter and hoist click events via lambda callbacks. Ensure layout semantics match the original padding and structure using Material 3 components.”
```

The Generated Composable

@Composable

fun UserProfileScreen(

    state: UserProfileState,

    onRetryClick: () -> Unit,

    modifier: Modifier = Modifier

) {

    Box(

        modifier = modifier

            .fillMaxSize()

            .padding(16.dp),

        contentAlignment = Alignment.Center

    ) {

        when (state) {

            is UserProfileState.Loading -> {

                CircularProgressIndicator()

            }

            is UserProfileState.Success -> {

                Column(

                    horizontalAlignment = Alignment.CenterHorizontally,

                    verticalArrangement = Arrangement.spacedBy(8.dp)

                ) {

                    Text(

                        text = state.user.name,

                        style = MaterialTheme.typography.titleLarge

                    )

                }

            }

            is UserProfileState.Error -> {

                Column(horizontalAlignment = Alignment.CenterHorizontally) {

                    Text(

                        text = state.message,

                        color = MaterialTheme.colorScheme.error

                    )

                    Spacer(modifier = Modifier.height(8.dp))

                    Button(onClick = onRetryClick) {

                        Text(text = "Retry")

                    }

                }

            }

        }

    }

}

Step 3: Identifying and Correcting AI Anti-Patterns

While Gemini generates strong code foundations, automated outputs can introduce subtle anti-patterns that require human review:

```
Unnecessary Allocation in Recomposition: Check if objects (like formatters or heavy objects) are being created inside the Composable body without remember.
Missing Key Identifiers in Lists: When converting RecyclerView adapters to LazyColumn, ensure key = { … } is explicitly provided to prevent unnecessary recompositions.
Improper Side-Effect Handling: Verify that long-running operations or state observations are wrapped in LaunchedEffect rather than called directly during composition.
```

Step 4: Generating Unit Tests for Regression Safeguards

Refactoring is incomplete without automated tests to verify business logic continuity. You can ask Gemini in Android Studio to write tests using Kotlin Coroutines Test utilities (runTest, StandardTestDispatcher).

```
“Write a Kotlin unit test using kotlinx.coroutines.test and MockK for ModernUserProfileViewModel. Cover successful data fetching and error states using runTest.”
```

@OptIn(ExperimentalCoroutinesApi::class)

class ModernUserProfileViewModelTest {

```
private val userRepository: UserRepository = mockk()
private val testDispatcher = StandardTestDispatcher()

@Before
fun setUp() {
    Dispatchers.setMain(testDispatcher)
}

@After
fun tearDown() {
    Dispatchers.resetMain()
}

@Test
fun `loadUserProfile updates state to Success when repository returns user`() = runTest {
    val fakeUser = User(id = "123", name = "Jane Doe")
    coEvery { userRepository.getUserById("123") } returns fakeUser

    val viewModel = ModernUserProfileViewModel(userRepository)
    viewModel.loadUserProfile("123")

    advanceUntilIdle()

    assertEquals(UserProfileState.Success(fakeUser), viewModel.uiState.value)
}
```

Key Takeaways for Developers

```
Treat AI as a Pair Programmer, Not an Autonomous Engine: Guide the assistant through small, testable increments rather than asking for full-file replacements.
Master the Context: Pass clean domain models and clear architectural rules in your prompts to ensure generated code aligns with your project’s standards.
Focus on Code Review: Spend time analyzing memory overhead, recomposition triggers, and lifecycle management rather than typing boilerplate setup code.
```

Links & Resources:

Enterprise Modernization: Explore expert insights and enterprise solutions for scaling modern mobile architecture by hiring dedicated [Android app development company](https://www.kellton.com/services/android-app-development).

Further Reading: Check out my previous article, Building Production-Ready On-Device AI in Android, on my Medium Profile.

Connect: Follow my latest Android updates on LinkedIn or subscribe to my newsletter.
