Data Structure Version Management System
Overview
This data structure version management system automatically handles application data structure upgrades, ensuring smooth data migration when users update the application.
Current Data Structure Version
Current Version: 1
Features
- Automatic Detection and Upgrade: Automatically detects data structure version on app startup and executes necessary migrations
- Version Tracking: Records current data structure version for easy management
- Backward Compatibility: Supports upgrading from older data structure versions to newer ones
- Data Backup: Provides data backup functionality to ensure safe migration
- Extensibility: Easy to add new version migration logic
Usage
Initialization
The system is automatically initialized in MainActivity.onCreate():
MMKV.initialize(this)
preferences = MMKV.defaultMMKV()
// Data structure migration check
DataMigrationManager.checkAndMigrate(this)Adding a New Data Structure Version
When you need to modify the data structure (e.g., adding new fields, modifying data formats, removing old fields), follow these steps:
Step 1: Increment the Version Number
Modify CURRENT_DATA_VERSION in DataMigrationManager.kt:
/**
* Current data structure version
* Increment this when making breaking changes to data structure
*/
const val CURRENT_DATA_VERSION = 2 // Increment from 1 to 2Step 2: Implement Migration Logic
Add handling for the new version in the when statement of the performMigration() method:
when (nextVersion) {
1 -> migrateToVersion1(context, MMKV.defaultMMKV())
2 -> migrateToVersion2(context, MMKV.defaultMMKV())
3 -> migrateToVersion3(context, MMKV.defaultMMKV())
// Add new version
4 -> migrateToVersion4(context, MMKV.defaultMMKV())
}Step 3: Write Migration Function
Implement specific migration logic:
/**
* Migration to version 4
* Example: Add a new notification setting
*/
private fun migrateToVersion4(context: Context, preferences: MMKV) {
Log.d(TAG, "Migrating to version 4")
// Add new field with default value
if (!preferences.contains("new_notification_setting")) {
preferences.putBoolean("new_notification_setting", false)
}
// Transform old data format
val oldValue = preferences.getString("old_format_field", "")
if (oldValue.isNotEmpty()) {
val newValue = transformOldFormat(oldValue)
preferences.putString("new_format_field", newValue)
// Optional: Remove old field
preferences.remove("old_format_field")
}
// Migrate MMKV instance data
val oldMMKV = MMKV.mmkvWithID(MMKVConst.OLD_ID)
val newMMKV = MMKV.mmkvWithID(MMKVConst.NEW_ID)
// ... data migration logic
}
private fun transformOldFormat(oldData: String): String {
// Implement data format transformation logic
return oldData.uppercase()
}Data Structure Version History
Version 1 (Current Baseline Version)
Date: January 2, 2026
Description: Established baseline version for the data structure version management system
Included Data Fields:
Main MMKV (Default):
bot_token(String) - Telegram Bot Tokenchat_id(String) - Telegram Chat IDmessage_thread_id(String) - Topic ID (for groups)trusted_phone_number(String) - Trusted phone numberfallback_sms(Boolean) - Fallback to SMSchat_command(Boolean) - Chat command switchbattery_monitoring_switch(Boolean) - Battery monitoring switchcharger_status(Boolean) - Charger status notificationverification_code(Boolean) - Verification code recognitiondoh_switch(Boolean) - DNS over HTTPSinitialized(Boolean) - Initialization flagprivacy_dialog_agree(Boolean) - Privacy policy agreementcall_notify(Boolean) - Call notificationhide_phone_number(Boolean) - Hide phone numberversion_code(Int) - App version codeapi_address(String) - Telegram API addressdata_structure_version(Int) - Data structure version number
MMKV Instances:
MMKVConst.PROXY_ID- Proxy settingsMMKVConst.CHAT_ID- Chat dataMMKVConst.CHAT_INFO_ID- Chat informationMMKVConst.RESEND_ID- Resend queueMMKVConst.CARBON_COPY_ID- Carbon copy service configurationMMKVConst.UPDATE_ID- Update check dataMMKVConst.NOTIFY_ID- Notification settingsMMKVConst.TEMPLATE_ID- Message templatesMMKVConst.LOG_ID- Log data
Version 2 (Example - Not Implemented)
When you need to upgrade to version 2, document the changes here.
Change Details:
- Add XXX field
- Modify XXX data format
- Remove XXX deprecated field
API Reference
DataMigrationManager
Public Methods
checkAndMigrate(context: Context)
Check and execute necessary data migrations. Should be called on app startup.
getCurrentVersion(): Int
Get the currently saved data structure version.
isMigrationNeeded(): Boolean
Check if migration is needed.
backupData(context: Context): Boolean
Backup all MMKV data. Recommended to call before major migrations.
resetDataVersion()
Reset data structure version. Warning: Use with caution, may cause data inconsistency.
Best Practices
- Incremental Version Numbers: Version number should increment by 1 each time the data structure is modified
- Maintain Backward Compatibility: Migration logic should handle all possible older version data
- Detailed Documentation: Each version change should be documented in detail in this document
- Thorough Testing: Test migration from all old versions to the new version before release
- Data Backup: Consider backing up data before major changes
- Logging: Migration process should log detailed information for troubleshooting
Example Scenarios
Scenario 1: Adding New Feature Requiring New Field
// Version 2: Adding message encryption feature
private fun migrateToVersion2(context: Context, preferences: MMKV) {
Log.d(TAG, "Migrating to version 2: Adding encryption support")
// Add encryption switch, default off
preferences.putBoolean("enable_encryption", false)
// Add encryption key storage location
preferences.putString("encryption_key", "")
}Scenario 2: Modifying Data Format
// Version 3: Migrating chat_id from String to Long
private fun migrateToVersion3(context: Context, preferences: MMKV) {
Log.d(TAG, "Migrating to version 3: Converting chat_id to Long")
val oldChatId = preferences.getString("chat_id", "")
if (oldChatId.isNotEmpty()) {
try {
val chatIdLong = oldChatId.toLong()
preferences.putLong("chat_id_long", chatIdLong)
// Keep old field for a while to allow rollback
// preferences.remove("chat_id")
} catch (e: NumberFormatException) {
Log.e(TAG, "Failed to convert chat_id to Long: $oldChatId")
}
}
}Scenario 3: Reorganizing Data to New MMKV Instance
// Version 4: Separating settings to dedicated MMKV instance
private fun migrateToVersion4(context: Context, preferences: MMKV) {
Log.d(TAG, "Migrating to version 4: Separating settings")
// Create new settings MMKV
val settingsMMKV = MMKV.mmkvWithID("settings")
// Migrate settings-related fields
val fieldsToMigrate = listOf(
"doh_switch",
"battery_monitoring_switch",
"charger_status",
"call_notify",
"verification_code"
)
for (field in fieldsToMigrate) {
val value = preferences.getBoolean(field, false)
settingsMMKV.putBoolean(field, value)
// Optional: Remove from main MMKV
// preferences.remove(field)
}
}Troubleshooting
Issue: Migration Failed
Solution:
- Check error messages in logs
- Verify correctness of migration logic
- Ensure all required fields have default values
- Consider using
backupData()to restore data
Issue: Data Loss
Solution:
- Check if fields were accidentally deleted
- Review backup data
- Ensure migration logic correctly handles all old versions
Issue: Version Number Mismatch
Solution:
- Check if
CURRENT_DATA_VERSIONis correctly updated - Ensure migration functions for all versions are implemented
- Verify migration logic executes in sequence
Contact
For issues or suggestions, please provide feedback through GitHub Issues.