4.9 KiB
CollabTable Android App
A collaborative table management Android application built with Jetpack Compose and Material 3.
Features
- Create and manage multiple tables
- Add custom fields to tables (name, link, price, category, etc.)
- Add items with values for each field
- Beautiful Material 3 design with dynamic colors
- Local Room database for offline support
- Automatic synchronization with server (WebSocket-first with HTTP fallback)
- Soft delete support (items can be recovered on server)
- Optimized large table scrolling (stable row ordering + debounced updates)
Architecture
- UI Layer: Jetpack Compose with Material 3
- Business Logic: ViewModels with Kotlin Coroutines
- Data Layer: Room database + Retrofit for API calls
- Navigation: Jetpack Navigation Compose
Prerequisites
- Android Studio Hedgehog or later
- Android SDK 26 (Android 8.0) or higher
- JDK 17
Building the Project
- Clone the repository
- Open the
CollabTableAndroidfolder in Android Studio - Wait for Gradle sync to complete
- Run the app on an emulator or physical device
Server Configuration
The app includes a Settings screen where you can configure the server URL:
- Tap the Settings icon (⚙️) in the top bar of the Tables screen
- Enter your server URL
- Tap "Save"
Default URLs:
- Android Emulator:
http://10.0.2.2:3000/api/ - Physical Device:
http://YOUR_COMPUTER_IP:3000/api/(replace YOUR_COMPUTER_IP with your actual IP address)
Note: Make sure to include /api/ at the end of the URL.
Project Structure
app/src/main/java/com/collabtable/app/
├── data/
│ ├── api/ # Retrofit API interfaces and client
│ ├── dao/ # Room DAOs
│ ├── database/ # Room database
│ ├── model/ # Data models
│ └── repository/ # Repository pattern for data access
├── ui/
│ ├── navigation/ # Navigation setup
│ ├── screens/ # UI screens and ViewModels
│ └── theme/ # Material 3 theme
├── CollabTableApplication.kt
└── MainActivity.kt
Key Technologies
- Jetpack Compose: Modern declarative UI toolkit
- Material 3: Latest Material Design guidelines
- Room: Local SQLite database
- Retrofit: REST API client (fallback)
- OkHttp WebSocket: Real-time sync channel
- Kotlin Coroutines: Asynchronous programming
- Flow: Reactive data streams
Usage
Creating a Table
- Tap the + button on the main screen
- Enter a table name
- Tap "Save"
Adding Fields
- Open a table
- Tap the + icon in the top bar
- Enter a field name (e.g., "Name", "Price", "Category")
- Tap "Add"
Adding Items
- After adding fields, tap the floating + button
- Fill in values for each field
- Values are saved automatically as you type
Performance Notes (Large Tables)
For very large tables the app applies several optimizations:
- Rows are kept in their original creation order rather than sorting by last update timestamp. This prevents the entire list from resorting after each edit and reduces scroll jank.
- Rapid successive database emissions are coalesced (debounced ~75ms) before updating the UI state, lowering unnecessary recompositions during bulk edits or sync bursts. Further planned improvements:
- Incremental paging for extremely large datasets
- Horizontal virtualization for very wide column sets If you encounter sluggishness with tens of thousands of rows, enable server-side filtering/sorting to reduce client load.
Column Content Alignment
In the Edit Column dialog you can choose how each column's cell content is aligned (Left, Center, Right) via a single connected Material 3 segmented button group. The selection persists per list/field and updates immediately when you save changes. To adjust later, open the Manage Columns dialog or edit the field again.
Deleting
- Tap the delete icon next to any table, field, or item
- Confirm the deletion
Synchronization
The app uses a WebSocket-first sync strategy with an HTTP fallback:
- WebSocket (
/api/ws): Sends asyncmessage with local changes and receives deltas plus the newserverTimestamp. - HTTP (
POST /api/sync): Used automatically as a fallback if the WS exchange fails.
Behavior:
- Sends local changes since the last sync and receives server changes since the last known timestamp.
- Timestamps are used to resolve conflicts (latest wins).
- If the server is protected with
SERVER_PASSWORD, the app sendsAuthorization: Bearer <password>for both HTTP and WebSocket.
To trigger sync programmatically, call SyncRepository.performSync().
Building for Release
- Update the version in
app/build.gradle - Create a keystore for signing
- Build the release APK:
./gradlew assembleRelease
License
MIT