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.
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) built at Teamexus Solutions Ltd. 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.
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 at ISOL 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 / presentation slice
Built the composition root (get_it + injectable) and the shared core/ infrastructure: base Usecase, Failure, Entity, and DataModel contracts 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
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:
features/student_list/
├── domain/Pure Dart — entities, repository contracts, use cases
├── data/Retrofit data sources, Freezed DTOs, repository implementations
└── presentation/Cubits + screens
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