Engineering 'Maths Kids': Interactive Column Arithmetic, Step-by-Step Division, and Responsive UX in Flutter
A deep dive into building an offline math practice app with custom column arithmetic engines, live keystroke validation, borrow/carry tracking, and step-by-step long division.
Most commercial math apps for young students rely on multiple-choice quizzes (MCQs). While MCQs are trivial to program, they fail to teach actual pencil-and-paper computation mechanics: aligning columns, carrying over values, borrowing across zero, and step-by-step long division algorithms.
Maths Kids was designed to replicate the exact mechanics of physical math workbooks inside a zero-latency, offline Flutter application.
Here is an architectural walkthrough of how the column-arithmetic calculation engines, keystroke validation, and custom input grids were implemented.
1. Core Principles: No Check Buttons, No MCQs
- Instant Keystroke Validation: There is no “Submit” or “Check” button. As soon as a child enters the correct digit in an active cell, the cell locks, turns green, and advances focus automatically.
- Natural Arithmetic Cursor Traversal:
- Addition, Subtraction, Multiplication: Moves Right-to-Left (units $\to$ tens $\to$ hundreds $\to$ thousands), matching natural hand calculations.
- Long Division: Moves Left-to-Right (highest place value dividend $\to$ remainder $\to$ drop down).
- Hold-to-Peek: Children can press and hold a “Peek” button to momentarily view expected digits if they are stuck. Releasing the button immediately hides the hint, encouraging self-correction without penalty.
2. Column Addition & Carry Propagation
Addition supports 4-digit $+$ 4-digit operations with potential 5-digit sums:
[C3] [C2] [C1] [ ] <-- Carry Row (Col 3, 2, 1)
4 8 7 6 <-- Term A
+ 7 4 5 9 <-- Term B
────────────────────────
[1] [2] [3] [3] [5] <-- 5-digit Answer Row
The Carry Row
The carry cells sit directly above the addends. They accept single digits ($0$ or $1$) and remain ungraded helper fields. If the child fills them, they see visual confirmation; if they compute mentally, they can bypass them directly to the answer row.
3. Subtraction: Borrow Cells & Strikethrough Engine
Borrowing in multi-digit subtraction is where most young learners struggle. Standard textbook mechanics require:
- Slashing through a lender digit in column $i+1$.
- Writing the decremented value above it.
- Adding $+10$ to the borrower digit in column $i$.
[ ] [ 7 ] [ 6 ] [16] <-- Borrow Row
8 8 7 6 <-- Minuend (strikethroughs applied dynamically)
- 3 4 8 9 <-- Subtrahend
────────────────────────
[5] [3] [8] [7] <-- Answer Row
The Material 3 Tinting Bug
In Flutter’s Material 3 theme, TextField containers inherit primary seed colors, tinting borrow cells green by default even before validation. We forced clean transparent backgrounds with:
TextField(
controller: _borrowControllers[col],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
decoration: const InputDecoration(
filled: true,
fillColor: Colors.transparent, // Prevents M3 auto-tinting
border: OutlineInputBorder(),
contentPadding: EdgeInsets.zero,
),
onChanged: (val) => _validateBorrow(col, val),
)
4. Multi-Stage Long Multiplication
Long multiplication ($3\text{-digit} \times 2\text{-digit}$) requires multiple distinct computational phases:
- Multiply top number by bottom units digit $\to$ Partial Product 1 ($P_1$).
- Multiply top number by bottom tens digit $\to$ Partial Product 2 ($P_2$, shifted by a fixed units zero).
- Sum $P_1 + P_2$ with carry cells $\to$ Final Product.
Dynamic Auto-Erasing Carry Row
Early prototypes featured two separate multiplication carry rows, which crowded phone screens. Instead, we implemented a single shared multiplication carry row that functions exactly like an eraser on physical paper:
- As the child calculates $P_1$ ($\times\text{units}$), the top row acts as the units carry buffer.
- Once $P_1$ turns fully green, the row is automatically cleared, relabeled to $\times\text{tens}$, and resets for $P_2$:
void _onCellChanged(int col, String val, List<bool> correct) {
// Check if Partial Product 1 just completed
if (identical(correct, _p1Correct) && _p1Done && !_carryClearedForP2) {
_carryClearedForP2 = true;
for (var controller in _multCarryControllers) {
controller.clear();
}
setState(() {
_currentMultPhase = MultiplicationPhase.tensPass;
});
}
}
5. Step-by-Step Long Division Bracket
Rather than reducing division to a simple quotient and remainder input, Maths Kids generates an authentic long division bracket with intermediate product and subtraction steps:
[ 1 ] [ 4 ] [ 2 ] R [ 3 ] <-- Quotient + Remainder
┌───────────────────
6 │ 8 5 5 <-- Divisor | Dividend
- 6 <-- Divisor x 1
──────
2 5 <-- Remainder + Brought Down
- 2 4 <-- Divisor x 4
───────────
1 5 <-- Remainder + Brought Down
- 1 2 <-- Divisor x 2
───────
[ 3 ] <-- Final Remainder
State Machine for Interleaved Working
The input sequence strictly enforces standard classroom order:
- Enter quotient digit.
- Enter subtraction product ($-\text{divisor} \times \text{quotient digit}$).
- Compute intermediate remainder.
- Bring down the next dividend digit.
By modeling the working rows as a structured matrix of fixed controller pools (_WorkRow data class), keyboard navigation moves seamlessly through each operation.
6. Daily Habituation: 12-Hour Reset Cycle
Times tables (from $2\times$ up to $20\times$) are unlocked sequentially. To prevent children from permanently completing the board and abandoning practice, progress resets on a rolling 12-hour epoch:
- If $\Delta t > 12\text{ hours}$ since the child’s last session, unlocked tables reset to Table 2.
- A single daily session allows rapid advancement through previously mastered tables, ensuring active revision of multiplication fundamentals.