Developing a Mobile App for Project Management
A mobile task manager is not "Jira on your phone." Tasks from meetings, quick status from a colleague on the go, deadline reminders five minutes before the deadline — mobile context is different. Therefore, the architecture differs: emphasis on offline-first, minimal input friction, and smart push notifications about critical changes.
Data Structure and Synchronization
Typical hierarchy: Workspace → Project → Board → Task → Subtask → Comment. The mobile device stores a local copy (Room or Core Data) synchronized with the server.
The main challenge — conflicts during offline editing. Two people changed a task's status while offline. Solution — Operational Transformation (used by Notion, Google Docs) or simpler Last-Write-Wins with timestamp. For most task managers, Last-Write-Wins is sufficient.
// Android — offline operation with subsequent synchronization
data class PendingOperation(
val id: String = UUID.randomUUID().toString(),
val type: OperationType,
val entityId: String,
val payload: String, // JSON
val createdAt: Long = System.currentTimeMillis(),
var syncedAt: Long? = null
)
class TaskRepository(
private val taskDao: TaskDao,
private val pendingOpsDao: PendingOperationDao,
private val api: ProjectApi
) {
suspend fun updateTaskStatus(taskId: String, newStatus: TaskStatus) {
// Update locally immediately
taskDao.updateStatus(taskId, newStatus)
// Queue for synchronization
val op = PendingOperation(
type = OperationType.UPDATE_TASK_STATUS,
entityId = taskId,
payload = json.encodeToString(mapOf("status" to newStatus.name))
)
pendingOpsDao.insert(op)
// Try to sync if connected
if (networkMonitor.isConnected) syncPendingOperations()
}
}
Kanban Board on Mobile
Drag-and-drop in mobile Kanban is technically complex. On Android — ItemTouchHelper for RecyclerView, but horizontal scroll between columns + vertical scroll within a column + drag across column borders — three competing gesture recognizers.
Working approach: instead of complex D&D — tap task → bottom sheet to select status. Less fancy, but reliable across devices.
// iOS — status selection via context menu (iOS 13+)
func tableView(_ tableView: UITableView,
contextMenuConfigurationForRowAt indexPath: IndexPath,
point: CGPoint) -> UIContextMenuConfiguration? {
let task = tasks[indexPath.row]
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
let moveActions = TaskStatus.allCases.map { status in
UIAction(title: status.title, image: status.icon) { [weak self] _ in
self?.viewModel.updateStatus(task: task, to: status)
}
}
return UIMenu(title: "Move to...", children: moveActions)
}
}
Push Notifications: What You Actually Need
Not "notification for every change" — that's annoying. Smart notification system considers context:
- @mention in comment — immediate, high priority.
- Task assigned — immediate.
- Task deadline changed — immediate to assignee.
- Deadline in 24 hours — scheduled reminder.
- Task status changed — only if user is creator or observer.
// Server-side logic to determine notification recipients
fun getNotificationRecipients(event: TaskEvent): Set<UserId> {
return buildSet {
when (event) {
is TaskEvent.Mentioned -> add(event.mentionedUserId)
is TaskEvent.Assigned -> add(event.assigneeId)
is TaskEvent.StatusChanged -> {
addAll(task.watchers)
add(task.creatorId)
// Don't notify whoever changed the status
remove(event.changedBy)
}
}
}
}
On the client — notification settings by type, stored in user profile on server. This way, when switching devices, settings are not lost.
Time Tracking
Simple time tracker: start/stop timer tied to a task. On mobile — foreground service (Android) or Background Task + live activity (iOS 16+) to display the current timer on lock screen.
// iOS 16+ Live Activity for task timer
struct TimerActivityAttributes: ActivityAttributes {
struct ContentState: Codable, Hashable {
var elapsedSeconds: Int
var taskName: String
}
var taskId: String
}
// Launch
let state = TimerActivityAttributes.ContentState(elapsedSeconds: 0, taskName: task.name)
let activity = try Activity<TimerActivityAttributes>.request(
attributes: TimerActivityAttributes(taskId: task.id),
contentState: state,
pushType: nil
)
Gantt Chart
Gantt chart on mobile — readability with horizontal scroll and zoom. Custom Canvas/UIView rendering the time scale. Ready libraries: DHTMLX Gantt for WebView approach or custom rendering via SurfaceView (Android) / CALayer (iOS).
WebView approach is simpler but slower with 200+ tasks. Native rendering — harder but smoother.
Timeline
| Functionality | Effort |
|---|---|
| Task list + filters + detail card | 3–4 weeks |
| Kanban board | 2–3 weeks |
| Offline synchronization | 2–3 weeks |
| Push notifications | 1–2 weeks |
| Time tracking | 1–2 weeks |
| Gantt chart | 2–3 weeks |
MVP (tasks, Kanban, push, offline) — 8 to 12 weeks. Full feature set including Gantt and time tracking — 16–20 weeks.







