On this page
Our AI Text Summarization guide covers summarising a block of text. This project goes end to end: picking real documents, handling inputs longer than the model accepts, and building a usable app around it.
Architecture
1. Pick a document
val openDoc = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
uri?.let(viewModel::onDocumentPicked)
}
Button(onClick = { openDoc.launch(arrayOf("application/pdf", "text/plain")) }) {
Text("Choose document")
}2. Extract text
Plain text is easy. For PDFs, there are several options:
- Cloud multimodal model: send the PDF directly to Gemini through Firebase AI Logic, which accepts PDFs as inline data (within size limits). Simplest, and handles layout.
- On-device: render pages with
PdfRendererto bitmaps and run ML Kit text recognition. Private, but slower. - A PDF text library: for text-based PDFs. Check licences.
class TextExtractor(private val context: Context, private val recognizer: TextRecognizer) {
suspend fun extract(uri: Uri): String = withContext(Dispatchers.IO) {
when (context.contentResolver.getType(uri)) {
"text/plain" -> context.contentResolver.openInputStream(uri)!!.bufferedReader().use { it.readText() }
"application/pdf" -> ocrPdf(uri)
else -> throw UnsupportedDocumentException()
}
}
private suspend fun ocrPdf(uri: Uri): String {
val pfd = context.contentResolver.openFileDescriptor(uri, "r") ?: return ""
return PdfRenderer(pfd).use { renderer ->
buildString {
for (i in 0 until minOf(renderer.pageCount, MAX_PAGES)) {
renderer.openPage(i).use { page ->
val bmp = Bitmap.createBitmap(page.width * 2, page.height * 2, Bitmap.Config.ARGB_8888)
bmp.eraseColor(Color.WHITE)
page.render(bmp, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
appendLine(recognizer.process(InputImage.fromBitmap(bmp, 0)).await().text)
bmp.recycle()
}
}
}
}
}
}3. Chunk long text
Models have input limits (Gemini Nano summarisation accepts under ~4,000 tokens; cloud models accept far more, but cost grows with input). Split on paragraph boundaries:
fun chunk(text: String, maxChars: Int = 8_000): List<String> {
val chunks = mutableListOf<String>()
val current = StringBuilder()
for (para in text.split(Regex("\\n\\s*\\n"))) {
if (current.length + para.length > maxChars && current.isNotEmpty()) {
chunks += current.toString(); current.clear()
}
current.append(para.take(maxChars)).append("\n\n")
}
if (current.isNotBlank()) chunks += current.toString()
return chunks
}
// Character counts approximate tokens (~4 chars/token in English); keep a safety margin.4. Map-reduce summarisation
class DocumentSummarizer(private val llm: Llm) { // Llm: generate(prompt): String, stream(prompt): Flow<String>
fun summarize(text: String, onProgress: (Int, Int) -> Unit): Flow<String> = flow {
val chunks = chunk(text)
val partials = if (chunks.size == 1) chunks else chunks.mapIndexed { i, c ->
onProgress(i + 1, chunks.size)
llm.generate("Summarise this section in 5 bullet points. Use only the text.\n\n$c")
}
emitAll(
llm.stream(
"Combine these section summaries into one summary: a 2-sentence overview, " +
"then up to 8 bullet points. Don't add facts not present.\n\n" +
partials.joinToString("\n\n")
)
)
}
}Run chunk summaries with limited parallelism to stay within rate limits, and stream only the final step. Render it as in Streaming in Compose.
5. Save history
@Entity(tableName = "summaries")
data class SummaryEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val summary: String,
val sourceChars: Int,
val modelSource: String, // "on-device" or "cloud"
val createdAt: Long,
)
// Store the summary, not the full document, unless users need it (privacy + storage).Quality and trust
- Label summaries as AI-generated, and show which model source was used.
- Prompt for faithfulness ("use only the text"), and spot-check with an evaluation set. See Evaluating AI.
- Disclose cloud processing, and offer on-device-only mode for sensitive documents.
- Handle failures per stage with clear messages.
Extensions
Ask the document
Add Q&A with retrieval. See RAG on Android.
Share target
Accept shared text or files from other apps.
Background processing
Long documents via WorkManager with a completion notification.
Frequently asked questions
How long a document can I summarise?
With map-reduce, length is limited mainly by time and cost. Cap pages or characters and tell users.
Will scanned PDFs work?
Yes, with OCR or a multimodal model, but quality depends on scan quality.
Conclusion
A solid summarizer picks files via SAF, extracts text (or sends PDFs to a multimodal model), chunks long inputs, summarises with map-reduce, streams the final result, and stores history, with clear AI labelling and privacy choices.
Next project: an AI Photo Caption Generator.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Every AI feature still runs inside an Android app that has to be fast, reliable and well structured. Strengthen the Kotlin, Jetpack Compose and architecture skills that AI integrations depend on, then apply these AI concepts with confidence.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.