Astute
Engineering one Flutter codebase to serve two fundamentally different user roles — teachers and students — with concurrent multi-account sessions on a single device, deployed across 80+ educational institutes in Bangladesh.
Overview
Astute's core engineering problem is running two fundamentally different applications inside one binary: teachers take attendance, enter marks, and review class rosters, while students check attendance history, exam schedules, and results — same codebase, same install, opposite permission and data models. Layered on top of that: the same physical device can hold multiple signed-in accounts at once, so a parent switching between their own teacher profile and two children's student profiles must never leak a filter preference or misroute a notification between them. That combination — role-based UI plus concurrent multi-account state — is what makes this harder than a typical CRUD app, and it's now running across 80+ educational institutes in Bangladesh.
The project has two phases: a native Android implementation (Java) built at Banglafire Solutions Ltd., and a later Flutter cross-platform version (Android + iOS). That "one app, many roles, many accounts" requirement is the real engineering problem — role-based UI, offline-aware local state, real-time push notifications, and multi-account session management all have to coexist without the codebase collapsing into spaghetti.
The Problem
Educational institutes were managing teacher-student-guardian communication through informal channels — WhatsApp groups, paper notices, and in-person meetings. There was no structured system for:
- Recording and accessing student attendance digitally
- Sharing marks, exam schedules, and academic progress with students in real time
- Broadcasting school notices reliably to teachers, students, and parents
- Giving teachers a central tool for class routine and content management
Astute replaced all of this with a structured, institute-branded mobile experience for both sides of the school community.
My Role
Phase 1 — Native Android (Banglafire Solutions Ltd.):
- Developed the Teacher App and Guardian App in Java using Android SDK
- Integrated Firebase Authentication for secure login
- Built REST API communication using Retrofit with OkHttp
- Implemented local data persistence with SQLite and PaperDB
- Managed the full app lifecycle: development → testing → Play Store release → crash monitoring
Phase 2 — Flutter Migration:
- Led the Flutter rewrite covering both Android and iOS from a single codebase, unifying the previously separate Teacher and Guardian apps into one app with role-based UI (teacher / student) and multi-account session switching
- Led a team of 2 junior developers, coordinating with Japanese PMs across 3 time zones — established screen-by-screen spec documentation in English and Japanese to eliminate translation ambiguity
- Architected the codebase on Clean Architecture principles with a feature-first folder structure — 20+ features (attendance, marks, notifications, fees, chat...), each a self-contained
domain/data/presentationslice - Built the composition root (
get_it+injectable) and the sharedcore/infrastructure: baseUsecase,Failure,Entity, andDataModelcontracts every feature builds on - Conducted R&D on background sync, push notifications, and location services
- Mentored junior developers through code reviews, architecture discussions, and pair programming — juniors now own feature modules independently
Teacher Role Features
Student Attendance Management Effortlessly record attendance digitally — no more manual registers. Provides a centralised, always-accessible attendance record per class.
Class Routine Viewing Teachers see their daily schedule at a glance, helping them stay organised and plan lessons effectively.
Marks Entry Input and manage grades per student per subject. Accurate, timestamped records of academic performance.
Digital Content Sharing Share study materials, presentations, and assignments through the app — paperless distribution with delivery confirmation.
Notice Board View school-wide announcements and event updates posted by administration, ensuring teachers are always informed.
Activity Updates Real-time feed of institute activities, upcoming events, and professional development notifications.
Student Role Features
Students get their own tailored view within the same app and binary:
- Attendance history for their own record
- Exam schedules and results
- Marks and academic progress visibility
- School notices and announcements
- Multi-account switching — one device can hold a teacher profile and multiple children's student profiles without losing filters or notification routing for any of them
Technical Stack
Native Android Phase Android SDK, Java, Retrofit, Firebase Authentication, Google Play Services, SQLite, PaperDB, Material Design, CircleImageView, Picasso, Gson, OkHttp, RecyclerView, CardView, Volley, Android PDF Viewer, Google Play Core
Flutter Phase
| Concern | Choice |
|---|---|
| Language / Framework | Dart / Flutter (iOS + Android from one codebase) |
| State Management | flutter_bloc — Cubits for screen-level state, a global AppBloc for auth |
| Dependency Injection | get_it + injectable (annotation-driven, code-generated) |
| Network Client | Retrofit over Dio — declarative, typed API interfaces |
| Local Persistence | SharedPreferences, namespaced per user account |
| Data Modeling | Freezed (immutable unions) + json_serializable |
| Error Handling | fpdart's Either<Failure, T> — no exceptions crossing layer boundaries |
| Routing | go_router — declarative, auth-guarded, deep-link aware |
| Observability | Firebase Analytics + Crashlytics + Cloud Messaging |
Technical Decisions
Clean Architecture, Feature-First
Most Flutter codebases organize by type — a screens/ folder, a blocs/ folder, a models/ folder. That works for a to-do app; it falls apart once 20+ features and multiple engineers are touching the same top-level folders every day. Astute instead slices the app two ways at once: horizontally by feature (attendance, marks, notifications, fees...) and vertically by layer (domain, data, presentation) within each feature. Every feature is a fully self-contained vertical slice with an identical internal shape:
The rule is strict: domain/ never imports from data/ or presentation/. Every use case implements the same Usecase<ReturnType, Params> contract and every repository method returns an Either<Failure, T>, so failure handling is explicit and impossible to silently swallow:
final result = await _saveAttendanceUsecase(params);
result.fold(
(failure) => emit(_FailureState(message: failure.message)),
(success) => emit(const _SuccessState()),
);Adding feature #21 means creating features/new_feature/{domain,data,presentation} and touching nothing else — two engineers building unrelated features never see a merge conflict, because the directory boundary is the ownership boundary.
Multi-Account Session Management
A parent switching between a teacher profile and two children's student profiles on one device needs attendance history, cached filters, and notification routing to stay correctly scoped per account. AppBloc is the single source of truth for which account is active right now — every feature that needs to scope data to a user reads from it rather than threading a userId parameter through six layers of constructors.
Background Sync & Push Notifications
Teachers and students need to see fresh notices and attendance data even when the app is backgrounded. Background sync (WorkManager on Android, Background Fetch via a Flutter plugin on iOS) keeps local data current so a push notification tap — routed to the right account via Firebase Cloud Messaging — always finds up-to-date content.
Challenges & Solutions
Challenge: 80+ institutes with different branding, feature sets, and contact information — but one app binary.
Solution: A theme configuration API returns institute-specific colours, logos, and copy on first launch. The app caches the configuration locally so it works offline. That institute-specific data flows through the same repository layer as everything else, so the behavior lives in data/, not scattered through UI code — the single binary fully adapts to each institute.
Challenge: Business logic needed to be testable without spinning up a widget tree or a mocked HTTP server for every Cubit.
Solution: Because domain/ has zero Flutter and zero I/O dependencies, use cases and repository logic are tested as plain Dart. Repository implementations are tested against mocked API clients with Mockito, asserting on the Either outcome via .fold(). Cubits are tested with bloc_test, asserting exact state-emission sequences — each layer tested in complete isolation from the layers around it.
Challenge: Japanese PM communication introduced translation ambiguity — requirements that seemed clear in English carried different implications in Japanese context.
Solution: We built a screen-by-screen specification document maintained in both English and Japanese before any development began. Any scope change required updating the spec first, creating a clear paper trail and reducing mid-sprint pivots.
Outcome
- Clean Architecture and feature-first structure let 20+ features get built in parallel by multiple engineers without merge conflicts or cross-feature regressions
- A single Flutter codebase now serves two opposite-permission roles and concurrent multi-account sessions — the hardest constraint of the project — without role-specific forks or duplicated business logic
- The Flutter rewrite added full iOS coverage without standing up a second native codebase
- Junior developers mentored through the migration now own feature modules independently
- Running across 80+ educational institutes in Bangladesh, on the App Store and Google Play