Surovi Agro Nexus+
কৃষি ক্ষেত্র কর্মকর্তাদের জন্য অফলাইন-ফার্স্ট Flutter ফিল্ড-ফোর্স ম্যানেজমেন্ট অ্যাপ — গ্রাম থেকে ভিজিট লগ করা, অর্ডার নেওয়া, আর কালেকশন রিপোর্ট জমা দেওয়া, যেখানে নেটওয়ার্ক থাকাটা জরুরি না।
সংক্ষেপ
Surovi Agro Nexus+ একটা অফলাইন-ফার্স্ট ফিল্ড-ফোর্স ম্যানেজমেন্ট অ্যাপ, বানানো কৃষি ক্ষেত্র কর্মকর্তাদের জন্য — যাদের কর্মদিবসের বেশিরভাগ সময় কাটে নেটওয়ার্ক সিগন্যালের বাইরে। এটা একটা নির্দিষ্ট, খুব একটা গ্ল্যামারাস না এমন সমস্যা সমাধান করে: কর্মকর্তাদের এমন সব গ্রাম থেকে কৃষক ভিজিট লগ করতে হয়, অর্ডার নিতে হয়, আর কালেকশন রিপোর্ট জমা দিতে হয়, যেখানে নেটওয়ার্ক থাকাটা জরুরি না — আর সিঙ্কের অপেক্ষায় থেকে কোনো ডেটা হারানোর সুযোগ নেই।
এই সীমাবদ্ধতাটাই অ্যাপের প্রায় প্রতিটা ইঞ্জিনিয়ারিং সিদ্ধান্ত ঠিক করে দেয়। প্রতিটা লেখা আগে লোকাল ডাটাবেজে যায়; নেটওয়ার্ককে ধরা হয় এমন একটা আপলোড চ্যানেল হিসেবে যেটা মাঝেমধ্যে পাওয়া যায় — কাজ করার জন্য জরুরি কোনো নির্ভরতা হিসেবে না।
100+ ফিল্ড অফিসার নিয়ে প্রোডাকশনে চলছে গ্রামীণ বাংলাদেশ জুড়ে, লাইভ আছে Google Play-এ।
Surovi Group-এর জন্য ফ্রিল্যান্স এনগেজমেন্ট হিসেবে অ্যাপটা বানিয়েছি — কৃষি খাতের একটা বড় প্রতিষ্ঠান, যাদের ফিল্ড অফিসাররা নির্ভরযোগ্য নেটওয়ার্ক কভারেজের অনেক বাইরেও কাজ করে।
সমস্যাটা কী ছিল
এই অ্যাপ আসার আগে, একজন ফিল্ড অফিসারের দিন চালাতে হতো আলাদা আলাদা টুল দিয়ে — নয়তো কাগজে — ভিজিট, অর্ডার, আর কালেকশনের জন্য, আর গ্রামে সিগন্যাল না থাকলে সেসবের কোনোটাই কাজে লাগত না। Surovi-র দরকার ছিল একটাই অ্যাপ, যেটা তিনটাই কভার করবে, জিরো বার সিগন্যালেও ফুল সিগন্যালের মতোই কাজ করবে, আর কভারেজ গ্যাপের জন্য কোনো অফিসারের একটা সকালের কাজ নষ্ট হবে না।
লিড ক্যাপচার দিয়ে শুরু হয়ে, সময়ের সাথে এটা বড় হয়ে একটা পূর্ণাঙ্গ অর্ডার-অ্যান্ড-ডেলিভারি ওয়ার্কফ্লোতে পরিণত হয়েছে, তারপর ডার্ক মোড, তারপর বাংলা আর ইংরেজি লোকালাইজেশন — প্রতিটা সংযোজন ছুঁয়ে গেছে ডেটা মডেল, লোকাল স্টোরেজ, নেটওয়ার্ক কল, আর UI-কে। এই বৃদ্ধিটাই সেই জায়গা, যেখানে architecture আর "থাকলে ভালো" জিনিস থাকে না — বরং ঠিক করে দেয় পরের ফিচারটা দুই দিনে হবে না দুই সপ্তাহে।
আমার ভূমিকা
Surovi Group-এর জন্য ফ্রিল্যান্স এনগেজমেন্ট — architecture, implementation, আর delivery:
- অফলাইন-ফার্স্ট ডেটা লেয়ার ডিজাইন করেছি: প্রতিটা লেখা আগে SQLite-এ যায়, নেটওয়ার্ক থাকে বেস্ট-এফোর্ট সিঙ্ক চ্যানেল হিসেবে
- feature-first ডিরেক্টরি স্ট্রাকচার architect করেছি, যাতে leads, orders, collection reports, customers, আর products প্রতিটা আলাদা vertical slice হিসেবে শিপ করতে পারে
- আগে থেকে থাকা লিড-ক্যাপচার ফাউন্ডেশনের উপর অর্ডার-অ্যান্ড-ডেলিভারি ওয়ার্কফ্লো বানিয়েছি, অন্য ফিচার না ছুঁয়ে
- Flutter-এর বিল্ট-ইন
gen-l10nদিয়ে বাংলা + ইংরেজি লোকালাইজেশন ইমপ্লিমেন্ট করেছি flutter_secure_storageদিয়ে সিকিউর টোকেন স্টোরেজ সেট করেছি, আর Riverpod দিয়ে connectivity-aware state
প্রধান ফিচার
ফার্মার ভিজিট লগিং — অফিসাররা ফিল্ডে কৃষক ভিজিট লগ করে, সিগন্যাল যাই থাকুক না কেন সব ডেটা আগে লোকালি ক্যাপচার হয়।
অর্ডার নেওয়া — মাল্টি-স্টেপ অর্ডার-ক্রিয়েশন ফ্লো, ব্যাকড একটা CurrentOrderNotifier দিয়ে, যেটা সাবমিশন পর্যন্ত ইন-প্রোগ্রেস ড্রাফট ধরে রাখে।
কালেকশন রিপোর্ট — ফিল্ড অফিসাররা যেকোনো জায়গা থেকে কালেকশন রিপোর্ট ফাইল করে — সাথে সাথে লোকাল স্টোরেজে লেখা হয়, কানেক্টিভিটি ফিরলে সিঙ্ক হয়।
ডার্ক মোড — পূর্ণাঙ্গ ডার্ক থিম, core/theme/-এ কেন্দ্রীভূত ডিজাইন টোকেন দিয়ে চালিত।
বাংলা + ইংরেজি লোকালাইজেশন — .arb সোর্স ফাইল আর Flutter-এর জেনারেটেড লোকালাইজেশন ক্লাস দিয়ে সম্পূর্ণ UI লোকালাইজেশন।
কারিগরি স্ট্যাক
| Layer | Choice |
|---|---|
| Language | Dart |
| Framework | Flutter (Material 3) |
| State management | Riverpod (NotifierProvider, StateProvider, ConsumerWidget) |
| Local persistence | SQLite (sqflite) + রেফারেন্স ডেটার জন্য JSON cache file |
| Network client | http, একটা thin custom API client-এ wrap করা |
| Auth storage | flutter_secure_storage |
| Localization | Flutter-এর built-in gen-l10n (বাংলা + ইংরেজি) |
কারিগরি সিদ্ধান্ত
Feature-First, ভেতরে Layered
প্রতিটা business capability — leads, orders, collection reports, customers, products — একটা self-contained vertical slice, আর প্রতিটা slice ভেতরে দায়িত্ব অনুযায়ী layered:
এখানে দুটো নিয়মই সব কাজ করে: টাইপ অনুযায়ী না, ফিচার অনুযায়ী গ্রুপ করা — orders নিয়ে সব কিছু থাকে features/orders/-এর নিচে, ব্যাস। আর প্রতিটা ফিচারের ভেতরে সুবিধামতো না, দায়িত্ব অনুযায়ী লেয়ার করা — কোনো স্ক্রিন সরাসরি SQLite বা http-এর সাথে কথা বলে না, শুধু data_sources/-এর মাধ্যমে। এটা Clean Architecture-এর data/domain/presentation split-এর একটা হালকা ভার্সন, Flutter অ্যাপের মাপে বানানো: models/ হলো domain layer, data_sources/ হলো data layer, screens/ + providers/ হলো presentation।
অফলাইন-ফার্স্ট Data Sources
প্রতিটা ফিচার একটা *LocalSource (raw SQLite CRUD) আর একটা *RemoteSource (HTTP কল আর রেসপন্স পার্সিং) পায়। এটাই সেই seam, যেটা অফলাইন-ফার্স্টকে সহজ করে তোলে: OrdersRemoteSource.createOrder() সফল হলে সরাসরি OrdersLocalSource-এ লিখে দেয়, তাই "এই অর্ডারটা কি সিঙ্ক হয়েছে" প্রশ্নের উত্তর সবসময় এক জায়গা থেকেই পাওয়া যায় — UI state জুড়ে ছড়িয়ে থাকে না।
একটা Thin Services Facade
services/-এর Api আর AppDatabase — এই দুটো ক্লাসই বেশিরভাগ স্ক্রিন ডেটা অ্যাক্সেসের জন্য ইমপোর্ট করে — এরা প্রতিটা ফিচারের ডেটা-সোর্স মেথডকে এক নামের নিচে রি-এক্সপোর্ট করে। কোনো ডেটা ভেতরে ভেতরে কোন ফিচারের "মালিকানায়" আছে, সেটা স্ক্রিনগুলোর জানার দরকার নেই, আর logging, retry policy, বা auth-refresh-এর মতো cross-feature বিষয় যোগ করার জন্য এটাই একমাত্র জায়গা — প্রতিটা ফিচার ছুঁতে হয় না।
চ্যালেঞ্জ ও সমাধান
চ্যালেঞ্জ: প্রতিটা লেখাকে টিকে থাকতে হবে একজন অফিসার ঘন্টার পর ঘন্টা অফলাইনে থাকার পরও, গ্রাম ভিজিটের মাঝে — কোনো ডুপ্লিকেট অর্ডার বা হারানো কালেকশন রিপোর্ট ছাড়া, যখন শেষমেশ সিগন্যাল পায়।
সমাধান: কোনো ফিচারের পার্সিস্টেন্স আর নেটওয়ার্কে হাত দেওয়ার একমাত্র অনুমতি data_sources/-এর — remote write সফল হলে matching local source-এ যায়, তাই sync state ঠিক এক জায়গায় ট্র্যাক হয়, ছড়ানো UI state থেকে অনুমান করতে হয় না।
চ্যালেঞ্জ: অ্যাপটা লিড ক্যাপচার থেকে বেড়ে একটা পূর্ণাঙ্গ অর্ডার-অ্যান্ড-ডেলিভারি ওয়ার্কফ্লো, সাথে ডার্ক মোড আর লোকালাইজেশন হয়ে উঠেছে — ফিচার ডেলিভারি স্লো না করে।
সমাধান: feature-first ফোল্ডার মানে একটা ফিচার যোগ করা মানে একটা ফোল্ডার যোগ করা, বিদ্যমান কিছু এডিট করা না। যখন অর্ডার-অ্যান্ড-ডেলিভারি ট্র্যাকিং যোগ হলো, leads/ বা collection_reports/-এর ভেতরে জিরো চেঞ্জ লাগলো — নতুন data_sources/, models/, providers/, আর screens/ সাবফোল্ডার features/orders/-এর নিচে, services/ আর core/router.dart-এ যুক্ত করে দেওয়া, আর ফিচার লাইভ।
চ্যালেঞ্জ: SQLite আর HTTP দুটোই ছোঁয় এমন স্ক্রিন টেস্ট করা মানে হয় পুরো স্ট্যাক mock করা, নয়তো কিছুই টেস্ট না করা।
সমাধান: যেহেতু আসল I/O ছোঁয় শুধু data_sources/, তার উপরের সব কিছু — providers/, screens/ — একটা fake *LocalSource/*RemoteSource-এর বিরুদ্ধে টেস্ট করা যায়, কোনো ডাটাবেজ বা সার্ভার না চালিয়েই।
ফলাফল
- গ্রাম ভিজিটের মাঝে ঘন্টার পর ঘন্টা অফলাইনে থাকার পরও প্রতিটা লেখা টিকে থাকে — কোনো ডুপ্লিকেট অর্ডার না, হারানো কালেকশন রিপোর্ট না, কানেক্টিভিটি ফিরলে সিঙ্ক নিজে থেকেই সমাধান হয়
- লিড ক্যাপচার থেকে অ্যাপটা বড় হয়ে একটা পূর্ণাঙ্গ অর্ডার-অ্যান্ড-ডেলিভারি ওয়ার্কফ্লো, ডার্ক মোড, আর বাংলা/ইংরেজি লোকালাইজেশনে পরিণত হয়েছে, একবারও architectural rewrite ছাড়া
- Feature isolation পুরো প্রজেক্টের জীবদ্দশা জুড়ে merge conflict-এর ঝুঁকি প্রায় শূন্যে রেখেছে, আর প্রতিটা নতুন capability আলাদা slice হিসেবে শিপ করতে দিয়েছে
- একজন নতুন ইঞ্জিনিয়ার যে একটা ফিচারের ফোল্ডার স্ট্রাকচার বোঝে, সে ইতিমধ্যেই বাকি সব ফিচারেরটাও বোঝে
- যেটা শুরু হয়েছিল লিড-ক্যাপচার টুল হিসেবে, সেটাই এখন গ্রামীণ বাংলাদেশ জুড়ে 100+ অফিসারের দৈনন্দিন ফিল্ড টুল