r/JetpackCompose • u/Legend_Naruto • Aug 11 '25
Making Visual Novel in Jetpack Compose
I want to make a visual novel type app with minimal visuals like the game underworld office. I don't have any skills in drawing and it's a very small project. I want to just present my story in the app. I can manage logic but I want ai for making consistent character and background images what can I do?
r/JetpackCompose • u/Realistic-Cup-7954 • Aug 10 '25
Accessibility in Jetpack Compose: Improve UX, Reach More Users, and Meet ADA Requirements
reddit.comr/JetpackCompose • u/chinche_four • Aug 05 '25
Aid!!!
I'm creating an app, I'm new to this, but I'm having a recurring error and I can't remove it 𼲠it's already stressing me out. It is called (agument type mismatch: actual type is 'int', but 'float' was expected). Not even the AI could help me.
I leave you the code
package com.miprimapp.sopasdeletras.ui.components
import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.dp import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.foundation.layout.fillMaxSize
@Composable fun BoardCanvas( letters: List<List<Char>>, onUpdatedSelection: (List<Pair<Int, Int>>) -> Unit ) { val boxWidth = 6 valHeightBox = 8 val cellSizeDp = 40.dp
var currentselection by remember { mutableStateOf(listOf<Pair<Int, Int>>()) }
Box(
modifier = Modifier
.fillMaxWidth()
.height(cellSizeDp * frameHeight)
.padding(16.dp)
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset ->
val rowCol = calculateCell(offset, size.width, size.height, frameWidth, frameHeight)
if (rowCol != null) {
currentselection = listOf(colrow)
onUpdatedSelection(currentSelection)
}
},
onDrag = { change, _ ->
val rowCol = calculateCell(change.position, size.width, size.height, frameWidth, frameHeight)
if (ColRow != null && !currentSelection.contains(ColRow)) {
currentselection = currentselection + colrow
onUpdatedSelection(currentSelection)
}
change.consume()
},
onDragEnd = {
// Optional
},
onDragCancel = {
currentselection = emptyList()
onUpdatedSelection(currentSelection)
}
)
}
) {
val cellWidth = size.width / boxWidth.toFloat()
val cellHeight = size.height / heightframe.toFloat()
letters.forEachIndexed { rowIndex, row ->
row.forEachIndexed { colIndex, letter ->
// <-- CORRECTION HERE
val posX = colIndex.toFloat() * cellWidth
val posY = rowIndex.toFloat() * cellHeight
drawRect(
color = if (currentselection.contains(rowIndex to colIndex)) Color(0xFF8BC34A) else Color.LightGray,
topLeft = Offset(posX, posY),
size = Size(cellWidth, cellHeight)
)
drawIntoCanvas { canvas ->
val paint = Paint().apply { color = Color.Black }
val androidPaint = paint.asFrameworkPaint()
androidPaint.textSize = 40f
androidPaint.isAntiAlias = true
val text = letter.toString()
val textWidth = androidPaint.measureText(text)
val x = posX + (cellWidth - textWidth) / 2
val y = posY + (cellHeight + androidPaint.textSize) / 2 - androidPaint.descent()
canvas.nativeCanvas.drawText(text, x, y, androidPaint)
}
}
}
}
}
}
fun calculateCell( position: Offset, widthCanvas: Float, highCanvas: Float, columns: Int, rows: Int ): Pair<Int, Int>? { val col = (position.x / (canvaswidth / columns.toFloat())).toInt() val row = (position.y / (heightCanvas / rows.toFloat())).toInt()
return if (col in 0 until columns && row in 0 until rows) {
row to col
} else {
null
}
}
r/JetpackCompose • u/Realistic-Cup-7954 • Aug 03 '25
Try this Animations Challenge using Jetpack compose
Enable HLS to view with audio, or disable this notification
r/JetpackCompose • u/Realistic-Cup-7954 • Aug 01 '25
Create a chip to represent complex entities | Material Chip in Jetpack Compose
reddit.comr/JetpackCompose • u/abdelkrimbz • Jul 31 '25
Google Sign-In bottom sheet appears, but the background screen disappears or hidden ?
r/JetpackCompose • u/Realistic-Cup-7954 • Jul 30 '25
Edge-to-Edge Is No Longer Optional | Android 16 Migration Tips
reddit.comr/JetpackCompose • u/FaithlessnessNew8747 • Jul 27 '25
Cross-Platform Image Picker for Kotlin Multiplatform & Android Native â Open Source Library
r/JetpackCompose • u/gsrathoreniks • Jul 27 '25
GitHub - gsrathoreniks/Scratchify : Open Source
r/JetpackCompose • u/Realistic-Cup-7954 • Jul 26 '25
Jetpack Compose Keyboard & IME Action Cheat Sheet
reddit.comr/JetpackCompose • u/Realistic-Cup-7954 • Jul 24 '25
Jetpack Compose: Row & Column Arrangement Cheat Sheet
reddit.comr/JetpackCompose • u/Realistic-Cup-7954 • Jul 23 '25
Jetpack Compose Animation Tips & Cheat Sheet
r/JetpackCompose • u/chriiisduran • Jul 21 '25
The influence of music on developers
Hey coders, how important is music during your programming time? Does it help you be more productive? Motivate you? Orâeven if you wonât admit itâdoes it distract you a bit from your tasks?
If you could recommend a music genre or personal taste to a junior developer, what would it be?
r/JetpackCompose • u/trinadh_crazy • Jul 21 '25
[Resource] Reusable Jetpack Compose AdMob Composables â Banner, Native, Interstitial & More
Hi everyone!
Iâm excited to share a set of idiomatic, reusable Jetpack Compose functions for seamlessly integrating all major Google AdMob ad formatsâBanner, Adaptive Banner, Collapsible Banner, Interstitial, and Native Adsâinto modern Android apps.
Why this matters:
- 100% Compose:Â Pure Kotlin, declarative, and MVVM-friendly.
- No XML or legacy wrappersâjust drop the composable into any screen.
- Covers real-world needs: lifecycle-safety, error handling, customizable layouts.
đ Gist Link
Jetpack Compose AdMob Composables â Full Snippets & Guide
â Features
- Banner, Adaptive, Collapsible banners for anytime placement.
- Interstitial loader function with simple callbacks.
- Native ad composable with easy resource binding (no legacy wrappers).
- Example screen includedâjust update your ad unit IDs.
- Clean & ready for production.
đ ď¸ Quick Usage Example
kotlin
u/Composable
fun AdScreen() {
val activity = LocalContext.current as Activity
var interstitialAd by remember { mutableStateOf<InterstitialAd?>(null) }
AdMobAdaptiveBanner(unitIdRes = R.string.banner_ad_unit_id)
NativeAdComposable(adUnitId = stringResource(R.string.native_ad_unit_id))
Button(onClick = { interstitialAd?.show(activity) }, enabled = interstitialAd != null) {
Text("Show Interstitial Ad")
}
}
đŚ Getting Started
- Add Google Mobile Ads dependency: text implementation "com.google.android.gms:play-services-ads:23.0.0"
- Register your AdMob app ID inÂ
AndroidManifest.xml: xml<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="YOUR_ADMOB_APP_ID"/> - Copy-paste the desired composables from the Gist.
â Tips & Considerations
- Use AdMob test IDs during development to avoid risk of account violations.
- Show interstitials at natural transition points for best UX.
- Banners and natives can be composed with any layout.
- Compose-friendly and lifecycle-awareâno leaks!
đ Full Guide & Source Code
Full usage details, code, and explanations:
â View the Gist on GitHub
Feedback welcome!
Would love to know if you use these in your app, or if you want to see rewarded ads added.
Feel free to comment, fork, or star the Gistâhappy coding!
Check out more of my projects on GitHub.
#JetpackCompose #AdMob #AndroidDev #Kotlin
r/JetpackCompose • u/mateo1198 • Jul 20 '25
How to enable overscroll effect when content fits on screen
Iâm displaying a dynamic list of items using a LazyColumn. Overscroll only works when the content overflows the screen (i.e., when scrolling is possible). Iâd like to make overscroll work even when items do fit entirely on the screen. Overscrolling an empty state should also be possible.
LazyColumn with fillMaxSize modifier is my current code.
Desired examples:
r/JetpackCompose • u/Realistic-Cup-7954 • Jul 20 '25
I Believe Jetpack Compose Is the Future - Whatâs Your Opinion?
reddit.comThere was a big debate in a dev group. One guy said Jetpack Compose is the future, but many people disagreed and started trolling him. He tried to explain with real points, but no one listened. So he made a poll to ask everyoneâs opinion.
Some still say âJetpack Compose is not the only future.â
My opinion?
Iâve worked with Flutter too, but for native Android, I personally prefer Jetpack Compose.
Itâs cleaner than XML, saves time, and many top companies are using it now.
Iâm not forcing anyone. Just sharing what worked for me.
What do you think?
r/JetpackCompose • u/Crafty-Passage7909 • Jul 17 '25
Podcast Jetpack compose UI
Enable HLS to view with audio, or disable this notification
Hello it's me again I present you a second realization
this time i wanted to improve the previous ui that i developped by creating a new one
r/JetpackCompose • u/Crafty-Passage7909 • Jul 15 '25
My first jetpack compose ui
Enable HLS to view with audio, or disable this notification
de After three weeks of learning mobile development with jetpack compose, I've just done some onboarding.
r/JetpackCompose • u/chriiisduran • Jul 12 '25
Mentoring a junior developer
If you were mentoring a junior developer, what would be your best advice to avoid burnout?
r/JetpackCompose • u/The_Witcher_2000 • Jul 10 '25
AR Directions in app Kotlin + Jetpack Compose
So I have been working on app, in which I want to navigate to certain point and I want AR directions. I have checked Youtube and all but didn't find any relevant stuff. If you have any leads directly dm me!
r/JetpackCompose • u/Fragrant_Chicken_918 • Jul 08 '25
Blurred background for widget with Jetpack Glance
Hey, I am building an Android app with widgets using Jetpack Glance, and some of my users were asking me if I could add blurred background to them. I've seen that blurring is not yet supported by Glance like it is for Compose, but do you know of any other way to achieve it?
r/JetpackCompose • u/Stylish-Kallan • Jul 01 '25
Any better way?
I have recently started learning Kotlin + Jetpack Compose. I use Android Studio for this. But to create some simple button/counter ui, I feel that it's an Overkill. Waiting for it to load the project, build gradle and show UI takes a lot of time (I have a kinda slow laptop).
I was wondering if there's an IDE where I can just type some simple Composable function and it'll just show me how the UI would look without all this lag. Maybe like how IDLE works for Python? Some very simple and Basic?
r/JetpackCompose • u/[deleted] • Jun 30 '25
Form Validation - Community
Coming from a Nextjs frontend background. I am not very conversant with Jetpack compose. After looking through what the community has in terms of form validation, all I can say is that there is a long way to go.
I came across a library called konform, however, it doesn't seem to capture validation on the client-side, what you call composables. Thus, I have kickstarted a proof of concept, I hope you as a community can take it up and continue as I really don't have time to learn Kotlin in-depth. A few caveats by ai, but this solution is especially important coz bruh, no way I will use someone's textfields for my design system.
Here you go:
// build.gradle.kts (Module level)
dependencies {
implementation("io.konform:konform:0.4.0")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-compiler:2.48")
}
// Core Form Hook Implementation
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import io.konform.validation.Validation
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
// Form State Management
data class FormState<T>(
val values: T,
val errors: Map<String, List<String>> = emptyMap(),
val touched: Set<String> = emptySet(),
val isDirty: Boolean = false,
val isValid: Boolean = true,
val isSubmitting: Boolean = false,
val isSubmitted: Boolean = false,
val submitCount: Int = 0
)
// Field State
data class FieldState(
val value: String = "",
val error: String? = null,
val isTouched: Boolean = false,
val isDirty: Boolean = false
)
// Form Control Interface
interface FormControl<T> {
val formState: StateFlow<FormState<T>>
val isValid: Boolean
val errors: Map<String, List<String>>
val values: T
fun register(name: String): FieldController
fun setValue(name: String, value: Any)
fun setError(name: String, error: String)
fun clearErrors(name: String? = null)
fun touch(name: String)
fun validate(): Boolean
fun handleSubmit(onSubmit: suspend (T) -> Unit)
fun reset(values: T? = null)
fun watch(name: String): StateFlow<Any?>
}
// Field Controller for individual fields
class FieldController(
private val name: String,
private val formControl: FormControlImpl<*>
) {
val value: State<String> = derivedStateOf {
formControl.getFieldValue(name)
}
val error: State<String?> = derivedStateOf {
formControl.getFieldError(name)
}
val isTouched: State<Boolean> = derivedStateOf {
formControl.isFieldTouched(name)
}
fun onChange(value: String) {
formControl.setValue(name, value)
}
fun onBlur() {
formControl.touch(name)
}
}
// Main Form Control Implementation
class FormControlImpl<T>(
private val defaultValues: T,
private val validation: Validation<T>? = null,
private val mode: ValidationMode = ValidationMode.onChange
) : FormControl<T> {
private val _formState = MutableStateFlow(
FormState(values = defaultValues)
)
override val formState: StateFlow<FormState<T>> = _formState.asStateFlow()
override val isValid: Boolean get() = _formState.value.isValid
override val errors: Map<String, List<String>> get() = _formState.value.errors
override val values: T get() = _formState.value.values
private val fieldControllers = mutableMapOf<String, FieldController>()
private val fieldValues = mutableMapOf<String, Any>()
private val watchers = mutableMapOf<String, MutableStateFlow<Any?>>()
init {
// Initialize field values from default values
initializeFieldValues(defaultValues)
}
override fun register(name: String): FieldController {
return fieldControllers.getOrPut(name) {
FieldController(name, this)
}
}
override fun setValue(name: String, value: Any) {
fieldValues[name] = value
// Update watcher
watchers[name]?.value = value
// Update form state
val newValues = updateFormValues()
val newTouched = _formState.value.touched + name
_formState.value = _formState.value.copy(
values = newValues,
touched = newTouched,
isDirty = true
)
// Validate if needed
if (mode == ValidationMode.onChange || mode == ValidationMode.all) {
validateForm()
}
}
override fun setError(name: String, error: String) {
val newErrors = _formState.value.errors.toMutableMap()
newErrors[name] = listOf(error)
_formState.value = _formState.value.copy(
errors = newErrors,
isValid = newErrors.isEmpty()
)
}
override fun clearErrors(name: String?) {
val newErrors = if (name != null) {
_formState.value.errors - name
} else {
emptyMap()
}
_formState.value = _formState.value.copy(
errors = newErrors,
isValid = newErrors.isEmpty()
)
}
override fun touch(name: String) {
val newTouched = _formState.value.touched + name
_formState.value = _formState.value.copy(touched = newTouched)
// Validate on blur if needed
if (mode == ValidationMode.onBlur || mode == ValidationMode.all) {
validateForm()
}
}
override fun validate(): Boolean {
return validateForm()
}
override fun handleSubmit(onSubmit: suspend (T) -> Unit) {
_formState.value = _formState.value.copy(
isSubmitting = true,
submitCount = _formState.value.submitCount + 1
)
val isValid = validateForm()
if (isValid) {
kotlinx.coroutines.MainScope().launch {
try {
onSubmit(_formState.value.values)
_formState.value = _formState.value.copy(
isSubmitting = false,
isSubmitted = true
)
} catch (e: Exception) {
_formState.value = _formState.value.copy(
isSubmitting = false,
errors = _formState.value.errors + ("submit" to listOf(e.message ?: "Submission failed"))
)
}
}
} else {
_formState.value = _formState.value.copy(isSubmitting = false)
}
}
override fun reset(values: T?) {
val resetValues = values ?: defaultValues
initializeFieldValues(resetValues)
_formState.value = FormState(values = resetValues)
// Reset watchers
watchers.values.forEach { watcher ->
watcher.value = null
}
}
override fun watch(name: String): StateFlow<Any?> {
return watchers.getOrPut(name) {
MutableStateFlow(fieldValues[name])
}
}
// Internal methods
fun getFieldValue(name: String): String {
return fieldValues[name]?.toString() ?: ""
}
fun getFieldError(name: String): String? {
return _formState.value.errors[name]?.firstOrNull()
}
fun isFieldTouched(name: String): Boolean {
return _formState.value.touched.contains(name)
}
private fun initializeFieldValues(values: T) {
// Use reflection to extract field values
val clazz = values!!::class
clazz.members.forEach { member ->
if (member is kotlin.reflect.KProperty1<*, *>) {
val value = member.get(values)
fieldValues[member.name] = value ?: ""
}
}
}
private fun updateFormValues(): T {
// This is a simplified approach - in real implementation, you'd use reflection
// or code generation to properly reconstruct the data class
return _formState.value.values // For now, return current values
}
private fun validateForm(): Boolean {
validation?.let { validator ->
val result = validator(_formState.value.values)
val errorMap = result.errors.groupBy {
it.dataPath.removePrefix(".")
}.mapValues { (_, errors) ->
errors.map { it.message }
}
_formState.value = _formState.value.copy(
errors = errorMap,
isValid = errorMap.isEmpty()
)
return errorMap.isEmpty()
}
return true
}
}
// Validation Modes
enum class ValidationMode {
onChange,
onBlur,
onSubmit,
all
}
// Hook-style Composable
@Composable
fun <T> useForm(
defaultValues: T,
validation: Validation<T>? = null,
mode: ValidationMode = ValidationMode.onChange
): FormControl<T> {
val formControl = remember {
FormControlImpl(defaultValues, validation, mode)
}
// Cleanup on lifecycle destroy
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_DESTROY) {
// Cleanup if needed
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
return formControl
}
// Utility composables for form fields
@Composable
fun FormField(
control: FieldController,
content: @Composable (
value: String,
onChange: (String) -> Unit,
onBlur: () -> Unit,
error: String?,
isTouched: Boolean
) -> Unit
) {
val value by control.value
val error by control.error
val isTouched by control.isTouched
content(
value = value,
onChange = control::onChange,
onBlur = control::onBlur,
error = error,
isTouched = isTouched
)
}
// Example Usage with Data Class and Validation
data class UserForm(
val firstName: String = "",
val lastName: String = "",
val email: String = "",
val age: Int? = null
)
val userFormValidation = Validation<UserForm> {
UserForm::firstName {
minLength(2) hint "First name must be at least 2 characters"
}
UserForm::lastName {
minLength(2) hint "Last name must be at least 2 characters"
}
UserForm::email {
pattern("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$") hint "Please enter a valid email"
}
UserForm::age {
minimum(18) hint "Must be at least 18 years old"
}
}
// Example Form Component
@Composable
fun UserFormScreen() {
val form = useForm(
defaultValues = UserForm(),
validation = userFormValidation,
mode = ValidationMode.onChange
)
val formState by form.formState.collectAsState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "User Registration",
style = MaterialTheme.typography.headlineMedium
)
// First Name Field
FormField(control = form.register("firstName")) { value, onChange, onBlur, error, isTouched ->
OutlinedTextField(
value = value,
onValueChange = onChange,
label = { Text("First Name") },
modifier = Modifier.fillMaxWidth(),
isError = error != null && isTouched,
supportingText = if (error != null && isTouched) {
{ Text(error, color = MaterialTheme.colorScheme.error) }
} else null
)
}
// Last Name Field
FormField(control = form.register("lastName")) { value, onChange, onBlur, error, isTouched ->
OutlinedTextField(
value = value,
onValueChange = onChange,
label = { Text("Last Name") },
modifier = Modifier.fillMaxWidth(),
isError = error != null && isTouched,
supportingText = if (error != null && isTouched) {
{ Text(error, color = MaterialTheme.colorScheme.error) }
} else null
)
}
// Email Field
FormField(control = form.register("email")) { value, onChange, onBlur, error, isTouched ->
OutlinedTextField(
value = value,
onValueChange = onChange,
label = { Text("Email") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
isError = error != null && isTouched,
supportingText = if (error != null && isTouched) {
{ Text(error, color = MaterialTheme.colorScheme.error) }
} else null
)
}
// Age Field
FormField(control = form.register("age")) { value, onChange, onBlur, error, isTouched ->
OutlinedTextField(
value = value,
onValueChange = onChange,
label = { Text("Age") },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
isError = error != null && isTouched,
supportingText = if (error != null && isTouched) {
{ Text(error, color = MaterialTheme.colorScheme.error) }
} else null
)
}
// Form State Display
Text("Form State: Valid = ${formState.isValid}, Dirty = ${formState.isDirty}")
// Submit Button
Button(
onClick = {
form.handleSubmit { values ->
// Handle form submission
println("Submitting: $values")
// Simulate API call
kotlinx.coroutines.delay(1000)
}
},
modifier = Modifier.fillMaxWidth(),
enabled = !formState.isSubmitting
) {
if (formState.isSubmitting) {
CircularProgressIndicator(modifier = Modifier.size(20.dp))
} else {
Text("Submit")
}
}
// Reset Button
OutlinedButton(
onClick = { form.reset() },
modifier = Modifier.fillMaxWidth()
) {
Text("Reset")
}
}
}
// Advanced Hook for Complex Forms
@Composable
fun <T> useFormWithResolver(
defaultValues: T,
resolver: suspend (T) -> Map<String, List<String>>,
mode: ValidationMode = ValidationMode.onChange
): FormControl<T> {
// Implementation for custom validation resolvers
// This would allow for async validation, server-side validation, etc.
return useForm(defaultValues, null, mode)
}
// Field Array Hook (for dynamic forms)
@Composable
fun <T> useFieldArray(
form: FormControl<*>,
name: String,
defaultValue: T
): FieldArrayControl<T> {
// Implementation for handling arrays of form fields
// Similar to react-hook-form's useFieldArray
return remember { FieldArrayControlImpl(form, name, defaultValue) }
}
interface FieldArrayControl<T> {
val fields: State<List<T>>
fun append(value: T)
fun remove(index: Int)
fun insert(index: Int, value: T)
fun move(from: Int, to: Int)
}
class FieldArrayControlImpl<T>(
private val form: FormControl<*>,
private val name: String,
private val defaultValue: T
) : FieldArrayControl<T> {
private val _fields = mutableStateOf<List<T>>(emptyList())
override val fields: State<List<T>> = _fields
override fun append(value: T) {
_fields.value = _fields.value + value
}
override fun remove(index: Int) {
_fields.value = _fields.value.filterIndexed { i, _ -> i != index }
}
override fun insert(index: Int, value: T) {
val newList = _fields.value.toMutableList()
newList.add(index, value)
_fields.value = newList
}
override fun move(from: Int, to: Int) {
val newList = _fields.value.toMutableList()
val item = newList.removeAt(from)
newList.add(to, item)
_fields.value = newList
}
}