Projects
Case Study In Development

UPLB Portal Mobile

A Flutter client for the UPLB Portal — the public landing page as the app's index, UP Mail sign-in, and an update channel that ships builds to testers from one command.

Software Engineer ·2026 – present ·University of the Philippines Los Baños

Flutter Dart go_router Google Sign-In Firebase App Distribution

What It Is

A mobile client for portal.uplb.edu.ph, the university’s front door. It opens on the same public landing page as the web portal — announcements, common services, applications, resources — and signs in with UP Mail through Google.

Same Laravel backend, same Google OAuth client. The app is a second front end, not a second system.

Guest First

The app’s index is the public page, not a login wall. Anyone can open it and read announcements without an account; sign-in only gates the parts that need one.

It mirrors the web section by section: hero with live campus weather, About, latest updates, common services, applications, resources, footer links. Each section is a side-scrolling carousel of equal-height cards, so a section stays about one screenful on a phone instead of a long scroll. Header search filters all four content sections at once, locally.

None of it is hardcoded UI — the whole page renders from a payload:

  • GET /weather/current is live and consumed as-is.
  • GET /api/guest/landing isn’t built yet. Until it ships, the app renders a bundled snapshot of the live page and shows a “Showing saved portal content” banner.

The repository decides which source won, and the UI just reports it. When the endpoint deploys, nothing in the app changes — it starts preferring live content and the banner stops appearing. The snapshot doubles as the offline story.

Weather is treated the same way. If conditions are unavailable the service returns null and the hero says “Weather unavailable”, matching what the web portal does rather than throwing an error into a landing page.

Dark mode uses GitHub’s Primer palette — the same #0d1117, #161b22, #30363d, #e6edf3 the web portal already declares — with UP maroon kept for brand surfaces, so the two don’t drift apart.

Updates and Distribution

Test builds go out through Firebase App Distribution, and the app checks for updates itself. That check reads a version manifest from the portal API rather than the Firebase SDK, so the app doesn’t need Firebase at runtime just to learn it’s out of date. When it’s behind, ota_update installs the new APK in place.

Publishing is one command:

./tool/distribute.sh --bump minor

It bumps pubspec.yaml, builds the APK, uploads it to App Distribution, and tells the portal which build is newest — in that order, from one place. The alternative is bumping the version in one system and publishing the manifest in another, which eventually offers testers the build they’re already running.

Settings is reachable from both the guest page and the signed-in area, so a tester can check for a build without signing in. It also carries appearance, account, a re-fetch of the landing payload that says whether it came from the API or the snapshot, and a diagnostics block — version, build number, environment, API base URL, package name — so a bug report off a distribution build arrives with the details already in it.

What Was Tricky

  • Shipping against an endpoint that doesn’t exist. The snapshot path had to be a real code path rather than a stub, or switching to live data later would have been a rewrite. It’s the same repository, the same models, one flag on the result.
  • Getting a phone into the loop. tool/start.sh targets a physical device by default — no emulator image on disk — and sets up adb reverse when the API base URL is local, so the phone can reach a Laravel running on the dev machine. --pair does the Wi-Fi debugging handshake.
  • Keeping the version honest in three places. pubspec.yaml, the APK on App Distribution, and the manifest the app reads all have to agree. Folding them into one script is what makes that true by construction.