02Aug. 2025Sole developerWIP

Hesabati

Every account, one screen.

A Flutter fintech dashboard that aggregates accounts across multiple banks, with transfers, spending analytics, child card controls, and conversion across 150+ currencies. Fully bilingual English and Arabic with right-to-left layout. It runs on mock data and won the Best Idea Award, recognised by CODED and Boursa Kuwait.

Best Idea Award, CODED & Boursa Kuwait

The problem

If you bank with more than one institution in Kuwait, you have more than one banking app, and none of them will answer the only question you actually have. How much money do I have. Getting to that number means opening three apps, reading three balances, and doing arithmetic on your phone's calculator, which is a thing I did often enough to be annoyed by it.

The second problem was smaller and more specific. Parents give a card to a teenager and then lose all visibility. There is no limit they can set, no category breakdown, and no way to check what was spent without asking, which turns a convenience into an argument.

Both problems are aggregation problems. Neither bank is doing anything wrong; there is just nowhere that the whole picture exists.

The constraints

This was the final project for CODED Academy's UniCode Program, so the shape of it was set before I started. Flutter track, fixed deadline, and a demo at the end judged partly on the idea and partly on the build.

The hard constraint was that no bank in Kuwait was going to give a student an API key. Open banking is a licensing conversation, not a technical one. So I could either build something smaller that was real, or build the actual product against mock data and be straightforward about it. I chose the second, because the interesting problems in this idea are interface problems, and those are just as hard against fake balances as real ones.

The other constraint was Arabic. This is a product for people in Kuwait, so an English-only version would have been a demo of something nobody would use. Arabic had to be a first class language, which as it turns out is a layout problem far more than a translation problem.

What I built

A Flutter application for iOS and Android from one codebase. The home screen shows total balance across accounts, excluding credit card debt, with a scrolling carousel of accounts. There are three account types: bank accounts, credit cards, and child or beneficiary cards, each with full create, read, update and delete.

Transfers move money between your own accounts with validation for sufficient funds, and a biometric confirmation step. Manual transactions let you record spending against a category. Analytics breaks spending down by category with percentages.

Child card management sets a spending limit with a visual progress bar showing consumption against it, and per-child notification toggles.

The part I am most pleased with is the foreign exchange module. 150+ currencies with real ISO 4217 metadata, a converter, a currency picker, and rates displayed with the timestamp they were captured.

The whole thing is bilingual with proper right-to-left layout, light and dark themes, and Material Design 3 throughout.

What this is and is not

It is not connected to a bank. There is no backend, no API and no real money. Every account, balance and transaction is mock data: five accounts, two of which are child cards, and seven transactions.

The login screen does not authenticate anyone. It accepts demo credentials and moves on. Registration creates an empty in-memory state and nothing is stored anywhere.

The biometric and two-factor prompts are animations. No platform biometric API is called. They demonstrate the intended flow, and in the code they are named as simulations rather than dressed up as integrations.

The exchange rates are a snapshot captured to JSON in October 2025, not a live feed. The conversion arithmetic against that snapshot is real and correct. The rates themselves are frozen and drift further from reality every day, which is why the capture timestamp is on screen.

What is real is the entire application: state management, navigation, the localisation and right-to-left system, theming, the conversion engine, form validation, and the analytics computed from those mock transactions.

I would rather say all of this up front than have someone discover it. The engineering that was in scope is genuinely there. Connecting a real banking backend was never a thing I could have done, and pretending otherwise would make everything else I say less credible.

The technical decisions

Decision Reasoning
Provider with a single central AppState The whole app is one user's financial state. One ChangeNotifier with Consumer widgets is proportionate to that. Bloc would have added event and state classes for every interaction to solve a coordination problem I did not have
All rates stored against KWD as a base 150 currencies as a full pair matrix is 22,500 entries that can contradict each other. As a base plus 150 rates, internal inconsistency is not representable. Every conversion routes through the dinar
ISO 4217 metadata kept separate from rate data Currency minor units are not always two. KWD has three decimal places, JPY has none. Formatting a dinar to two places is wrong in the intended user's own currency
Directionality-aware layout instead of a parallel Arabic UI Expressing padding and alignment relative to the start and end of the reading order, rather than to the left and right of the screen, means one layout follows the locale. Two layouts would have meant every future change made twice, and one of them eventually forgotten
A static JSON snapshot rather than a live rates API Live rates need a key and usually a paid tier. A dated snapshot with the date shown is honest, costs nothing, and does not fail in a demo because a free tier ran out
The FX feature isolated into data, domain, ui and util It is the one part with logic worth separating from presentation, and the only part that could be lifted into another project unchanged

The hardest thing

Right-to-left, and not for the reason I expected.

I assumed Arabic support was a translation task: extract the strings, add a second file, switch on locale. That part took an afternoon. Then I ran it in Arabic and the layout was subtly wrong everywhere in ways that were hard to name. Padding sat on the wrong side. Icons that meant "forward" pointed backwards. A row that read naturally in English read inside out. Nothing crashed and nothing looked obviously broken, which made it worse, because I could not point at a bug.

What I had done was write the layout in absolute terms. EdgeInsets.only(left: 16) means sixteen pixels on the left, always, in every language. What I wanted was sixteen pixels before the content, where "before" depends on the reading direction.

Flutter has the answer built in. Its layout system distinguishes physical directions from directional ones, so padding and alignment can be expressed relative to the start and end of the reading order rather than to the left and right of the screen. Once I understood that distinction the fix was mechanical. The work was finding every place I had written a physical direction when I meant a logical position.

The genuine lesson was that "supports Arabic" is not a feature you add. It is a property of how you write every layout in the app, and retrofitting it means auditing all of them. If I had known that on day one I would have written the first screen directionally and never paid the cost.

The currency formatting problem was the same shape in miniature. I formatted everything to two decimal places because that is what money looks like, and then noticed KWD has three. Hardcoding two would have been wrong in the currency of the people the app was for.

What I would do differently

The architecture is inconsistent and that is the thing I would fix. The FX module is properly layered into data, domain, UI and utilities. The rest of the app is organised by widget type with logic living inside screens. The FX module is what the whole application should look like, and it only turned out that way because I built it last and had learned something by then. I would apply that structure from the first screen.

I would add persistence. The app forgets everything on restart, which means nobody can evaluate it the way they would actually use it. Local storage is not a large piece of work and its absence undercuts the demo.

I would use local_auth instead of a simulated biometric animation. It is a real integration, it is not difficult, and having the genuine article would have removed the largest asterisk from the project.

And I would put the rates behind a fetch with the snapshot as a fallback, so that updating them does not require shipping a new build.

What came of it

It won the Best Idea Award, recognised by CODED and Boursa Kuwait, at the end of the UniCode Program. Best Idea rather than best implementation, which I think is the correct read: the aggregation concept and the child card controls were the strongest part, and the implementation was a competent demo of them.

More usefully to me, it is the project where I learned that internationalisation is an architectural decision. Every bilingual thing I have built since starts directionally, because I already know what it costs to add it later.