JobPulse — Native Android Job Board Client

AndroidJavaRetrofitOkHttpRoomWorkManager· 3 min read

A native Android client built against a REST job-board API: register or log in, browse paginated offers, and keep a local list of the ones worth coming back to — with the token lifecycle and the HTTP layer handled once, centrally, rather than re-derived in every screen.

The problem & constraints

A mobile client for an authenticated API has three problems that are easy to solve badly: the auth token arrives after startup and changes on login/logout, every request needs the same headers, and rebuilding the HTTP stack per call quietly throws away the connection pool. The constraint was to keep all of that in one place so the activities only deal with typed calls and results.

Approach

  • A single Retrofit instance cached statically and rebuilt only when the token actually changes, so the OkHttp connection pool and thread pool survive across requests.
  • An OkHttp interceptor attaches Accept: application/json and, when a token is present, Authorization: Bearer … — screens never touch headers.
  • API surface split by concern into typed interfaces: AuthApi (register / login / logout / me) and JobsApi (list / create), with a generic PagedResponse<T> wrapper so pagination is modeled once rather than per endpoint.
  • Favorites toggled locally as a Set<String> of offer IDs, so the list survives restarts and stays readable without a round trip.
localbeareractivitiestyped calls onlyAuthApiregister, login,logout, meJobsApilist, createPagedResponse<T>SharedPreferencesfavorites: Set<String>Retrofit + OkHttpcached, keyed on tokenAccept + Bearerjob-board API

Described in full:

  1. activities — typed calls only → AuthApi — register, login, — logout, me
  2. activities — typed calls only → JobsApi — list, create — PagedResponse<T>
  3. activities — typed calls only → SharedPreferences — favorites: Set<String> (local)
  4. AuthApi — register, login, — logout, me → Retrofit + OkHttp — cached, keyed on token — Accept + Bearer
  5. JobsApi — list, create — PagedResponse<T> → Retrofit + OkHttp — cached, keyed on token — Accept + Bearer
  6. Retrofit + OkHttp — cached, keyed on token — Accept + Bearer → job-board API (bearer)
Everything to the right of the activities column is what a screen never touches: one cached Retrofit instance keyed on the token, and the interceptor that attaches the headers. Favorites hang off to the side because they deliberately never cross that boundary.

Trade-offs made

Decision

A cached Retrofit instance keyed on the token, over building one per call: keeps connection reuse and avoids re-allocating the client on every request, at the cost of explicit invalidation logic — the instance has to know when the token changed, which is a small piece of state I now own rather than avoid.

Decision

Favorites in SharedPreferences rather than a Room table: a set of integer IDs doesn't need a schema, a migration, or a DAO, and the read/write cost is negligible at this size. Room and WorkManager are wired in for the sync path where a real local mirror of the offer list does need structure.

Results

A working login → paginated browse → favorite loop against a live API, with the HTTP and auth concerns isolated behind two interfaces and an interceptor instead of spread across the activities.

What I'd do differently

Gate HttpLoggingInterceptor at Level.BODY behind BuildConfig.DEBUG — as it stands, a release build would write bearer tokens and full response bodies to logcat, which is the single change I'd make before shipping anything. I'd also move favorites server-side: they're currently device-local, so the same account on a second device starts empty.