Automated migration tool for Material 3 to Mudita Mindful Design (MMD) components
Seamlessly migrate your Jetpack Compose Material 3 components to MMD with intelligent, context-aware transformations.
- π Fast Regex-Based Migration - Process entire projects in seconds
- π― Context-Aware Replacements - Smart detection of component relationships
- π UTF-8 Validation - Ensures file encoding compatibility
- π Detailed Reports - Clear statistics and change summaries
- π§ͺ Dry-Run Mode - Preview changes before applying
- βοΈ Custom Mappings - Override default component mappings
- π§ Gradle Integration - Simple task-based workflow
- π¬ AST-Based Transformations - Precise syntax tree analysis
β οΈ Experimental Status - Use with caution, known limitations exist
In your build.gradle.kts:
plugins {
id("com.mudita.mmd-migrator") version "{version}"
}Using Simple Migration (Recommended):
# Preview changes (dry-run)
./gradlew migrateMmdSimpleDryRun
# Apply migration
./gradlew migrateMmdSimpleUsing OpenRewrite (Experimental):
# Preview changes (dry-run)
./gradlew migrateToMmdDryRun
# Apply migration
./gradlew migrateToMmdπ‘ Tip: Start with Simple Migration for best results. See Migration Methods for detailed comparison.
# Check compilation
./gradlew compileKotlin
# Run your tests
./gradlew testTransforms Material 3 components to their MMD equivalents:
// Before
import androidx.compose.material3.Button
import androidx.compose.material3.Text
Button(onClick = { }) {
Text("Click me")
}// After
import com.mudita.mmd.components.buttons.ButtonMMD
import com.mudita.mmd.components.text.TextMMD
ButtonMMD(onClick = { }) {
TextMMD("Click me")
}Intelligently migrates Defaults objects based on context:
// Before
TextField(
value = text,
onValueChange = { text = it },
colors = TextFieldDefaults.colors()
)
// After
TextFieldMMD(
value = text,
onValueChange = { text = it },
colors = TextFieldDefaultsMMD.colors()
)Smart Detection:
- β
TextFieldDefaultsinsideTextFieldβ migrated toTextFieldDefaultsMMD - β
TextFieldDefaultsinsideOutlinedTextFieldβ not migrated (OutlinedTextField isn't in MMD)
View full list (60+ components)
- Badge, BadgedBox
- Button, OutlinedButton, TextButton, IconButton
- Card, ElevatedCard, OutlinedCard
- Checkbox, TriStateCheckbox
- Switch
- RadioButton
- Text
- TextField
- Slider
- AssistChip, FilterChip, InputChip, SuggestionChip
- NavigationBar, NavigationBarItem
- TopAppBar
- Tab, TabRow, LeadingIconTab
- PrimaryTabRow, PrimaryScrollableTabRow
- SecondaryTabRow, SecondaryScrollableTabRow
- CircularProgressIndicator, LinearProgressIndicator
- ProgressIndicatorDefaults
- DatePicker, TimePicker
- rememberDatePickerState, rememberTimePickerState
- DropdownMenu, DropdownMenuItem
- ModalBottomSheet, rememberModalBottomSheetState
- HorizontalDivider, VerticalDivider
- Snackbar, SnackbarHost, SnackbarHostState
- SearchBar, SearchBarDefaults
- PlainTooltip (β TooltipMMD)
- TooltipBox
- FloatingActionButton, SmallFloatingActionButton
- LazyColumn, LazyRow
- MaterialTheme β ThemeMMD
=== MMD Simple Migration ===
Project: /path/to/your/project
Mode: DRY RUN
Mappings loaded: 156
UTF-8 encoding validation: enabled
HomeScreen.kt:
β Button -> ButtonMMD
β ButtonDefaults -> ButtonDefaultsMMD (inside ButtonMMD)
β Text -> TextMMD
β Card -> CardMMD
ProfileScreen.kt:
β TextField -> TextFieldMMD
β TextFieldDefaults -> TextFieldDefaultsMMD (inside TextFieldMMD)
β Switch -> SwitchMMD
============================================================
=== Migration Summary ===
============================================================
π Files:
Total analyzed: 42
Successfully migrated: 38
Skipped (no changes): 4
π Changes:
Total transformations: 156
Unique components: 12
π¦ Migrated components:
β’ Button
β’ Card
β’ Switch
β’ Text
β’ TextField
...
============================================================
βΉοΈ This was a DRY RUN - No files were modified
============================================================
To apply these changes, run:
./gradlew migrateMmdSimple
MMD Migrator provides two migration approaches with different trade-offs:
Fast, reliable regex-based migration for production use.
- β Fast: Process entire projects in seconds
- β Context-Aware: Smart detection of component relationships
- β UTF-8 Safe: Built-in encoding validation
- β Detailed Reports: Clear statistics and change summaries
- β Production Ready: Stable and well-tested
# Preview changes without modifying files
./gradlew migrateMmdSimpleDryRun
# Apply migration
./gradlew migrateMmdSimple
# With custom mappings
./gradlew migrateMmdSimple -PmappingPath=/path/to/custom-mapping.json- β Production projects
- β Large codebases
- β When you need reliable, predictable results
- β Standard Material 3 to MMD migrations
AST-based migration with precise syntax analysis - currently has limitations.
- π¬ AST-Based: Operates on Abstract Syntax Tree for precise transformations
- π― Syntax-Aware: Understands Kotlin code structure
β οΈ Experimental: Not recommended for production use
- β Unstable: May fail on some Kotlin constructs
- β Slow: Significantly longer execution time for large projects
- β Compatibility: Issues with some Gradle versions
- β Limited Context-Aware: Less sophisticated than Simple Migration
- β Configuration: May require additional setup
# Preview changes (dry-run)
./gradlew migrateToMmdDryRun
# Apply migration
./gradlew migrateToMmd- π¬ Experimental/research purposes
- π§ͺ Testing AST-based transformations
- π Learning about OpenRewrite capabilities
β οΈ NOT recommended for production projects
| Feature | Simple Migration | OpenRewrite Migration |
|---|---|---|
| Speed | β‘ Fast (seconds) | π’ Slow (minutes) |
| Stability | β Production-ready | |
| Context-Aware | β Advanced | |
| UTF-8 Support | β Full validation | |
| Reports | β Detailed | |
| Dry-Run | β Yes | β Yes |
| Custom Mappings | β Yes | β No |
| Recommended | β Yes | β No |
Recommendation: Use Simple Migration (migrateMmdSimple) for all production work. OpenRewrite is available for experimental purposes only.
Override default component mappings:
./gradlew migrateMmdSimple -PmappingPath=/path/to/custom-mapping.jsonMapping Format:
{
"components": [
{
"componentName": {
"m3": "Button",
"mmd": "ButtonMMD"
},
"additionalFields": {
"m3": ["ButtonDefaults"],
"mmd": ["ButtonDefaultsMMD"]
},
"sourcePackage": "androidx.compose.material3",
"targetPackage": "com.mudita.mmd.components.buttons",
"paramsToDrop": ["elevation", "interactionSource"]
}
]
}If you need to skip encoding validation:
// In your build.gradle.kts
tasks.named<com.mudita.migration.gradle.SimpleMigrationTask>("migrateMmdSimple") {
// Note: This is not directly configurable via task API yet
// UTF-8 validation is enabled by default for safety
}mmd-migrator/
βββ mmd-migrator-core/ # Core migration logic
β βββ src/main/kotlin/
β β βββ simple/ # Regex-based migrator (recommended)
β β β βββ SimpleMigrator.kt
β β β βββ core/ # CodeTransformer, MappingLoader
β β β βββ validation/ # FileEncodingValidator
β β β βββ logger/ # Logging adapters
β β βββ openrewrite/ # AST-based migrator (experimental)
β β βββ recipe/ # OpenRewrite recipes
β β β βββ ButtonMigrationRecipe.kt
β β β βββ ... # Other component recipes
β β βββ MigrationExecutor.kt
β βββ src/main/resources/
β βββ mapping.json # Component mappings for simple migrator
β βββ META-INF/rewrite/ # OpenRewrite recipe descriptors
β
βββ mmd-migrator-plugin/ # Gradle plugin wrapper
β βββ src/main/kotlin/
β βββ gradle/
β βββ MmdMigratorPlugin.kt
β βββ SimpleMigrationTask.kt # Simple migration task
β βββ OpenRewriteMigrationTask.kt # OpenRewrite migration task
β
βββ README.md
The migrator uses intelligent context detection to avoid breaking code:
// File contains both TextField (migrated) and OutlinedTextField (not migrated)
@Composable
fun MyForm() {
TextField(
value = text1,
colors = TextFieldDefaults.colors() // β
Migrated to TextFieldDefaultsMMD
)
OutlinedTextField(
value = text2,
colors = TextFieldDefaults.colors() // β Not migrated (OutlinedTextField not in MMD)
)
}// File uses Button (migrated) and TextButton (not migrated)
@Composable
fun MyButtons() {
Button(
colors = ButtonDefaults.buttonColors() // β
Migrated
) { }
TextButton(
colors = ButtonDefaults.textButtonColors() // β Not migrated
) { }
}Migration happens in phases:
- Phase 1: Context-aware components (import-based)
- Phase 1.5: Defaults inside already-migrated MMD components
- Phase 2: Regular components
- Phase 3: Smart property-based replacements
- Phase 4: Revert unmigrated Defaults methods
- Phase 5: Revert Defaults inside unmigrated siblings
# Build and publish to Maven Local
./gradlew publishToMavenLocal
# Use in another project
# In settings.gradle.kts of target project:
pluginManagement {
repositories {
mavenLocal()
gradlePluginPortal()
}
}# All tests
./gradlew test
# Simple migration tests only
./gradlew :mmd-migrator-core:test --tests "*simple*"
# Integration tests
./gradlew :mmd-migrator-core:test --tests "*Integration*"
# With verbose output
./gradlew test --info- Clone the repository
- Build the plugin:
./gradlew publishToMavenLocal - Create a test project with the plugin applied
- Iterate:
- Make changes in
mmd-migrator-coreormmd-migrator-plugin - Run
./gradlew publishToMavenLocal - Test in your test project
- Make changes in
- Kotlin: 2.1.0 or newer
- Gradle: 7.x or newer
- JDK: 11 or newer
- Project Type: Android or Kotlin with Jetpack Compose
The regex-based approach may not perfectly handle multi-line triple-quoted strings:
val text = """
This is a
multi-line string
with Text("content") // Might be incorrectly replaced
"""Workaround: Review changes carefully in files with extensive multi-line strings.
Components resolved at runtime won't be detected:
val componentName = if (condition) "Button" else "Card"
// Cannot be migrated automaticallyComponents accessed via reflection are not migrated:
val componentClass = Class.forName("androidx.compose.material3.Button")Issue: No files found or some files skipped.
Solution: The migrator only processes files that:
- Have
.ktextension - Contain
@Composableannotation - Import from
androidx.compose.material3orandroidx.compose.foundation.lazy
Check if your files meet these criteria.
Issue: File encoding validation failed
Solution:
- Ensure your files are UTF-8 encoded
- Check for BOM (Byte Order Mark) - the migrator preserves it
- Look for mixed line endings (CRLF vs LF)
Issue: Some components were migrated incorrectly.
Solution:
- Run dry-run first:
./gradlew migrateMmdSimpleDryRun - Review the changes carefully
- Use version control to diff changes
- Report issues with specific examples
Issue: Plugin with id 'com.mudita.mmd-migrator' not found
Solution:
- Ensure you've published to Maven Local:
./gradlew publishToMavenLocal - Check your
settings.gradle.ktsincludesmavenLocal() - Verify the version number matches
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
./gradlew test - Submit a pull request
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Built with OpenRewrite for experimental AST-based transformations
- Powered by Kotlin and Gradle
- Inspired by the need for seamless design system migrations
- Primary migration engine uses custom regex-based approach for reliability and performance
- Issues: GitHub Issues
- Documentation: This README
Made with β€οΈ by Mudita