Frontend Repository: https://github.com/coder2505/doodleFrontend.git
Backend Repository: https://github.com/coder2505/doodleBackend.git
Welcome to Doodle, a full-stack real-time collaborative Android widget and canvas application. Doodle allows users to create shared rooms, draw or write text on an interactive canvas, and instantly sync updates across all room members' Android home screen widgets powered by Spring Boot, PostgreSQL, and Firebase Cloud Messaging (FCM).
Below is the overall end-to-end architecture and messaging flow of Doodle, demonstrating how the Android App, Spring Boot Backend, PostgreSQL Database, and Firebase Cloud Messaging (FCM) interact in real-time.
sequenceDiagram
autonumber
actor User as Mobile App User
participant App as doodleFrontend (Android)
participant FCM as Firebase Cloud Messaging
participant BE as doodleBackend (Spring Boot)
participant DB as PostgreSQL Database
App->>FCM: 1. Fetch FCM Device Token
FCM-->>App: Return Token
App->>BE: 2. POST /login/user/{username}/{fcmToken}
BE->>DB: 3. Save User & FCM Token
DB-->>BE: Saved
BE-->>App: Return JWT Access & Refresh Tokens
User->>App: 4. Edit Text / Canvas
App->>BE: 5. POST /widget/text/{payload} (Bearer JWT)
BE->>DB: 6. Update room.resource payload
BE->>DB: 7. Query room members & FCM tokens
DB-->>BE: Return list of user FCM tokens
BE->>BE: 8. Generate OAuth2 Access Token (doodleapp-firebase.json)
BE->>FCM: 9. HTTP v1 POST Data Message (FCM Token, Payload)
FCM-->>App: 10. Deliver silent FCM Push Message
App->>App: 11. MyFirebaseMessagingService -> Update Glance Widget
+-------------------------------------------------+
| Android App (doodleFrontend) |
| - Jetpack Compose UI / Canvas Drawing |
| - Jetpack Glance (Home Screen Widget) |
| - Hilt, Retrofit2, CryptoManager, WorkManager |
+-----------------------+-------------------------+
|
HTTP REST API | Firebase FCM
(JWT Protected) | Push Payload
v ^
+------------------------------------+ +------+-----------+--------------+
| PostgreSQL Database | | Spring Boot Backend |
| | <=========> | (doodleBackend) |
| - users (UUID PK, fcm_token) | Spring | - Controllers, Security, JWT |
| - room (BigInt PK, resource) | Data JPA | - FCM Service (WebClient) |
| - user_room (Composite PK, M:N) | | - Spring Web / Jackson |
+------------------------------------+ +---------------------------------+
The Android application is built natively in Kotlin using modern Jetpack libraries, clean architecture patterns, and robust network resilience mechanisms.
Networking is powered by Retrofit 2 and OkHttp, utilizing a specialized dual-interceptor pipeline:
HeaderInterceptor: Transparently injects theAuthorization: Bearer <access_token>header into all outgoing API requests by fetching valid credentials from encrypted storage.RefreshInterceptor: Manages automatic token renewal without disrupting the UI:- Intercepts
400 BAD_REQUESTor401 UNAUTHORIZEDresponses caused by an expired access token (15-minute lifetime). - Synchronously executes a token refresh call (
POST /login/refresh) using the securely stored long-lived Refresh Token (365-day lifetime). - Updates local encrypted storage with the new Access Token.
- Retries the original failed HTTP request seamlessly, ensuring zero interrupted user sessions.
- Intercepts
Sensitive JWT access and refresh tokens are stored securely on-device using Android KeyStore primitives and EncryptedSharedPreferences (CryptoManager), preventing credential extraction from rooted devices or memory dumps.
- Built with Jetpack Compose custom drawing primitives and gesture handling (
pointerInput). - Captures touch coordinates, stroke paths, colors, and text annotations in real time, serializing canvas vector data into optimized payload strings.
- Declarative home screen app widget constructed with Jetpack Glance (Jetpack Compose for Widgets).
- Re-renders dynamically when receiving silent FCM pushes, rendering fresh canvas drawings or text directly on the Android home screen.
- Handles background data payloads from Firebase Cloud Messaging.
- Signals Jetpack Glance (
GlanceAppWidget.updateAll()) and schedules background sync tasks (WorkManager) even if the application is killed or inactive.
- Uses
@HiltAndroidAppand@AndroidEntryPointfor clean dependency graph management. - Provides singletons for OkHttpClient, Retrofit API interfaces, Token Managers, and Repository layers.
The Spring Boot backend serves as the core REST API engine and push notification dispatcher.
- WebMvc Handler Interceptor: Implements Spring's
HandlerInterceptorregistered viaInterceptorConfig(excluding public paths/login/**,/swagger-ui/**, and API docs). - Request Pre-Handling & User Context: Validates
Authorization: Bearer <token>headers on all protected endpoints (/widget/**,/room/**). Validates JWT signature and expiration, extracts the user'sUUIDsubject, and binds it toHttpServletRequestattributes (request.setAttribute("user_id", user_id)), keeping controller endpoints clean and decoupled from token parsing logic.
- Access Tokens: Short-lived (15 minutes) HMAC-SHA signed JWT tokens used for authenticating REST requests.
- Refresh Tokens: Long-lived (365 days) tokens used exclusively for renewing expired access tokens via
POST /login/refresh. - Environment-based Key Management: Signing keys are securely decoded from environment configurations (
SIGNING_KEY).
- Async Spring WebClient: Executes non-blocking HTTP v1 POST payloads to Firebase Cloud Messaging endpoints (
https://fcm.googleapis.com/v1/projects/{project_id}/messages:send). - OAuth2 Service Account Authentication:
FCMTokenServicereadsdoodleapp-firebase.jsonservice account credentials to generate Google OAuth2 access tokens scoped tohttps://www.googleapis.com/auth/firebase.messaging. - Targeted Room Fanout: On canvas/text payload mutations, the backend queries PostgreSQL for all FCM tokens belonging to active room members and dispatches push data messages asynchronously.
The PostgreSQL database (doodleDB) consists of three normalized tables designed to capture user identities, room resources, and membership relationships.
erDiagram
users {
uuid user_id PK "gen_random_uuid()"
varchar user_name "NOT NULL"
varchar fcm_token "NULLABLE"
}
room {
bigint room_id PK "generated by default as identity"
varchar room_name "NOT NULL"
varchar resource "NULLABLE (Payload/Canvas/Text)"
}
user_room {
uuid user_id PK, FK "ON DELETE CASCADE"
bigint room_id PK, FK "ON DELETE CASCADE"
}
users ||--o{ user_room : "joins"
room ||--o{ user_room : "contains"
CREATE TABLE public.users (
user_id uuid NOT NULL DEFAULT gen_random_uuid(),
user_name character varying(255) NOT NULL,
fcm_token character varying(255),
CONSTRAINT users_pkey PRIMARY KEY (user_id)
);user_id(UUID): Universally unique primary key automatically generated by PostgreSQL'sgen_random_uuid().user_name(VARCHAR(255)): Display name of the user.fcm_token(VARCHAR(255)): Unique device registration token assigned by Firebase Cloud Messaging.
CREATE TABLE public.room (
room_id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
room_name character varying(255) NOT NULL,
resource character varying(255),
CONSTRAINT room_pkey PRIMARY KEY (room_id)
);room_id(BIGINT): Auto-incrementing identity sequence used as a primary key and room code.room_name(VARCHAR(255)): Name assigned to the doodle room.resource(VARCHAR(255)): Stores the current shared payload (text string or drawing data) displayed on all room members' widgets.
CREATE TABLE public.user_room (
user_id uuid NOT NULL,
room_id bigint NOT NULL,
CONSTRAINT user_room_pkey PRIMARY KEY (user_id, room_id),
CONSTRAINT user_room_room_id_fkey FOREIGN KEY (room_id)
REFERENCES public.room(room_id) ON DELETE CASCADE,
CONSTRAINT user_room_user_id_fkey FOREIGN KEY (user_id)
REFERENCES public.users(user_id) ON DELETE CASCADE
);user_id&room_id: Composite primary key enforcing unique membership.- Foreign Keys: Direct references to
users(user_id)androom(room_id)withON DELETE CASCADE.
- First Normal Form (1NF): Every table column contains atomic, scalar values without nested arrays or repeated groups.
- Second Normal Form (2NF): The junction table
user_roomuses a Composite Primary Key(user_id, room_id). Non-key dependencies are eliminated since user metadata resides solely inusersand room metadata resides solely inroom. - Third Normal Form (3NF): Transitive dependencies are eliminated.
fcm_tokenis dependent exclusively onuser_id, andresourceis dependent strictly onroom_id.
- Users (
UUID): Utilizing 128-bit UUIDs (gen_random_uuid()) prevents sequential user enumeration attacks, securely embeds user IDs into JWT tokens, and allows safe multi-device identity tracking. - Rooms (
BIGINT Identity): Utilizing an auto-incrementingBIGINTidentity sequence provides clean, concise numeric room codes that users can easily copy, share, and input on mobile screens to join rooms.
- Both Foreign Keys in
user_roomenforceON DELETE CASCADE. When a user deletes their account or a room is destroyed, PostgreSQL automatically cleans up all associated junction records. This eliminates orphan rows and ensures absolute data integrity without requiring redundant application-level cleanup logic.