Mehedi Hassan Piash | Senior Software Engineer | Android | iOS | KMP | Ktor | Jetpack Compose | React-Native.

May 26, 2026

The Engineering Culture Playbook: Building High-Performing Software Teams

May 26, 2026 Posted by Piash No comments

 There’s a quiet truth that most engineering leaders eventually discover: technical skill gets you to the table, but culture determines what you build there.

I’ve spent time studying what high-performance software teams have in common — and it isn’t genius-level architecture or the trendiest tech stack. It’s two deeply interconnected things: how they build and how they connect. Process discipline and human decency, woven together into something powerful.

This is that playbook.

Press enter or click to view image in full size

🛠️ Part One: Software Development Culture — How We Build

01 — Process-Driven Development Isn’t Bureaucracy. It’s Respect.

When a team has no process, the people who suffer most are the ones who care the most. They’re left guessing sprint goals, navigating chaotic branches, and wondering whether their commit just broke production.

Process-driven development means:

  • Sprint planning — clear goals, story points, and acceptance criteria that everyone actually understands
  • A branching strategy everyone follows — Git Flow or trunk-based, with feature branches, pull requests, and a protected main branch
  • CI/CD pipelines that verify every commit — automate the boring stuff so humans can focus on the interesting stuff
  • Semantic versioning (MAJOR.MINOR.PATCH) — communicate the impact of changes clearly, every time
  • Clean code — enforced not by policing, but by automation and shared standards
  • Documentation written as you build — not as an afterthought, not “when we have time”

02 — Code Review: A Culture of Learning, Not Judgment

Here’s a test for your team’s code review culture: does opening a pull request feel like submitting work to be graded, or sharing something you’re proud of?

The best teams treat code review as a teaching moment, not a gatekeeping ritual.

The principles that make it work:

  • All code goes through peer review before merge — no exceptions, no ego-based shortcuts
  • Reviewers comment on code, never on the person
  • At least 2 approvals on critical changes
  • Turnaround within 24 hours — because leaving a colleague’s PR open for days is a silent message that their time doesn’t matter
  • API docs kept current via Swagger / OpenAPI 3.0 — outdated docs = wasted dev hours

03 — Retrospectives: Build a Culture of Learning, Not Blame

Sprint retrospectives are one of the most misused rituals in software development. Done poorly, they’re theater. Done well, they’re the mechanism by which teams actually get better.

WhenFocusAfter every sprintWhat went well? What can improve?After every projectDeep dive — timelines, blockers, winsAlwaysAction items with owners + follow-through

The magic is in the mindset shift: we’re here to improve the system, not to find someone to blame. A blame culture silences the people who noticed the problem first.

04 — Knowledge Sharing: The Compounding Investment

Every piece of knowledge that lives only in one person’s head is a liability.

High-performing teams invest in knowledge transfer continuously:

  • Bi-weekly tech talks — internal demos and lightning talks keep everyone learning
  • Living documentation — Confluence, Notion, or a wiki that’s actually maintained
  • Dev channels — Slack or Teams threads where articles, libraries, and tools get shared
  • Pair programming — real-time knowledge transfer that beats any brown-bag session

The teams that share knowledge generously are the ones that can absorb departures, scale quickly, and onboard new members without grinding to a halt.

05 — Mentoring Juniors: The Multiplier Effect

Senior engineers who invest in juniors don’t slow down. They multiply their impact.

What meaningful mentorship looks like in practice:

  • Guiding juniors day-to-day, not just during onboarding
  • Using code reviews as explicit teaching moments
  • Creating a culture where no question is a dumb question
  • Pairing on complex tickets
  • Setting real growth goals — not vague aspirations

Every junior engineer you develop well is future leverage for the entire team.

06 — Risk Communication: A Risk Flagged Early Is a Crisis Avoided

This one is deceptively simple and consistently violated.

The rule: Flag blockers and delays early. Never wait.

When something is going wrong, the instinct is often to solve it quietly first — to not look bad. But the team that finds out about a delay on the day of the deadline is the team that can’t help you. The team that finds out three days earlier might have exactly what you need.

How to raise a risk well:

  • Communicate during standup or async, same day you know
  • Include the impact and your proposed solution — don’t just throw a problem over the fence
  • No blame — raise the issue, solve it together

07 — Timeliness & Accountability: Trust Is Built in Small Moments

Accountability isn’t about surveillance. It’s about being someone your team can count on.

  • Meet sprint commitments
  • Join standups and meetings on time
  • Deliver what you committed to during sprint planning
  • Communicate if timelines change — don’t go silent

Silence is not neutral. When a teammate goes quiet on a commitment, it doesn’t just affect the sprint — it affects how much the team can trust them next time.

🤝 Part Two: Communication & Soft Culture — How We Connect

08 — Standups in English: Every Voice Heard

If your team spans multiple backgrounds, running standups in a shared language — English — isn’t about formality. It’s about inclusion.

When some team members can follow the conversation and others can’t, you don’t have one team. You have two groups who happen to sit near each other.

English standups ensure cross-team alignment and make every voice genuinely heard.

09 — Proactive Communication: Don’t Wait to Be Asked

The question to ask yourself before going home each day: Is there anything someone on this team needs to know that they don’t know yet?

Proactive communication means:

  • Sharing updates, progress, and blockers before someone follows up
  • Not waiting to be asked for a status — offering it
  • Treating communication as part of the job, not an interruption to it

The most frustrating teammate isn’t the one who struggles technically. It’s the one who goes quiet and surfaces problems too late for anyone to help.

10 — Constructive Feedback & Appreciation

There’s a version of feedback that makes people defensive and a version that makes them better. The difference is almost entirely in the framing.

Constructive feedback:

  • Critiques the idea or work, never the person
  • Is specific — “this function does too many things” beats “this is messy”
  • Is kind and solution-focused
  • Ends with encouragement

And on the flip side — celebrate wins, big and small. A shoutout in standup. A quick Slack message. A public acknowledgment. These gestures cost almost nothing and mean more than most managers realize.

11 — Respect, Ego, and Psychological Safety

These three things are deeply connected.

Respect flows in every direction — not just up to seniority, but across and down. Titles don’t define a person’s worth. Cultural and personal differences aren’t obstacles; they’re strengths.

Ego out of technical decisions. When the best idea wins rather than the loudest voice, you get better software and a healthier team. Code review comments are not personal attacks.

Psychological safety is the foundation everything else rests on:

Leaders set the tone here by being willing to be vulnerable themselves.

12 — Personal Wellbeing: Your Best Code Comes From Your Best Self

A team running on chronic sleep deprivation, skipped meals, and unmanaged stress is not operating at anything close to its potential — regardless of the hours logged.

What to protectWhy it matters7–8 hours of sleepMeasurably improves decision-making and code qualityPhysical healthRegular movement and breaks keep your mind sharpProfessional presenceHow you show up reflects respect for your team and clientsMental wellbeingA supported engineer outperforms a burned-out one every time

Speak up if you’re overwhelmed. The best investment a team can make in engineering quality is in the health of its engineers.

The Culture Is the Competitive Advantage

Here’s the through-line in everything above: culture is not a soft concern.

Process discipline reduces bugs and deployment failures. Knowledge sharing reduces bus factor. Psychological safety surfaces risks before they become crises. Constructive feedback accelerates growth. Wellbeing sustains performance over time.

These aren’t nice-to-haves. They’re the infrastructure that makes great software possible.

The teams that build the best products aren’t the ones with the most talented individuals. They’re the ones where talented people can actually do their best work — because the culture lets them.

February 12, 2026

AI Tools I Use Daily for Software Development

February 12, 2026 Posted by Piash , , No comments

 In recent software development, it’s hard to imagine our work without AI — or at least without taking help from it. I’m no different. I use AI in my daily life for both personal tasks and software development, and it has significantly improved my productivity, efficiency, and delivery speed.

Today, I’d like to share the AI tools I regularly use in my development workflow and how each of them helps me work smarter and ship faster.

Press enter or click to view image in full size

Claude

I use Claude by Anthropic for UI design, layout planning, and UX thinking because it’s strong at understanding visual structure and user flows. It helps me refine screens and component structure before I write code.

👉 Explore Claude: https://claude.com/

📝 Documentation & Small Tasks —

ChatGPT

For documentation, refactoring small functions, quick fixes, and snippets, I rely on ChatGPT. It’s fast, produces clear explanations, and is great for everyday development support.

👉 Try ChatGPT: https://chat.openai.com/

🧠 Complex Logic —

Antigravity

For deep business logic, multi-step reasoning, and complicated engineering problems, I use Antigravity — Google’s agent-first AMIDE-style development platform where AI agents can plan, code, and verify complex tasks across your editor and tools.

👉 Explore Antigravity: https://antigravity.google/

🤖 Agentic Coding —

Qwen Code

My favorite tool for agentic programming is Qwen Code from Alibaba Cloud. It excels at understanding codebases, managing multi-file changes, and automating big refactors or feature builds.

👉 See Qwen Code docs: https://www.alibabacloud.com/help/en/model-studio/qwen-code

🔄 My AI Workflow

I choose tools depending on the task:

  • UI & UX → Claude
  • Docs & small tasks → ChatGPT
  • Complex logic → Antigravity
  • Agentic coding → Qwen Code

October 25, 2025

ZodKmp: Kotlin Multiplatform Validation

October 25, 2025 Posted by Piash , , , No comments

Data validation is one of the most overlooked yet crucial parts of software development. Whether you’re building a mobile app, web app, or backend API — ensuring that your data is correct, consistent, and safe to process is non-negotiable. That’s where ZodKmp comes in. ZodKmp is a Kotlin Multiplatform (KMP) implementation of the popular Zod TypeScript validation library. It provides a declarativetype-safe, and cross-platform way to validate your data models across AndroidiOSDesktop, and Web — all from one shared Kotlin codebase. For detailed documentation .

Installation

Add the following to your build.gradle.kts:

kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.github.piashcse:zodkmp:1.2.0")
}
}
}
}

Version Catalog (libs.versions.toml)

[versions]
zodkmp = "1.2.0"

[libraries]
zodkmp = { module = "io.github.piashcse:zodkmp", version.ref = "zodkmp" }

Getting Started

ZodKmp allows you to define validation schemas and use them to validate data:

import com.piashcse.zodkmp.Zod
// Define a schema
val userSchema = Zod.objectSchema<User>({
string("name", Zod.string().min(2))
string("email", Zod.string().email())
number("age", Zod.number().min(0).max(120))
}) { map ->
User(
name = map["name"] as String,
email = map["email"] as String,
age = (map["age"] as Number).toDouble()
)
}
// Use the schema
val userData = mapOf(
"name" to "John Doe",
"email" to "john@example.com",
"age" to 30.0
)
val result = userSchema.safeParse(userData)
when (result) {
is ZodResult.Success -> println("Valid user: ${result.data}")
is ZodResult.Failure -> println("Validation errors: ${result.error.errors}")
}

Full Example Code (Shown in Screenshot)

package com.piashcse.zodkmp

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Category
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Label
import androidx.compose.material.icons.filled.Science
import androidx.compose.material.icons.outlined.AdsClick
import androidx.compose.material.icons.outlined.Category
import androidx.compose.material.icons.outlined.ControlPoint
import androidx.compose.material.icons.outlined.Dataset
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material.icons.outlined.JoinFull
import androidx.compose.material.icons.outlined.Key
import androidx.compose.material.icons.outlined.List
import androidx.compose.material.icons.outlined.Numbers
import androidx.compose.material.icons.outlined.Reorder
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material.icons.outlined.Science
import androidx.compose.material.icons.outlined.TextFields
import androidx.compose.material.icons.outlined.ToggleOn
import androidx.compose.material.icons.outlined.Transform
import androidx.compose.material.icons.outlined.ViewList
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ZodKmpModernExample() {
var selectedTab by remember { mutableStateOf(0) }

Scaffold(
topBar = {
TopAppBar(
title = {
Text(
"ZodKmp",
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold
)
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary
)
)
},
bottomBar = {
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Filled.Home, contentDescription = null) },
label = { Text("Basics") },
selected = selectedTab == 0,
onClick = { selectedTab = 0 }
)
NavigationBarItem(
icon = { Icon(Icons.Filled.Category, contentDescription = null) },
label = { Text("Advanced") },
selected = selectedTab == 1,
onClick = { selectedTab = 1 }
)
NavigationBarItem(
icon = { Icon(Icons.Filled.Science, contentDescription = null) },
label = { Text("Transform") },
selected = selectedTab == 2,
onClick = { selectedTab = 2 }
)
}
}
) { paddingValues ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
) {
when (selectedTab) {
0 -> BasicValidationScreen()
1 -> AdvancedValidationScreen()
2 -> TransformationScreen()
}
}
}
}

@Composable
fun BasicValidationScreen() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
HeaderCard(
title = "Basic Validation",
description = "Fundamental data validation with ZodKmp",
icon = Icons.Filled.CheckCircle
)
}

item {
StringValidationCard()
}

item {
NumberValidationCard()
}

item {
BooleanValidationCard()
}

item {
LiteralValidationCard()
}

item {
EnumValidationCard()
}

item {
FooterSpacer()
}
}
}

@Composable
fun AdvancedValidationScreen() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
HeaderCard(
title = "Advanced Validation",
description = "Complex data structures and schemas",
icon = Icons.Outlined.Category
)
}

item {
ArrayValidationCard()
}

item {
ObjectValidationCard()
}

item {
UnionValidationCard()
}

item {
TupleValidationCard()
}

item {
RecordValidationCard()
}

item {
NullableValidationCard()
}

item {
FooterSpacer()
}
}
}

@Composable
fun TransformationScreen() {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
item {
HeaderCard(
title = "Transformations & Refinements",
description = "Modify and enhance your data validation",
icon = Icons.Outlined.Science
)
}

item {
TransformValidationCard()
}

item {
RefineValidationCard()
}

item {
DefaultValidationCard()
}

item {
ConditionalValidationCard()
}

item {
FooterSpacer()
}
}
}

@Composable
fun HeaderCard(title: String, description: String, icon: androidx.compose.ui.graphics.vector.ImageVector) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(24.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.size(48.dp)
)
Column {
Text(
text = title,
style = MaterialTheme.typography.headlineSmall.copy(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
)
Text(
text = description,
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f)
),
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}

@Composable
fun ValidationCard(
title: String,
icon: androidx.compose.ui.graphics.vector.ImageVector,
content: @Composable () -> Unit
)
{
Card(
modifier = Modifier.fillMaxWidth(),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
Text(
text = title,
style = MaterialTheme.typography.titleMedium.copy(
fontWeight = FontWeight.SemiBold
),
modifier = Modifier.padding(start = 8.dp)
)
}

content()
}
}
}

@Composable
fun StringValidationCard() {
ValidationCard(
title = "String Validation",
icon = Icons.Outlined.TextFields
) {
var text by remember { mutableStateOf("hello@example.com") }
var validationResult by remember { mutableStateOf<ZodResult<String>?>(null) }

val emailSchema = remember { Zod.string().email() }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
validationResult = emailSchema.safeParse(it)
},
label = { Text("Email Address") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Try: john@example.com or invalid-email",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun NumberValidationCard() {
ValidationCard(
title = "Number Validation",
icon = Icons.Outlined.Numbers
) {
var text by remember { mutableStateOf("25") }
var validationResult by remember { mutableStateOf<ZodResult<Double>?>(null) }

val ageSchema = remember { Zod.number().min(0.0).max(120.0) }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
val number = try { it.toDouble() } catch (e: Exception) { null }
validationResult = if (number != null) ageSchema.safeParse(number) else ZodResult.Failure(ZodError(listOf("Invalid number")))
},
label = { Text("Age (0-120)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Try: 25, -5, or 150",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun BooleanValidationCard() {
ValidationCard(
title = "Boolean Validation",
icon = Icons.Outlined.ToggleOn
) {
var isChecked by remember { mutableStateOf(true) }
var validationResult by remember { mutableStateOf<ZodResult<Boolean>?>(null) }

val booleanSchema = remember { Zod.boolean() }

Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Switch(
checked = isChecked,
onCheckedChange = {
isChecked = it
validationResult = booleanSchema.safeParse(it)
}
)
Text(
text = if (isChecked) "Enabled" else "Disabled",
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 12.dp)
)
}

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Toggle the switch to see validation in action",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun LiteralValidationCard() {
ValidationCard(
title = "Literal Validation",
icon = Icons.Filled.Label
) {
var text by remember { mutableStateOf("active") }
var validationResult by remember { mutableStateOf<ZodResult<String>?>(null) }

val statusSchema = remember { Zod.literal("active") }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
validationResult = statusSchema.safeParse(it)
},
label = { Text("Status (must be 'active')") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Only 'active' is valid",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun EnumValidationCard() {
ValidationCard(
title = "Enum Validation",
icon = Icons.Outlined.List
) {
var selectedRole by remember { mutableStateOf("user") }
var validationResult by remember { mutableStateOf<ZodResult<String>?>(null) }

val roleSchema = remember { Zod.enum("admin", "user", "guest") }

Column {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
listOf("admin", "user", "guest").forEach { role ->
FilterChip(
selected = selectedRole == role,
onClick = {
selectedRole = role
validationResult = roleSchema.safeParse(role)
},
label = { Text(role.replaceFirstChar { it.uppercase() }) }
)
}
}

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Select a role from the chips above",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun ArrayValidationCard() {
ValidationCard(
title = "Array Validation",
icon = Icons.Outlined.ViewList
) {
var tags by remember { mutableStateOf("tag1,tag2,tag3") }
var validationResult by remember { mutableStateOf<ZodResult<List<String>>?>(null) }

val tagsSchema = remember { Zod.array(Zod.string().min(2)).min(1).max(5) }

Column {
OutlinedTextField(
value = tags,
onValueChange = {
tags = it
val tagList = it.split(",").map { tag -> tag.trim() }.filter { it.isNotEmpty() }
validationResult = tagsSchema.safeParse(tagList)
},
label = { Text("Tags (comma separated, 1-5 items, min 2 chars each)") },
modifier = Modifier.fillMaxWidth(),
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Try: tag1,tag2 or tag (too short)",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun ObjectValidationCard() {
ValidationCard(
title = "Object Validation",
icon = Icons.Outlined.Dataset
) {
var name by remember { mutableStateOf("John Doe") }
var email by remember { mutableStateOf("john@example.com") }
var age by remember { mutableStateOf("30") }
var validationResult by remember { mutableStateOf<ZodResult<Map<String, Any?>>?>(null) }

val userSchema = Zod.objectSchema<Map<String, Any?>>({
string("name", Zod.string().min(2))
string("email", Zod.string().email())
number("age", Zod.number().min(0.0).max(120.0))
}) { map -> map }

Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedTextField(
value = name,
onValueChange = {
name = it
validateUserObject(userSchema, name, email, age) { result -> validationResult = result }
},
label = { Text("Name") },
modifier = Modifier.fillMaxWidth()
)

OutlinedTextField(
value = email,
onValueChange = {
email = it
validateUserObject(userSchema, name, email, age) { result -> validationResult = result }
},
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)

OutlinedTextField(
value = age,
onValueChange = {
age = it
validateUserObject(userSchema, name, email, age) { result -> validationResult = result }
},
label = { Text("Age") },
modifier = Modifier.fillMaxWidth()
)

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

ValidationStatusDisplay(validationResult)
}
}
}

fun validateUserObject(
schema: ZodObjectSchema<Map<String, Any?>>,
name: String,
email: String,
age: String,
onResult: (ZodResult<Map<String, Any?>>) -> Unit
)
{
val ageValue = try { age.toDouble() } catch (e: Exception) { null }
if (ageValue != null) {
val userData = mapOf(
"name" to name,
"email" to email,
"age" to ageValue
)
onResult(schema.safeParse(userData))
}
}

@Composable
fun UnionValidationCard() {
ValidationCard(
title = "Union Validation",
icon = Icons.Outlined.JoinFull
) {
var inputValue by remember { mutableStateOf("hello") }
var validationResult by remember { mutableStateOf<ZodResult<Any?>?>(null) }

val stringOrNumberSchema = remember { Zod.union(Zod.string(), Zod.number()) }

Column {
OutlinedTextField(
value = inputValue,
onValueChange = {
inputValue = it
val parsedValue = try {
if (it.toDoubleOrNull() != null) it.toDouble() else it
} catch (e: Exception) { it }
validationResult = stringOrNumberSchema.safeParse(parsedValue)
},
label = { Text("String or Number") },
modifier = Modifier.fillMaxWidth(),
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Try: hello or 42",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun TupleValidationCard() {
ValidationCard(
title = "Tuple Validation",
icon = Icons.Outlined.Reorder
) {
var x by remember { mutableStateOf("10") }
var y by remember { mutableStateOf("20") }
var validationResult by remember { mutableStateOf<ZodResult<List<Any?>>?>(null) }

val coordinateSchema = remember { Zod.tuple(listOf(Zod.number(), Zod.number())) }

Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedTextField(
value = x,
onValueChange = {
x = it
validateCoordinate(coordinateSchema, x, y) { result -> validationResult = result }
},
label = { Text("X Coordinate") },
modifier = Modifier.weight(1f)
)

OutlinedTextField(
value = y,
onValueChange = {
y = it
validateCoordinate(coordinateSchema, x, y) { result -> validationResult = result }
},
label = { Text("Y Coordinate") },
modifier = Modifier.weight(1f)
)
}

ValidationStatusDisplay(validationResult)
}
}
}

fun validateCoordinate(
schema: ZodTuple,
x: String,
y: String,
onResult: (ZodResult<List<Any?>>) -> Unit
)
{
val xValue = try { x.toDouble() } catch (e: Exception) { null }
val yValue = try { y.toDouble() } catch (e: Exception) { null }

if (xValue != null && yValue != null) {
val coordinate = listOf(xValue, yValue)
onResult(schema.safeParse(coordinate))
}
}

@Composable
fun RecordValidationCard() {
ValidationCard(
title = "Record Validation",
icon = Icons.Outlined.Key
) {
val recordSchema = remember { Zod.record(Zod.string()) }

Column {
Text(
text = "Records validate objects with string keys",
style = MaterialTheme.typography.bodyMedium
)

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

val sampleRecord = remember { mapOf("firstName" to "John", "lastName" to "Doe") }
val result = remember { recordSchema.safeParse(sampleRecord) }

ValidationStatusDisplay(result)

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

Text(
text = "Sample record validated automatically",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun NullableValidationCard() {
ValidationCard(
title = "Nullable Validation",
icon = Icons.Outlined.HelpOutline
) {
var text by remember { mutableStateOf("") }
var validationResult by remember { mutableStateOf<ZodResult<String?>?>(null) }

val nullableSchema = remember { Zod.string().nullable() }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
val value = if (it.isEmpty()) null else it
validationResult = nullableSchema.safeParse(value)
},
label = { Text("Nullable String (can be empty)") },
modifier = Modifier.fillMaxWidth()
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Leave empty to test null value",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun TransformValidationCard() {
ValidationCard(
title = "Transform Validation",
icon = Icons.Outlined.Transform
) {
var text by remember { mutableStateOf("hello world") }
var validationResult by remember { mutableStateOf<ZodResult<String>?>(null) }

val uppercaseSchema = remember { Zod.string().transform { it.uppercase() } }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
validationResult = uppercaseSchema.safeParse(it)
},
label = { Text("Text to Uppercase") },
modifier = Modifier.fillMaxWidth()
)

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

validationResult?.let { result ->
when (result) {
is ZodResult.Success -> {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
),
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Transformed: ${result.data}",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.padding(16.dp)
)
}
}
is ZodResult.Failure -> {
ErrorMessage(result.error.errors.joinToString(", "))
}
}
}

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

Text(
text = "Enter text to see it transformed to uppercase",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun RefineValidationCard() {
ValidationCard(
title = "Refinement Validation",
icon = Icons.Outlined.AdsClick
) {
var text by remember { mutableStateOf("42") }
var validationResult by remember { mutableStateOf<ZodResult<Double>?>(null) }

val evenNumberSchema = remember {
Zod.number().refine({ it.toInt() % 2 == 0 }) { "Number must be even" }
}

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
val number = try { it.toDouble() } catch (e: Exception) { null }
validationResult = if (number != null) evenNumberSchema.safeParse(number) else ZodResult.Failure(ZodError(listOf("Invalid number")))
},
label = { Text("Even Number") },
modifier = Modifier.fillMaxWidth(),
isError = validationResult is ZodResult.Failure
)

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

ValidationStatusDisplay(validationResult)

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

Text(
text = "Try: 42 (even) or 43 (odd)",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun DefaultValidationCard() {
ValidationCard(
title = "Default Values",
icon = Icons.Outlined.Restore
) {
var text by remember { mutableStateOf("") }
var validationResult by remember { mutableStateOf<ZodResult<String>?>(null) }

val stringWithDefault = remember { Zod.string().default("default_value") }

Column {
OutlinedTextField(
value = text,
onValueChange = {
text = it
val value = if (it.isEmpty()) null else it
validationResult = stringWithDefault.safeParse(value)
},
label = { Text("String (empty = default)") },
modifier = Modifier.fillMaxWidth()
)

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

validationResult?.let { result ->
when (result) {
is ZodResult.Success -> {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.tertiaryContainer
),
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Value: '${result.data}'",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onTertiaryContainer,
modifier = Modifier.padding(16.dp)
)
}
}
is ZodResult.Failure -> {
ErrorMessage(result.error.errors.joinToString(", "))
}
}
}

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

Text(
text = "Leave empty to see default value applied",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}

@Composable
fun ConditionalValidationCard() {
ValidationCard(
title = "Conditional Validation",
icon = Icons.Outlined.ControlPoint
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = Icons.Outlined.Science,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(48.dp)
)

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

Text(
text = "Advanced Conditional Validation",
style = MaterialTheme.typography.titleMedium,
textAlign = TextAlign.Center
)

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

Text(
text = "Combine multiple schemas with conditional logic",
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
)

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

Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
),
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "✓ Complex validation rules\n✓ Cross-field validation\n✓ Dynamic schema selection",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(16.dp)
)
}
}
}
}

@Composable
fun ValidationStatusDisplay(result: ZodResult<*>?) {
result?.let { res ->
when (res) {
is ZodResult.Success -> {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primaryContainer
),
modifier = Modifier.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp)
) {
Icon(
imageVector = Icons.Filled.CheckCircle,
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
Text(
text = "Valid input",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}
is ZodResult.Failure -> {
ErrorMessage(res.error.errors.joinToString(", "))
}
}
}
}

@Composable
fun ErrorMessage(message: String) {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer
),
modifier = Modifier.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(16.dp)
) {
Icon(
imageVector = Icons.Filled.Error,
contentDescription = null,
tint = MaterialTheme.colorScheme.onErrorContainer
)
Text(
text = message,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
modifier = Modifier.padding(start = 8.dp)
)
}
}
}

@Composable
fun FooterSpacer() {
Spacer(modifier = Modifier.height(32.dp))
}

Github repo: https://github.com/piashcse/ZodKmp
Example Code: https://github.com/piashcse/ZodKmpModernExample.kt
Medium: https://piashcse.medium.com/zodkmp-kotlin-multiplatform-validation-f2f5d702d4d4