Astute
একটাই Flutter কোডবেজ দিয়ে দুই ধরনের সম্পূর্ণ ভিন্ন ইউজার রোল — টিচার আর স্টুডেন্ট — সার্ভ করা, একই ডিভাইসে concurrent multi-account সেশনসহ, বাংলাদেশ জুড়ে 80+ শিক্ষাপ্রতিষ্ঠানে ডিপ্লয়েড।
সংক্ষেপ
Astute-এর মূল ইঞ্জিনিয়ারিং সমস্যা হলো একটা binary-এর ভেতরে দুইটা সম্পূর্ণ আলাদা অ্যাপ্লিকেশন চালানো: টিচাররা attendance নেয়, marks এন্ট্রি করে, class roster রিভিউ করে, আবার স্টুডেন্টরা তাদের attendance history, exam schedule, আর result চেক করে — একই কোডবেজ, একই ইনস্টল, কিন্তু বিপরীত permission আর data model। এর উপরে আরেক স্তর: একই ফিজিক্যাল ডিভাইসে একসাথে একাধিক signed-in account থাকতে পারে, তাই একজন অভিভাবক যখন নিজের টিচার প্রোফাইল আর দুই সন্তানের স্টুডেন্ট প্রোফাইলের মধ্যে সুইচ করেন, তখন কোনো filter preference leak হওয়া বা notification ভুল অ্যাকাউন্টে যাওয়া চলবে না। role-based UI আর concurrent multi-account state-এর এই কম্বিনেশনই এটাকে সাধারণ CRUD অ্যাপের চেয়ে কঠিন করে তোলে, আর এটা এখন বাংলাদেশ জুড়ে 80+ শিক্ষাপ্রতিষ্ঠানে চলছে।
প্রজেক্টটার দুইটা ফেজ আছে: Banglafire Solutions Ltd.-এ বানানো একটা native Android implementation (Java), আর পরে একটা Flutter cross-platform ভার্সন (Android + iOS)। এই "এক অ্যাপ, অনেক রোল, অনেক অ্যাকাউন্ট" রিকোয়্যারমেন্টটাই আসল ইঞ্জিনিয়ারিং সমস্যা — role-based UI, offline-aware local state, রিয়েল-টাইম push notification, আর multi-account session management, এই সবকিছুকে একসাথে চলতে হয়, কোডবেজটা spaghetti না হয়ে।
সমস্যাটা কী ছিল
শিক্ষাপ্রতিষ্ঠানগুলো টিচার-স্টুডেন্ট-অভিভাবক যোগাযোগ চালাতো অনানুষ্ঠানিক মাধ্যমে — WhatsApp গ্রুপ, কাগজের নোটিশ, আর সরাসরি সাক্ষাৎ। এসবের জন্য কোনো স্ট্রাকচার্ড সিস্টেম ছিল না:
- স্টুডেন্ট attendance ডিজিটালি রেকর্ড আর অ্যাক্সেস করা
- মার্কস, পরীক্ষার সময়সূচি, আর একাডেমিক অগ্রগতি রিয়েল-টাইমে স্টুডেন্টদের সাথে শেয়ার করা
- স্কুলের নোটিশ টিচার, স্টুডেন্ট, আর অভিভাবকদের কাছে নির্ভরযোগ্যভাবে পৌঁছানো
- ক্লাস রুটিন আর কন্টেন্ট ম্যানেজমেন্টের জন্য টিচারদের একটা কেন্দ্রীয় টুল দেওয়া
Astute এই সবকিছুর জায়গায় দিয়েছে একটা স্ট্রাকচার্ড, ইনস্টিটিউট-ব্র্যান্ডেড মোবাইল অভিজ্ঞতা — স্কুল কমিউনিটির দুই পক্ষের জন্যই।
আমার ভূমিকা
ফেজ ১ — Native Android (Banglafire Solutions Ltd.):
- Android SDK দিয়ে Java-তে Teacher App আর Guardian App ডেভেলপ করেছি
- সিকিউর লগইনের জন্য Firebase Authentication ইন্টিগ্রেট করেছি
- OkHttp-সহ Retrofit দিয়ে REST API কমিউনিকেশন বানিয়েছি
- SQLite আর PaperDB দিয়ে লোকাল ডেটা পার্সিস্টেন্স ইমপ্লিমেন্ট করেছি
- পুরো অ্যাপ লাইফসাইকেল ম্যানেজ করেছি: ডেভেলপমেন্ট → টেস্টিং → Play Store রিলিজ → ক্র্যাশ মনিটরিং
ফেজ ২ — Flutter Migration:
- একটাই কোডবেজ থেকে Android আর iOS দুটোই কভার করে Flutter rewrite লিড করেছি, আগে আলাদা থাকা Teacher আর Guardian অ্যাপ দুটোকে একটা অ্যাপে একত্র করেছি role-based UI (teacher / student) আর multi-account session switching সহ
- 2 জন জুনিয়র ডেভেলপারের একটা টিম লিড করেছি, 3টা টাইম জোন জুড়ে জাপানি PM-দের সাথে কোঅর্ডিনেট করেছি — অনুবাদের অস্পষ্টতা দূর করতে ইংরেজি আর জাপানি ভাষায় screen-by-screen spec ডকুমেন্টেশন তৈরি করেছি
- Clean Architecture প্রিন্সিপলের উপর feature-first ফোল্ডার স্ট্রাকচার দিয়ে কোডবেজ আর্কিটেক্ট করেছি — 20+ ফিচার (attendance, marks, notifications, fees, chat...), প্রতিটা একটা self-contained
domain/data/presentationslice - composition root (
get_it+injectable) আর শেয়ার্ডcore/ইনফ্রাস্ট্রাকচার বানিয়েছি: বেসUsecase,Failure,Entity, আরDataModelকন্ট্র্যাক্ট, যার উপর প্রতিটা ফিচার দাঁড়িয়ে - background sync, push notification, আর location service নিয়ে R&D করেছি
- কোড রিভিউ, আর্কিটেকচার আলোচনা, আর পেয়ার প্রোগ্রামিংয়ের মাধ্যমে জুনিয়র ডেভেলপারদের মেন্টর করেছি — জুনিয়ররা এখন স্বাধীনভাবে নিজেদের ফিচার মডিউল ওউন করে
টিচার রোল ফিচার
স্টুডেন্ট Attendance ম্যানেজমেন্ট সহজে ডিজিটালি attendance রেকর্ড করা যায় — আর ম্যানুয়াল রেজিস্টারের দরকার নেই। প্রতিটা ক্লাসের জন্য একটা কেন্দ্রীভূত, সবসময় অ্যাক্সেসযোগ্য attendance রেকর্ড দেয়।
ক্লাস রুটিন দেখা টিচাররা এক নজরে তাদের দৈনিক শিডিউল দেখতে পারে, যা তাদের সংগঠিত থাকতে আর কার্যকরভাবে পাঠ পরিকল্পনা করতে সাহায্য করে।
মার্কস এন্ট্রি প্রতিটা স্টুডেন্টের প্রতিটা সাবজেক্টে গ্রেড ইনপুট আর ম্যানেজ করা যায়। একাডেমিক পারফরম্যান্সের সঠিক, টাইমস্ট্যাম্পড রেকর্ড।
ডিজিটাল কন্টেন্ট শেয়ারিং অ্যাপের মাধ্যমে স্টাডি ম্যাটেরিয়াল, প্রেজেন্টেশন, আর অ্যাসাইনমেন্ট শেয়ার করা যায় — ডেলিভারি কনফার্মেশনসহ পেপারলেস ডিস্ট্রিবিউশন।
নোটিশ বোর্ড অ্যাডমিনিস্ট্রেশনের পোস্ট করা স্কুল-ওয়াইড ঘোষণা আর ইভেন্ট আপডেট দেখা যায়, যাতে টিচাররা সবসময় খবর রাখতে পারে।
অ্যাক্টিভিটি আপডেট ইনস্টিটিউট অ্যাক্টিভিটি, আসন্ন ইভেন্ট, আর প্রফেশনাল ডেভেলপমেন্ট নোটিফিকেশনের রিয়েল-টাইম ফিড।
স্টুডেন্ট রোল ফিচার
স্টুডেন্টরা একই অ্যাপ আর binary-র ভেতরে নিজেদের জন্য বানানো আলাদা ভিউ পায়:
- নিজের রেকর্ডের জন্য attendance history
- পরীক্ষার সময়সূচি আর ফলাফল
- মার্কস আর একাডেমিক অগ্রগতির দৃশ্যমানতা
- স্কুলের নোটিশ আর ঘোষণা
- Multi-account switching — একটা ডিভাইসে একটা টিচার প্রোফাইল আর একাধিক সন্তানের স্টুডেন্ট প্রোফাইল রাখা যায়, কোনোটার filter বা notification routing না হারিয়ে
কারিগরি স্ট্যাক
Native Android ফেজ 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 ফেজ
| Concern | Choice |
|---|---|
| Language / Framework | Dart / Flutter (একই কোডবেজ থেকে iOS + Android) |
| State Management | flutter_bloc — স্ক্রিন-লেভেল state-এর জন্য Cubit, auth-এর জন্য একটা গ্লোবাল AppBloc |
| Dependency Injection | get_it + injectable (annotation-driven, code-generated) |
| Network Client | Dio-র উপর Retrofit — declarative, typed API interface |
| Local Persistence | SharedPreferences, প্রতি user account অনুযায়ী namespaced |
| Data Modeling | Freezed (immutable union) + json_serializable |
| Error Handling | fpdart-এর Either<Failure, T> — কোনো exception layer boundary পার হয় না |
| Routing | go_router — declarative, auth-guarded, deep-link aware |
| Observability | Firebase Analytics + Crashlytics + Cloud Messaging |
কারিগরি সিদ্ধান্ত
Clean Architecture, Feature-First
বেশিরভাগ Flutter কোডবেজ type অনুযায়ী organize হয় — একটা screens/ ফোল্ডার, একটা blocs/ ফোল্ডার, একটা models/ ফোল্ডার। এটা একটা to-do অ্যাপের জন্য ঠিকই কাজ করে; কিন্তু যখন 20+ ফিচার আর একাধিক ইঞ্জিনিয়ার প্রতিদিন একই top-level ফোল্ডার ছোঁয়, তখন এটা ভেঙে পড়ে। Astute বরং অ্যাপটাকে একসাথে দুইভাবে স্লাইস করে: হরাইজন্টালি feature অনুযায়ী (attendance, marks, notifications, fees...) আর ভার্টিক্যালি layer অনুযায়ী (domain, data, presentation) প্রতিটা ফিচারের ভেতরে। প্রতিটা ফিচার একটা সম্পূর্ণ self-contained vertical slice, ভেতরের গঠন হুবহু একই:
নিয়মটা কড়া: domain/ কখনো data/ বা presentation/ থেকে import করে না। প্রতিটা use case একই Usecase<ReturnType, Params> কন্ট্র্যাক্ট ইমপ্লিমেন্ট করে, আর প্রতিটা repository method রিটার্ন করে Either<Failure, T>, তাই failure handling স্পষ্ট আর নীরবে চেপে যাওয়ার সুযোগ নেই:
final result = await _saveAttendanceUsecase(params);
result.fold(
(failure) => emit(_FailureState(message: failure.message)),
(success) => emit(const _SuccessState()),
);ফিচার #21 যোগ করা মানে features/new_feature/{domain,data,presentation} তৈরি করা, আর বাকি কিছু না ছোঁয়া — দুইজন ইঞ্জিনিয়ার আলাদা আলাদা ফিচার বানালে কখনো merge conflict দেখে না, কারণ ডিরেক্টরির বাউন্ডারিই হলো ownership-এর বাউন্ডারি।
Multi-Account Session Management
একটা ডিভাইসে একজন অভিভাবক যখন টিচার প্রোফাইল আর দুই সন্তানের স্টুডেন্ট প্রোফাইলের মধ্যে সুইচ করেন, তখন attendance history, cache করা filter, আর notification routing প্রতিটা অ্যাকাউন্টের জন্য সঠিকভাবে scoped থাকা দরকার। এই মুহূর্তে কোন অ্যাকাউন্ট active — তার single source of truth হলো AppBloc — যেকোনো ফিচারের ডেটা কোনো user-এর সাথে scope করতে হলে সেটা এখান থেকেই পড়ে, ছয় স্তরের constructor জুড়ে userId প্যারামিটার টেনে না নিয়ে গিয়ে।
Background Sync & Push Notifications
অ্যাপ background-এ থাকলেও টিচার আর স্টুডেন্টদের নতুন নোটিশ আর attendance ডেটা দেখতে পারতে হয়। Background sync (Android-এ WorkManager, iOS-এ Flutter plugin দিয়ে Background Fetch) লোকাল ডেটা আপ-টু-ডেট রাখে, তাই Firebase Cloud Messaging দিয়ে সঠিক অ্যাকাউন্টে routed একটা push notification tap করলে সবসময় সর্বশেষ কন্টেন্ট পাওয়া যায়।
চ্যালেঞ্জ ও সমাধান
চ্যালেঞ্জ: 80+ ইনস্টিটিউট, প্রতিটার আলাদা ব্র্যান্ডিং, ফিচার সেট, আর কন্টাক্ট ইনফরমেশন — কিন্তু একটাই অ্যাপ binary।
সমাধান: প্রথম লঞ্চে একটা theme configuration API ইনস্টিটিউট-নির্দিষ্ট কালার, লোগো, আর কপি রিটার্ন করে। অ্যাপটা এই কনফিগারেশন লোকালি cache করে, তাই অফলাইনেও কাজ করে। এই ইনস্টিটিউট-নির্দিষ্ট ডেটা বাকি সবকিছুর মতো একই repository layer দিয়ে যায়, তাই এই behavior থাকে data/-এ, UI কোড জুড়ে ছড়ানো না — একটাই binary প্রতিটা ইনস্টিটিউটের সাথে পুরোপুরি মানিয়ে নেয়।
চ্যালেঞ্জ: প্রতিটা Cubit-এর জন্য widget tree দাঁড় করানো বা mocked HTTP server ছাড়াই business logic টেস্টযোগ্য হতে হতো।
সমাধান: যেহেতু domain/-এ কোনো Flutter বা I/O dependency নেই, use case আর repository logic প্লেইন Dart হিসেবে টেস্ট করা যায়। Repository implementation-গুলো Mockito দিয়ে mocked API client-এর বিরুদ্ধে টেস্ট হয়, .fold()-এর মাধ্যমে Either-এর ফলাফল assert করে। Cubit-গুলো bloc_test দিয়ে টেস্ট হয়, নির্দিষ্ট state-emission সিকোয়েন্স assert করে — প্রতিটা layer আশেপাশের layer থেকে সম্পূর্ণ আলাদাভাবে টেস্ট হয়।
ফলাফল
- Clean Architecture আর feature-first স্ট্রাকচার একাধিক ইঞ্জিনিয়ারকে একসাথে 20+ ফিচার বানাতে দিয়েছে, কোনো merge conflict বা cross-feature regression ছাড়াই
- একটাই Flutter কোডবেজ এখন দুইটা বিপরীত-permission রোল আর concurrent multi-account session সার্ভ করে — প্রজেক্টের সবচেয়ে কঠিন constraint — কোনো role-নির্দিষ্ট fork বা ডুপ্লিকেট business logic ছাড়াই
- Flutter rewrite দ্বিতীয় কোনো native কোডবেজ না বানিয়েই পূর্ণাঙ্গ iOS কভারেজ যোগ করেছে
- মাইগ্রেশনের সময় মেন্টর করা জুনিয়র ডেভেলপাররা এখন স্বাধীনভাবে ফিচার মডিউল ওউন করে
- বাংলাদেশ জুড়ে 80+ শিক্ষাপ্রতিষ্ঠানে চলছে, App Store আর Google Play-এ