Googleโ€™s AI Just Proofread My Writing Better Than I Ever Could

J

Jose Garcia

Guest
This is Part 2 of a series of articles where I explain how to implement GenAI on Android. [Click here to view the full series.]
[TrendyMediaToday.com] Googleโ€™s AI Just Proofread My Writing Better Than I Ever Could {file_size} {filename}

Letโ€™s test out the proofreading feature on MLKit

After building the summarisation feature in SmartWriter, integrating proofreading was incredibly fastโ€Šโ€”โ€ŠI reused almost all the same logic and had a working feature in under an hour.

This time, instead of focusing on UI, Iโ€™ll walk you through how the entire ViewModel worksโ€Šโ€”โ€Šincluding model download handling, inference, and graceful error fallbackโ€Šโ€”โ€Šusing the new Proofreading API from ML Kit GenAI.

๐Ÿ‘‰ Full app available here:

https://github.com/josegbel/smartwriter

๐Ÿ“Œ What weโ€™re building​


Weโ€™re using ML Kitโ€™s on-device GenAI model to take raw user input and return grammatically improved alternatives. For example:

Input:

i think this idea could works, but not sure if make sense

Output:

I think this idea could work, but Iโ€™m not sure if it makes sense.

Letโ€™s look at how the ViewModel makes this possibleโ€Šโ€”โ€Šno Compose needed to understand this one ๐Ÿ˜‰

๐Ÿง  The ViewModel in full​


Hereโ€™s the complete code for the ProofreadingViewModel. Iโ€™ll break it down below:

package com.example.smartwriter.viewmodel

import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.smartwriter.ui.model.ProofreadingUiEvent
import com.example.smartwriter.ui.model.ProofreadingUiState
import com.google.mlkit.genai.common.DownloadCallback
import com.google.mlkit.genai.common.FeatureStatus
import com.google.mlkit.genai.common.GenAiException
import com.google.mlkit.genai.proofreading.Proofreader
import com.google.mlkit.genai.proofreading.ProofreaderOptions
import com.google.mlkit.genai.proofreading.Proofreading
import com.google.mlkit.genai.proofreading.ProofreadingRequest
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import javax.inject.Inject

class ProofreadingViewModel
@Inject
constructor() : ViewModel() {

companion object {
private val TAG = ProofreadingViewModel::class.java.simpleName
}

private val _uiState = MutableStateFlow(ProofreadingUiState())
val uiState: StateFlow<ProofreadingUiState> = _uiState.asStateFlow()

private val _uiEvent = MutableSharedFlow<ProofreadingUiEvent>()
val uiEvent: SharedFlow<ProofreadingUiEvent> = _uiEvent.asSharedFlow()

private var proofreader: Proofreader? = null

override fun onCleared() {
proofreader?.close()
super.onCleared()
}

fun onInputTextChanged(newText: String) {
_uiState.update { it.copy(inputText = newText) }
}

fun onProofreadClicked(context: Context) {
viewModelScope.launch {
try {
val options =
ProofreaderOptions
.builder(context)
.setInputType(ProofreaderOptions.InputType.KEYBOARD)
.setLanguage(ProofreaderOptions.Language.ENGLISH)
.build()

proofreader = Proofreading.getClient(options)

prepareAndStartProofreading()
} catch (e: Exception) {
Log.e(TAG, "Error in onProofreadClicked: ${e.message}", e)
_uiEvent.emit(ProofreadingUiEvent.Error(message = "Error: ${e.message}"))
}
}
}

๐Ÿ” Breakdownโ€Šโ€”โ€ŠInitialisation​

  • We use ProofreaderOptions to configure the input type (keyboard text vs voice) and language (English).
  • Proofreading.getClient(options) creates the proofreader instance.
  • Then we delegate to prepareAndStartProofreading().

suspend fun prepareAndStartProofreading() {
val featureStatus = proofreader?.checkFeatureStatus()?.await()
Log.d(TAG, "Feature status: $featureStatus")

when (featureStatus) {
FeatureStatus.DOWNLOADABLE -> {
Log.d(TAG, "Feature DOWNLOADABLE โ€“ starting download")
downloadFeature()
}

FeatureStatus.DOWNLOADING -> {
Log.d(TAG, "Feature DOWNLOADING โ€“ will start once ready")
proofreader?.let { startProofreadingRequest(uiState.value.inputText, it) }
}

FeatureStatus.AVAILABLE -> {
Log.d(TAG, "Feature AVAILABLE โ€“ running inference")
_uiState.update { it.copy(isLoading = true) }
proofreader?.let {
Log.d(TAG, "starting proofreading request")
startProofreadingRequest(uiState.value.inputText, it)
}
}

FeatureStatus.UNAVAILABLE, null -> {
Log.e(TAG, "Feature UNAVAILABLE")
_uiEvent.emit(ProofreadingUiEvent.Error(message = "Your device does not support this feature."))
}
}
}

๐Ÿ” Breakdownโ€Šโ€”โ€ŠFeature availability​

  • checkFeatureStatus() tells us whether the Gemini Nano model is available, downloading, or needs to be downloaded.
  • If itโ€™s downloadable, we call downloadFeature().
  • If itโ€™s already available, we immediately run the inference.
  • We also handle DOWNLOADING and UNAVAILABLE.

This flexibility ensures the user gets feedback even when the model isnโ€™t ready yet.

private fun downloadFeature() {
proofreader?.downloadFeature(
object : DownloadCallback {
override fun onDownloadStarted(bytesToDownload: Long) {
_uiState.update { it.copy(isLoading = true) }
Log.d(TAG, "Download started โ€“ bytesToDownload=$bytesToDownload")
}

override fun onDownloadProgress(totalBytesDownloaded: Long) {
_uiState.update { it.copy(isLoading = true) }
Log.d(TAG, "Download progress โ€“ totalBytesDownloaded=$totalBytesDownloaded")
}

override fun onDownloadCompleted() {
_uiState.update { it.copy(isLoading = false) }
Log.d(TAG, "Download completed โ€“ starting inference")
proofreader?.let { startProofreadingRequest(uiState.value.inputText, it) }
}

override fun onDownloadFailed(e: GenAiException) {
_uiState.update { it.copy(isLoading = false) }
Log.e(TAG, "Download failed: ${e.message}", e)
_uiEvent.tryEmit(
ProofreadingUiEvent.Error(
message = "Download failed: ${e.message}",
),
)
}
},
)
}

๐Ÿ” Breakdownโ€Šโ€”โ€ŠDownload logic​

  • This handles model download events and updates the UI accordingly.
  • On successful download, we trigger the inference againโ€Šโ€”โ€Šusing the same input.

fun startProofreadingRequest(
text: String,
proofreader: Proofreader,
) {
val proofreadingRequest = ProofreadingRequest.builder(text).build()
_uiState.update { it.copy(isLoading = true) }

viewModelScope.launch {
try {
val results = proofreader.runInference(proofreadingRequest).await().results
_uiState.update { state ->
state.copy(correctionSuggestions = results.map { it.text })
}
} catch (e: Exception) {
_uiEvent.emit(
ProofreadingUiEvent.Error(
message = "Error during proofreading: ${e.message}",
),
)
} finally {
_uiState.update { it.copy(isLoading = false) }
}
}
}
}

๐Ÿ” Breakdownโ€Šโ€”โ€ŠRunning the model​

  • The input string is wrapped in a ProofreadingRequest.
  • runInference(โ€ฆ).await() gives us the corrected suggestions.
  • We extract and display result.text for each suggestion in the UI.

๐Ÿงช Key takeaways​

  • โœ… If youโ€™ve implemented summarisation already, adding proofreading is fast.
  • โœ… The Gemini Nano model gives usable, human-like corrections.
  • ๐Ÿง  You get multiple suggestions, not diffsโ€Šโ€”โ€Šyou can choose how to present them.
  • ๐Ÿ“ต Emulators and unsupported phones wonโ€™t work. I tested on a Galaxy S25 Ultra.
  • ๐ŸŒ Currently only supports English.

๐Ÿš€ Try it yourself​


This is Part 2 of my SmartWriter blog series. You can find the full app (including Compose UI and previews) here:

๐Ÿ‘‰ https://github.com/josegbel/smartwriter

Next up: Text Rewritingโ€Šโ€”โ€Šwhere weโ€™ll teach the app to rephrase user input in different tones and lengths.

Follow along and let me know what youโ€™d improve.

[TrendyMediaToday.com] Googleโ€™s AI Just Proofread My Writing Better Than I Ever Could {file_size} {filename}



Googleโ€™s AI Just Proofread My Writing Better Than I Ever Could ๐Ÿคฏ was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.

Continue reading...
 


Join ๐•‹๐•„๐•‹ on Telegram
Channel PREVIEW:
Back
Top