cd /news/ai-tools/pragmatic-ai-driven-workflows-refact… · home › topics › ai-tools › article
[ARTICLE · art-140386] src=dev.to ↗ pub= topic=ai-tools verified=true sentiment=↑ positive

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

A developer outlines a multi-stage workflow for using Gemini in Android Studio and AI coding agents to migrate legacy Android codebases from RxJava and XML Views to Kotlin Coroutines, StateFlow, and Jetpack Compose. The approach breaks refactoring into isolated steps — converting RxJava observables to Coroutines/Flow, extracting imperative View state into a single immutable UI state object, migrating XML layouts to Compose, and generating comparative unit tests — rather than prompting the AI to convert entire files at once, which the author says causes hallucinated state hoisting, broken lifecycle handling, and dropped business logic.

by read5 min views1 publishedSep 27, 2026

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 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.)

    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.)
val uiState: StateFlow<UserProfileState> = _uiState.asStateFlow()

fun loadUserProfile(userId: String) {
    viewModelScope.launch {
        _uiState.value = UserProfileState.
        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. -> {

            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.

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.

── more in #ai-tools 4 stories · sorted by recency
── more on @gemini 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/pragmatic-ai-driven-…] indexed:0 read:5min 2026-09-27 · —