Skip to content

mudita/mmd-migrator

Repository files navigation

πŸ”„ MMD Migrator

Automated migration tool for Material 3 to Mudita Mindful Design (MMD) components

Kotlin Gradle Plugin License

Seamlessly migrate your Jetpack Compose Material 3 components to MMD with intelligent, context-aware transformations.


✨ Features

Simple Migration (Recommended)

  • πŸš€ 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

OpenRewrite Migration (Experimental)

  • πŸ”¬ AST-Based Transformations - Precise syntax tree analysis
  • ⚠️ Experimental Status - Use with caution, known limitations exist

πŸš€ Quick Start

1. Add the Plugin

In your build.gradle.kts:

plugins {
    id("com.mudita.mmd-migrator") version "{version}"
}

2. Run Migration

Using Simple Migration (Recommended):

# Preview changes (dry-run)
./gradlew migrateMmdSimpleDryRun

# Apply migration
./gradlew migrateMmdSimple

Using 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.

3. Review & Compile

# Check compilation
./gradlew compileKotlin

# Run your tests
./gradlew test

πŸ“– What Gets Migrated

Components

Transforms 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")
}

Defaults & Properties (Context-Aware)

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:

  • βœ… TextFieldDefaults inside TextField β†’ migrated to TextFieldDefaultsMMD
  • ❌ TextFieldDefaults inside OutlinedTextField β†’ not migrated (OutlinedTextField isn't in MMD)

Supported Components

View full list (60+ components)

Basic Components

  • Badge, BadgedBox
  • Button, OutlinedButton, TextButton, IconButton
  • Card, ElevatedCard, OutlinedCard
  • Checkbox, TriStateCheckbox
  • Switch
  • RadioButton
  • Text

Input Components

  • TextField
  • Slider

Chips

  • AssistChip, FilterChip, InputChip, SuggestionChip

Navigation

  • NavigationBar, NavigationBarItem
  • TopAppBar

Tabs

  • Tab, TabRow, LeadingIconTab
  • PrimaryTabRow, PrimaryScrollableTabRow
  • SecondaryTabRow, SecondaryScrollableTabRow

Progress & Loading

  • CircularProgressIndicator, LinearProgressIndicator
  • ProgressIndicatorDefaults

Pickers

  • DatePicker, TimePicker
  • rememberDatePickerState, rememberTimePickerState

Menus & Dropdowns

  • DropdownMenu, DropdownMenuItem

Dialogs & Sheets

  • ModalBottomSheet, rememberModalBottomSheetState

Layout & Dividers

  • HorizontalDivider, VerticalDivider

Feedback

  • Snackbar, SnackbarHost, SnackbarHostState

Search

  • SearchBar, SearchBarDefaults

Tooltips

  • PlainTooltip (β†’ TooltipMMD)
  • TooltipBox

Buttons (Extended)

  • FloatingActionButton, SmallFloatingActionButton

Lazy Lists

  • LazyColumn, LazyRow

Theme

  • MaterialTheme β†’ ThemeMMD

πŸ“Š Migration Output

Console Report (Dry-Run)

=== 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

πŸ”€ Migration Methods

MMD Migrator provides two migration approaches with different trade-offs:

Method 1: Simple Migration (Recommended) πŸš€

Fast, reliable regex-based migration for production use.

Features

  • βœ… 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

Usage

# Preview changes without modifying files
./gradlew migrateMmdSimpleDryRun

# Apply migration
./gradlew migrateMmdSimple

# With custom mappings
./gradlew migrateMmdSimple -PmappingPath=/path/to/custom-mapping.json

When to Use

  • βœ… Production projects
  • βœ… Large codebases
  • βœ… When you need reliable, predictable results
  • βœ… Standard Material 3 to MMD migrations

Method 2: OpenRewrite Migration (Experimental) ⚠️

AST-based migration with precise syntax analysis - currently has limitations.

Features

  • πŸ”¬ AST-Based: Operates on Abstract Syntax Tree for precise transformations
  • 🎯 Syntax-Aware: Understands Kotlin code structure
  • ⚠️ Experimental: Not recommended for production use

Known Issues

  • ❌ 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

Usage

# Preview changes (dry-run)
./gradlew migrateToMmdDryRun

# Apply migration
./gradlew migrateToMmd

When to Use

  • πŸ”¬ Experimental/research purposes
  • πŸ§ͺ Testing AST-based transformations
  • πŸ“š Learning about OpenRewrite capabilities
  • ⚠️ NOT recommended for production projects

Comparison

Feature Simple Migration OpenRewrite Migration
Speed ⚑ Fast (seconds) 🐒 Slow (minutes)
Stability βœ… Production-ready ⚠️ Experimental
Context-Aware βœ… Advanced ⚠️ Limited
UTF-8 Support βœ… Full validation ⚠️ Basic
Reports βœ… Detailed ⚠️ Basic
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.


🎯 Advanced Usage

Custom Mapping File

Override default component mappings:

./gradlew migrateMmdSimple -PmappingPath=/path/to/custom-mapping.json

Mapping 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"]
    }
  ]
}

Disable UTF-8 Validation

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
}

πŸ—οΈ Project Structure

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

🧠 How It Works: Context-Aware Detection

The migrator uses intelligent context detection to avoid breaking code:

Example 1: Mixed Component Usage

// 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)
    )
}

Example 2: Button Variants

// File uses Button (migrated) and TextButton (not migrated)

@Composable
fun MyButtons() {
    Button(
        colors = ButtonDefaults.buttonColors()  // βœ… Migrated
    ) { }
    
    TextButton(
        colors = ButtonDefaults.textButtonColors()  // ❌ Not migrated
    ) { }
}

Multi-Pass Processing

Migration happens in phases:

  1. Phase 1: Context-aware components (import-based)
  2. Phase 1.5: Defaults inside already-migrated MMD components
  3. Phase 2: Regular components
  4. Phase 3: Smart property-based replacements
  5. Phase 4: Revert unmigrated Defaults methods
  6. Phase 5: Revert Defaults inside unmigrated siblings

πŸ”§ For Plugin Developers

Building Locally

# Build and publish to Maven Local
./gradlew publishToMavenLocal

# Use in another project
# In settings.gradle.kts of target project:
pluginManagement {
    repositories {
        mavenLocal()
        gradlePluginPortal()
    }
}

Running Tests

# 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

Project Setup for Development

  1. Clone the repository
  2. Build the plugin: ./gradlew publishToMavenLocal
  3. Create a test project with the plugin applied
  4. Iterate:
    • Make changes in mmd-migrator-core or mmd-migrator-plugin
    • Run ./gradlew publishToMavenLocal
    • Test in your test project

πŸ“‹ Requirements

  • Kotlin: 2.1.0 or newer
  • Gradle: 7.x or newer
  • JDK: 11 or newer
  • Project Type: Android or Kotlin with Jetpack Compose

⚠️ Known Limitations

1. Multi-line Raw Strings

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.

2. Dynamic Component Names

Components resolved at runtime won't be detected:

val componentName = if (condition) "Button" else "Card"
// Cannot be migrated automatically

3. Reflection-Based Usage

Components accessed via reflection are not migrated:

val componentClass = Class.forName("androidx.compose.material3.Button")

πŸ› Troubleshooting

Migration Doesn't Find Files

Issue: No files found or some files skipped.

Solution: The migrator only processes files that:

  • Have .kt extension
  • Contain @Composable annotation
  • Import from androidx.compose.material3 or androidx.compose.foundation.lazy

Check if your files meet these criteria.


Encoding Errors

Issue: File encoding validation failed

Solution:

  1. Ensure your files are UTF-8 encoded
  2. Check for BOM (Byte Order Mark) - the migrator preserves it
  3. Look for mixed line endings (CRLF vs LF)

Unexpected Changes

Issue: Some components were migrated incorrectly.

Solution:

  1. Run dry-run first: ./gradlew migrateMmdSimpleDryRun
  2. Review the changes carefully
  3. Use version control to diff changes
  4. Report issues with specific examples

Plugin Not Found

Issue: Plugin with id 'com.mudita.mmd-migrator' not found

Solution:

  1. Ensure you've published to Maven Local: ./gradlew publishToMavenLocal
  2. Check your settings.gradle.kts includes mavenLocal()
  3. Verify the version number matches

🀝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: ./gradlew test
  5. Submit a pull request

πŸ“„ License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.


πŸ™ Acknowledgments

  • 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

πŸ“ž Support


Made with ❀️ by Mudita

About

Android library for migrating components from Material 3 to Mudita Mindful Design (MMD).

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages