Skip to main content
Glama

dhis2_android_setup_sync

Configure offline-first data synchronization for DHIS2 Android apps by setting sync strategies, scope, conflict resolution, and network conditions.

Instructions

Configure offline-first data synchronization patterns for DHIS2 Android app

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
syncStrategyYesData synchronization strategy
syncScopeNo
conflictResolutionNoStrategy for resolving sync conflicts
networkConditionsNo
progressTrackingNoInclude sync progress tracking UI

Implementation Reference

  • The core handler function `generateSyncConfiguration` that implements the tool logic. It takes configuration parameters and generates comprehensive Kotlin code for DHIS2 Android app synchronization, including SyncManager, background WorkManager jobs, network monitoring, conflict resolution strategies, and progress tracking.
    export function generateSyncConfiguration(args: any): string {
      const { syncStrategy, syncScope, conflictResolution, networkConditions, progressTracking } = args;
    
      return `# DHIS2 Android Sync Configuration
    
    ## Overview
    This configuration sets up ${syncStrategy} synchronization with ${conflictResolution} conflict resolution.
    
    ## Sync Manager Setup
    
    \`\`\`kotlin
    class SyncManager @Inject constructor(
        private val d2: D2,
        private val networkManager: NetworkManager,
        private val syncPreferences: SyncPreferences
    ) {
        
        suspend fun performSync(syncType: SyncType = SyncType.FULL): SyncResult {
            return when (syncStrategy) {
                SyncStrategy.MANUAL -> performManualSync(syncType)
                SyncStrategy.AUTOMATIC -> performAutomaticSync()
                SyncStrategy.SCHEDULED -> scheduleSync()
                SyncStrategy.SMART -> performSmartSync()
            }
        }
    
        private suspend fun performManualSync(syncType: SyncType): SyncResult {
            if (!canSync()) return SyncResult.Failed("Network conditions not met")
            
            return try {
                ${syncScope.metadata ? 'syncMetadata()' : ''}
                ${syncScope.dataValues ? 'syncDataValues()' : ''}  
                ${syncScope.events ? 'syncEvents()' : ''}
                ${syncScope.enrollments ? 'syncEnrollments()' : ''}
                
                SyncResult.Success
            } catch (e: Exception) {
                handleSyncError(e)
            }
        }
    
        private suspend fun syncMetadata() {
            ${progressTracking ? 'updateProgress("Syncing metadata...", 10)' : ''}
            d2.metadataModule().download().blockingDownload()
        }
    
        private suspend fun syncDataValues() {  
            ${progressTracking ? 'updateProgress("Syncing data values...", 40)' : ''}
            d2.dataValueModule().dataValueUploader().blockingUpload()
        }
    
        private suspend fun syncEvents() {
            ${progressTracking ? 'updateProgress("Syncing events...", 70)' : ''}
            d2.trackerModule().trackedEntityInstances().blockingUpload()
            d2.eventModule().events().blockingUpload()
        }
    
        private suspend fun syncEnrollments() {
            ${progressTracking ? 'updateProgress("Syncing enrollments...", 90)' : ''}
            d2.enrollmentModule().enrollments().blockingUpload()
        }
    
        private fun canSync(): Boolean {
            ${networkConditions.wifiOnly ? 'if (!networkManager.isWiFiConnected()) return false' : ''}
            if (!networkManager.isConnected()) return false
            return true
        }
    
        private fun handleConflict(conflict: ImportConflict): ConflictResolution {
            return when (conflictResolution) {
                ConflictResolution.SERVER_WINS -> ConflictResolution.SERVER_WINS
                ConflictResolution.CLIENT_WINS -> ConflictResolution.CLIENT_WINS
                ConflictResolution.MERGE -> mergeConflict(conflict)
                ConflictResolution.USER_PROMPT -> promptUserForResolution(conflict)
            }
        }
    }
    
    sealed class SyncResult {
        object Success : SyncResult()
        data class Failed(val error: String) : SyncResult()
        data class PartialSuccess(val details: String) : SyncResult()
    }
    
    enum class SyncStrategy {
        MANUAL, AUTOMATIC, SCHEDULED, SMART
    }
    \`\`\`
    
    ## Background Sync Service
    
    \`\`\`kotlin
    @HiltWorker  
    class SyncWorker @AssistedInject constructor(
        @Assisted context: Context,
        @Assisted workerParams: WorkerParameters,
        private val syncManager: SyncManager
    ) : CoroutineWorker(context, workerParams) {
    
        override suspend fun doWork(): Result {
            return try {
                val result = syncManager.performSync()
                when (result) {
                    is SyncResult.Success -> Result.success()
                    is SyncResult.Failed -> Result.retry()
                    is SyncResult.PartialSuccess -> Result.success()
                }
            } catch (e: Exception) {
                Result.failure()
            }
        }
    
        @AssistedFactory
        interface Factory {
            fun create(context: Context, params: WorkerParameters): SyncWorker
        }
    }
    
    // Schedule periodic sync
    class SyncScheduler @Inject constructor(
        private val workManager: WorkManager
    ) {
        
        fun schedulePeriodicSync() {
            val constraints = Constraints.Builder()
                ${networkConditions.wifiOnly ? '.setRequiredNetworkType(NetworkType.UNMETERED)' : '.setRequiredNetworkType(NetworkType.CONNECTED)'}
                ${networkConditions.backgroundSync ? '' : '.setRequiresBatteryNotLow(true)'}
                .build()
    
            val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
                repeatInterval = 15, // minutes
                repeatIntervalTimeUnit = TimeUnit.MINUTES
            ).setConstraints(constraints)
             .build()
    
            workManager.enqueueUniquePeriodicWork(
                "sync_work",
                ExistingPeriodicWorkPolicy.KEEP,
                syncRequest
            )
        }
    }
    \`\`\`
    
    ## Network Monitoring
    
    \`\`\`kotlin
    @Singleton
    class NetworkManager @Inject constructor(
        @ApplicationContext private val context: Context
    ) {
        
        private val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    
        fun isConnected(): Boolean {
            val network = connectivityManager.activeNetwork ?: return false
            val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
            return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
        }
    
        fun isWiFiConnected(): Boolean {
            val network = connectivityManager.activeNetwork ?: return false
            val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
            return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
        }
    
        fun getNetworkType(): NetworkType {
            val network = connectivityManager.activeNetwork ?: return NetworkType.NONE
            val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return NetworkType.NONE
            
            return when {
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> NetworkType.WIFI
                capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> NetworkType.CELLULAR
                else -> NetworkType.OTHER
            }
        }
    }
    
    enum class NetworkType { NONE, WIFI, CELLULAR, OTHER }
    \`\`\`
    
    ${progressTracking ? generateProgressTracking() : ''}
    
    ## Usage Example
    
    \`\`\`kotlin
    class MainActivity : AppCompatActivity() {
        
        @Inject lateinit var syncManager: SyncManager
        @Inject lateinit var syncScheduler: SyncScheduler
        
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            
            // Schedule background sync
            syncScheduler.schedulePeriodicSync()
            
            // Manual sync button
            binding.syncButton.setOnClickListener {
                lifecycleScope.launch {
                    val result = syncManager.performSync()
                    handleSyncResult(result)
                }
            }
        }
        
        private fun handleSyncResult(result: SyncResult) {
            when (result) {
                is SyncResult.Success -> {
                    Toast.makeText(this, "Sync completed successfully", Toast.LENGTH_SHORT).show()
                }
                is SyncResult.Failed -> {
                    Toast.makeText(this, "Sync failed: \${result.error}", Toast.LENGTH_LONG).show()
                }
                is SyncResult.PartialSuccess -> {
                    Toast.makeText(this, "Partial sync: \${result.details}", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    \`\`\`
    
    ## Configuration Summary
    
    - **Strategy**: ${syncStrategy}
    - **Scope**: ${Object.entries(syncScope).filter(([_, enabled]) => enabled).map(([key]) => key).join(', ')}
    - **Conflict Resolution**: ${conflictResolution}
    - **Network**: ${networkConditions.wifiOnly ? 'WiFi only' : 'Any connection'}${networkConditions.backgroundSync ? ', Background sync enabled' : ''}
    - **Progress Tracking**: ${progressTracking ? 'Enabled' : 'Disabled'}
    ${networkConditions.chunkSize ? `- **Chunk Size**: ${networkConditions.chunkSize}KB` : ''}
    `;
    }
  • src/index.ts:1261-1271 (registration)
    Tool registration in the main MCP server request handler. Dispatches calls to the `dhis2_android_setup_sync` tool by invoking the imported `generateSyncConfiguration` handler function from android-generators.ts.
    case 'dhis2_android_setup_sync':
      const syncArgs = args as any;
      const syncConfig = generateSyncConfiguration(syncArgs);
      return {
        content: [
          {
            type: 'text',
            text: syncConfig,
          },
        ],
      };
  • Permission registration: Maps the tool to the 'canConfigureMobile' permission in the TOOL_PERMISSIONS Map, used by PermissionSystem.filterToolsByPermissions to control access.
    ['dhis2_android_setup_sync', 'canConfigureMobile'],

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Dradebo/dhis2-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server