From Prototype to Production — Building Mobile Apps with Flutter in Nigeria
How we build production Flutter apps for Nigerian fintech, e-learning, and trading clients — from architecture choices to App Store submission.
Flutter has won the cross-platform argument in our shop. We’ve shipped a fintech wallet (CreditPoint), an e-learning app (Learnrail), and a gift-card and crypto trading app (Martizzy Trade) on it. We’ll keep using it for most projects until something better comes along.
This article isn’t a Flutter advert. It’s how we actually build with it — the architecture choices, the libraries we keep reaching for, and the gotchas that have bitten us shipping real apps to Nigerian users.
Why Flutter, specifically
Three reasons:
- One codebase, two stores. A team of 3 ships to iOS and Android in roughly the same time as 5 native engineers. The economics for African startups are decisive.
- Performance is good enough. Flutter renders to its own canvas, so frame timing is consistent across devices. Mid-range Android phones — which is what most Nigerian users have — perform better with Flutter than with React Native.
- The ecosystem matured. As of 2026, every important integration (Firebase, Sentry, Paystack, Flutterwave, biometric auth, deep links) has a maintained Flutter package.
The case against Flutter, fairly: deeply native UI patterns (Apple Wallet integration, ARKit, intricate platform-specific gestures) are harder. App size is bigger than equivalent native apps (~15 MB minimum download). And the Flutter community can sometimes solve a problem with eight different state-management solutions. We have opinions.
Project structure that scales
We use a feature-first folder structure rather than a type-first one. Type-first looks like:
lib/
models/
controllers/
screens/
widgets/
Feature-first looks like:
lib/
features/
auth/
data/ <- repositories, data sources
domain/ <- entities, use cases
presentation/ <- screens, widgets, controllers
wallet/
data/
domain/
presentation/
transfer/
...
core/ <- shared utilities, constants, theme
app.dart
main.dart
The advantage: when you delete a feature, you delete one folder. When you onboard a new engineer, they understand the wallet without reading the auth code.
State management we actually use
We default to Riverpod. It’s predictable, testable, and handles dependency injection cleanly without the boilerplate of older patterns.
For genuinely complex state (e.g., a multi-step KYC wizard with branching logic), we sometimes reach for flutter_bloc because the explicit state classes make a complex state machine easier to reason about.
What we don’t use: setState everywhere, Provider as the only state solution, or Redux — the ecosystem has moved on.
Offline-first
This matters in Nigeria. Connections drop. Users get on planes. Data tariffs end mid-session. An app that breaks the moment the connection drops is an app users uninstall.
Our pattern:
- Local store:
drift(formerly Moor) for relational data,hivefor simple key-value. - Sync layer: a queue of pending writes that retry with exponential backoff when connectivity returns. Use
connectivity_plusto detect network changes and trigger sync. - Conflict resolution: last-write-wins for most fields, server-priority for sensitive ones (balance, transaction status).
- UI feedback: show a subtle banner (“Working offline — will sync when connected”) rather than blocking errors.
For a wallet, the rules are tighter: never let the user submit a transfer offline (because you’d be lying about the balance). Show a clear “no connection” state instead.
Payment flows
The biggest source of bugs in Nigerian fintech apps is payment integration. The defaults that have served us well:
- Use the official SDKs. Paystack and Flutterwave both maintain Flutter packages. They handle the deep linking, redirect handling, and webview lifecycle for you.
- Always pass an idempotency key for any payment initiation, so the user can retry without double-charging.
- Never trust the client about success. The mobile SDK saying “payment successful” is a hint, not a fact. Verify server-side via webhook.
- Have an explicit “pending” state in the UI for payments where the webhook hasn’t fired yet. Don’t lie to the user that the transfer succeeded; don’t make them stare at a spinner forever.
Authentication & biometrics
Pattern we use:
- First sign-in: email/phone + password + OTP.
- After successful auth: offer biometric setup (“Use TouchID for faster sign-in?”). Store the auth token in the platform’s secure storage (
flutter_secure_storage). - Subsequent sign-ins: biometric prompt → unlock the stored token → call backend
/refreshto issue a new short-lived access token. - Sensitive actions (transfer above ₦100k, change password): re-prompt for biometric or PIN.
Critical: never store the password locally, even encrypted. Store the long-lived refresh token, protected by biometric, and exchange for short-lived access tokens.
Push notifications & deep links
For push, we use Firebase Cloud Messaging (FCM) for both Android and iOS. APNs is the iOS provider but FCM wraps it cleanly.
For deep links, the modern Android+iOS approach is Universal Links / App Links with a fallback to deep-link schemes for older OS versions. Configure them so:
- A user clicks
https://creditpointapp.com/transfer/abc123from a WhatsApp message. - If the app is installed, it opens directly to the transfer detail page.
- If not, the user lands on a web page that prompts them to install.
We use the uni_links package and a custom router that maps incoming URIs to navigator destinations.
Crash reporting & observability
Three things, on every production Flutter app we ship:
- Sentry for error tracking. We capture every uncaught exception, every Flutter framework error, and every network error with a 5xx response.
- Firebase Crashlytics as a second source of truth. Sentry is better for our team’s debugging workflow; Crashlytics has deeper integration with Google Play stability metrics.
- Custom event logging for product analytics — we use Firebase Analytics or Mixpanel depending on the client.
Critical: configure Sentry to capture release versions and scrub sensitive fields (auth tokens, account numbers, KYC documents) before they leave the device.
App Store & Play Store submission
The reality of shipping to the stores:
Apple App Store
- First submission almost always gets a rejection. The most common reasons we hit: missing privacy policy URL, account-deletion flow not implemented, IAP-vs-external-purchase clarity, or copy that mentions “subscriptions” without IAP.
- Budget 1–2 weeks for the back-and-forth on first submission. Subsequent updates clear in 24–48 hours.
- Apple charges $99/year for the developer account.
- Test on a real iPhone before submitting; the simulator misses platform-specific bugs.
Google Play Store
- Faster review, usually 12–48 hours.
- $25 one-time fee.
- Required: a privacy policy URL, target SDK level matching the latest requirements, an in-app account deletion flow.
- Pre-launch report runs your app on a small fleet of real Android devices and surfaces crashes — read it.
Both stores
- App Store Optimisation (ASO) matters more than you think. Strong screenshots, clear copy, keyword-targeted titles. We work with clients to craft these before submission.
- Sign your release builds properly. Lost signing keys are catastrophic.
Performance budgets we enforce
For every production Flutter app we ship:
- Cold start under 2 seconds on a mid-range Android (Samsung A35, Tecno Camon).
- App size under 25 MB download. Pay attention to fonts, images, and unused dependencies.
- No frame drops on the main flows. The Flutter DevTools timeline is your friend.
- Memory usage under 200 MB in normal use.
We test on a hardware fleet (a couple of mid-range Androids and a 3-year-old iPhone) before every release. Production users don’t have flagship devices.
Worked example — CreditPoint
CreditPoint is a Nigerian fintech wallet built end-to-end on Flutter + Laravel API. It demonstrates most of the patterns above:
- Feature-first architecture across
auth,wallet,transfer,cards,kyc. - Riverpod for state.
- Drift for local storage; offline-tolerant for read paths.
- Paystack/Flutterwave for payment via official SDKs, with server-side verification.
- Biometric auth via
local_auth. - Sentry + Firebase Crashlytics for observability.
- 12-week build, ₦25M-class budget.
Read the full case study for the architecture and outcome.
FAQ
Should I use Flutter or React Native?
Flutter, in 2026, for most cases. React Native works, especially if your team is already React-fluent, but Flutter’s performance, widget consistency, and tooling have edged ahead.
Can Flutter do everything native can?
Almost. The exceptions: deeply embedded platform features (Apple Watch complications, advanced widget extensions, ARKit). For 95% of consumer apps, you won’t notice the gap.
Will Flutter still be supported in 5 years?
Google’s commitment is strong (Flutter is the engine for parts of Google’s own products and the basis of Fuchsia OS apps). The community is huge. The risk is comparable to any framework choice you make today.
How big is your Flutter team?
In Lagos, we have 5 Flutter engineers across our portfolio. Hiring strong Flutter engineers in Nigeria takes longer than hiring strong Laravel engineers — but they exist, and the pipeline is growing.
What about Compose Multiplatform?
JetBrains’ Compose Multiplatform is interesting but not yet mature enough for production fintech. We watch it. We don’t ship on it.
Building a mobile app? Send us a brief — Flutter, native, or hybrid, we’ll tell you what fits your project.
Related reading: How much does mobile app development cost in Nigeria? · How to build a fintech app in Nigeria