eBangladesh
A Flutter e-commerce app unifying four incompatible Bangladeshi payment gateways behind one interface, with a local-first cart and connectivity-aware UX built for mid-range devices across the whole country.
Overview
eBangladesh's hardest engineering problem wasn't the shopping flow — it was payments. Four Bangladeshi gateways (bKash, Nagad, uPay, aamarPay), four incompatible integration patterns, four different failure vocabularies, and a checkout screen that couldn't be allowed to know or care which one a customer picked. On top of that: a large share of the target userbase is on mid-range Android devices with inconsistent connectivity, so the app also had to stay usable — browsing, cart, checkout warnings — when the network wasn't.
I built the Flutter mobile app at Codeboxr, designing the payment abstraction layer, the offline-tolerant cart, and the connectivity-aware UX that made the app usable across the country, not just in areas with reliable signal.
The Problem
E-commerce penetration in Bangladesh outside of Dhaka required a different approach than traditional marketplaces. Customers in semi-urban and rural areas needed local coordinators they could trust, local payment methods they already used (bKash, Nagad), and an app that worked reliably on mid-range devices with variable connectivity.
eBangladesh's model of having a physical coordinator in every square mile needed a mobile app that made both browsing and buying frictionless for users across the country.
My Role
I built the Flutter mobile app at Codeboxr — responsible for architecture, implementation, and delivery:
- Designed a payment gateway abstraction layer — unified bKash, Nagad, uPay, and aamarPay behind a common
PaymentGatewayinterface with a singlepay(amount, orderId)contract. Each gateway's WebView redirects, token management, and error normalization are sealed behind that interface; the checkout screen never touches a gateway directly. - Chose Provider over Bloc or Riverpod — the app's state graph is relatively flat (cart, auth, connectivity), and the team context at Codeboxr favored Provider's simplicity. The tradeoff was accepted: less compile-time safety for faster onboarding and fewer boilerplate files across 15+ screens.
- Implemented a local-first cart backed by
shared_preferences— users can browse, add to cart, and manage wishlists entirely offline. Cart state persists across sessions without requiring authentication, reducing checkout friction for first-time users. - Built connectivity-aware UX using
connectivity_plus— on network loss the app degrades gracefully: cached product data remains browsable, cart operations work offline, and the user is warned before attempting payment without a connection. - Optimized for mid-range Android devices (2GB RAM, Android 8+) — profiled on real devices throughout development. Used
ListView.buildereverywhere, kept widget trees shallow, deferred non-critical initialization withaddPostFrameCallback, and standardized image loading viacached_network_imagewith fixed dimensions and shimmer placeholders.
Technical Decisions
Payment Gateway Abstraction
Four gateways, four integration patterns — bKash and Nagad use WebView redirects, uPay uses a custom SDK, aamarPay provides a direct card form. Each returns different response objects and error codes. Rather than branching on gateway type throughout the checkout flow, I defined a common contract:
abstract class PaymentGateway {
Future<PaymentResult> pay(double amount, String orderId);
}Each gateway implementation encapsulates its own redirect flow, token lifecycle, and error normalization behind that single method. The checkout screen depends only on the interface — adding a fifth gateway means writing one new class, touching zero checkout code.
Local-First Cart Without an Account
E-commerce apps typically require login before adding to cart. I chose shared_preferences (not SQLite) for the cart and wishlist because the data is small (a list of product IDs + quantities), session-scoped, and doesn't need querying. This lets users build a cart before committing to sign-up — a deliberate product decision to reduce abandonment in a market where users are cautious about sharing credentials.
Connectivity-Aware Architecture
Rather than wrapping every network call in try-catch, connectivity_plus feeds a singleton stream into Provider. Screens subscribe to connectivity state and adjust their UI accordingly: product grids remain fully browsable from cache, the checkout button shows a warning banner when offline, and the cart badge continues to update. This decouples connectivity logic from business logic — no screen needs to know how connectivity is detected.
Technical Stack
| Package | Purpose |
|---|---|
dio | Network requests with interceptors |
provider | State management |
shared_preferences | Local persistence (cart, wishlist, session) |
connectivity_plus | Network state monitoring |
cached_network_image | Image loading and caching |
carousel_slider | Product image galleries |
image_picker | Profile photo selection |
flutter_svg | SVG asset rendering |
pin_code_fields | OTP verification UI |
syncfusion_flutter_pdfviewer | Invoice and document viewing |
share_plus | Share products via platform share sheet |
Challenges & Solutions
Challenge: Four different payment gateways, each with a distinct integration pattern, error code vocabulary, and sandbox behaviour. Keeping the checkout flow consistent regardless of which gateway the user chose was complex.
Solution: Built a payment gateway abstraction layer — a common PaymentGateway interface with a pay(amount, orderId) method. Each gateway implementation handles its own redirect flow, token management, and error normalisation behind that interface. The checkout screen only knows about the interface, not the gateways.
Challenge: Product images from vendor systems were inconsistent in size and quality, causing layout jank as images loaded.
Solution: Standardised image dimensions via cached_network_image with fixed width and height constraints and a shimmer placeholder. Images are loaded lazily via ListView.builder so only visible items trigger network requests.
Challenge: App needed to work on budget Android devices (2GB RAM, older Android versions) that are common outside Dhaka.
Solution: Profiled on a real mid-range device throughout development. Used ListView.builder everywhere, kept widget trees shallow, and deferred initialisation of non-critical services using addPostFrameCallback.
Outcome
- A single
PaymentGatewayinterface absorbed all four gateways' quirks — adding a fifth would mean one new class and zero changes to checkout code - Local-first cart and connectivity-aware UX kept the app fully browsable and largely usable through the connectivity gaps common outside Dhaka
- Rendering and initialization work tuned specifically for 2GB-RAM, Android 8+ devices, profiled on real hardware rather than emulators
- Post-launch monitoring drove targeted fixes that meaningfully reduced the crash rate
- Live on Google Play, with the platform at ebangladesh.com