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