Tech Stack
Kotlin Jetpack Compose for Wear OS Room Database Retrofit 2 Coroutines & Flow WorkManager Google OAuth 2.0 Google Tasks REST API DataStore Preferences
Architecture Overview
GTasksWear follows a layered architecture pattern with clear separation between data, domain, and presentation concerns.
| PRESENTATION LAYER |
| Compose UI · ViewModels · Screens |
+-----------------+-------------------+
|
+-----------------v-------------------+
| DOMAIN LAYER |
| Models · SyncManager · SyncWorker |
+-----------------+-------------------+
|
+-----------------v-------------------+
| DATA LAYER |
| Room DB · Retrofit · Repositories |
+-----------------+-------------------+
|
+-----------------v-------------------+
| EXTERNAL SERVICES |
| Google Tasks API · Google OAuth |
+-------------------------------------+
Google Tasks API Integration
GTasksWear communicates with the Google Tasks REST API v1 via Retrofit 2. The base URL is
https://www.googleapis.com/tasks/v1/.
API Endpoints Used
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/users/@me/lists |
Retrieve all task lists |
GET |
/lists/{id}/tasks |
Retrieve tasks in a list |
POST |
/lists/{id}/tasks |
Create a new task |
PATCH |
/lists/{id}/tasks/{taskId} |
Update an existing task |
DELETE |
/lists/{id}/tasks/{taskId} |
Delete a task |
Authentication Flow
Authentication uses Google OAuth 2.0 with the https://www.googleapis.com/auth/tasks scope. An
AuthInterceptor automatically injects the Bearer token into every Retrofit request. Token
refresh is handled transparently.
Local Database (Room)
GTasksWear uses Room (version 2) as the local persistence layer, enabling offline-first functionality.
Database Schema
| Table | Primary Key | Key Fields |
|---|---|---|
tasks |
id (String) |
taskListId, title, notes, status,
due, parent, position, isPendingSync,
deleted |
task_lists |
id (String) |
title, updated |
taskListId → task_lists.id with CASCADE delete — removing a list
automatically removes all its tasks.
Offline Sync Tracking
Each TaskEntity has two fields for offline support:
isPendingSync: Boolean— Marks tasks that have been modified locally but not yet pushed to the server.deleted: Boolean— Soft-delete flag. Tasks are marked for deletion locally and removed from the server on the next sync.
Locally created tasks use an ID prefixed with local_ to distinguish them from server-assigned
IDs.
Synchronization Engine
The SyncManager is the core orchestrator for keeping local and remote data in sync.
Sync Flow
1. PUSH LOCAL CHANGES ├── For each task list: │ ├── Find tasks with isPendingSync = true │ ├── If deleted & local_ → delete from Room │ ├── If deleted & remote → DELETE via API, then delete from Room │ ├── If local_ (new) → POST via API, replace with server entity │ └── If remote (updated) → PATCH via API, update entity │ 2. FETCH REMOTE STATE ├── GET all task lists └── For each list → GET all tasks │ 3. ATOMIC DATABASE UPDATE (Room Transaction) ├── Clear all task lists (cascades to tasks) ├── Insert all fetched task lists └── Insert all fetched tasks
Room.withTransaction {} block. This prevents the UI from observing a temporarily empty database
during sync, eliminating the "flash" effect where tasks briefly disappear.
Sync Intervals
| Type | Interval | Mechanism |
|---|---|---|
| Foreground | 60 seconds | Coroutine loop with delay(60_000) in MainActivity |
| Background | 15 minutes | WorkManager periodic task via SyncWorker |
UI Layer (Jetpack Compose for Wear OS)
The entire UI is built with Jetpack Compose for Wear OS — no XML layouts.
Key Screens
| Screen | Components Used |
|---|---|
| Login | Chip (Google Sign-In trigger) |
| List Picker | ScalingLazyColumn with selectable Chip items |
| Task List | ScalingLazyColumn, Chip, CompactChip, collapsible
parent/child tree |
| Task Detail | Title, notes, due date editing via RemoteInput |
Task Tree Rendering
Tasks support parent-child relationships. The UI builds a flattened tree using a recursive algorithm:
fun addWithChildren(task: Task) {
orderedList.add(task)
if (!collapsed[task.id]) {
childrenMap[task.id]?.forEach { child ->
addWithChildren(child)
}
}
}
This is wrapped in a remember(tasks, sortOption, collapsedParents) block with a
TaskTreeResult data class to avoid unnecessary recomputations.
Design Principles
- AMOLED-optimized: True black backgrounds to save battery on OLED displays.
- High contrast: White text on dark backgrounds for outdoor readability.
- Touch-friendly: All interactive elements sized ≥ 48dp for easy tapping on small screens.
- Rotary input:
ScalingLazyColumnnatively supports crown/bezel scrolling.
Build & Optimization
| Feature | Debug | Release |
|---|---|---|
| Enabled | Enabled | |
| Resource Shrinking | — | Enabled |
| Signing | Debug keystore | Release keystore |
| Target SDK | 34 (Android 14) | |
| Min SDK | 28 (Wear OS 2+) | |
| JVM Target | Java 17 | |
Dependencies
| Library | Purpose |
|---|---|
| Retrofit 2 + Gson | HTTP client for Google Tasks API |
| Room + KSP | Local SQLite database with compile-time query verification |
| WorkManager | Reliable background sync scheduling |
| DataStore Preferences | Key-value storage for auth tokens and settings |
| Wear Compose Material | Wear OS-specific Material Design components |
| Wear Compose Navigation | SwipeDismissable navigation for Wear OS |
| Google Play Services Auth | Google Sign-In on Wear OS |
| Material Icons Extended | Full set of Material Design icons |
| Wear Input | Remote text input (voice/keyboard) for task creation |
Security
- No server infrastructure: All data flows directly between the device and Google's APIs. There is no intermediary server.
- OAuth 2.0: No passwords are ever stored. Authentication is handled entirely via Google's token-based system. significantly harder.
- HTTPS only: All API communication uses TLS encryption.