06 - Stage Walkthrough
Let's trace Stage 1 Registration end-to-end. By following this one stage carefully, you'll understand how every other stage in the app is wired.
User taps "Proceed" on the registration form → backend creates a lead → app navigates to Stage 2 (OTP). We'll show the frontend code + backend handler in parallel for each step.
Step 1 - App Launches, Lands on Stage 0
main.dart initializes storage and runs EkycApp. The root route / maps to Stage0ArrivalScreen.
flutter_app/lib/main.dartDart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageService.getInstance(); // Singleton init
runApp(const EkycApp());
}
Step 2 - Stage 0 Calls journeyInit and Navigates to Stage 1
Stage0ArrivalScreen has no UI input - it shows a Lottie loader, calls the backend to create a session, then forwards to Stage 1.
flutter_app/lib/screens/stage0_arrival.dartDart_initJourney
Future<void> _initJourney() async {
final storage = await StorageService.getInstance();
final api = ApiService(storage: storage);
// Check for resume first (existing lead)
if (storage.hasActiveLead) { ... resume ... }
// Fresh journey - create session
final result = await api.journeyInit(deviceType: 'DESKTOP');
if (result.success) {
Navigator.pushReplacementNamed(context, AppRoutes.stage1Registration);
}
}
- HTTP:
GET /api/v1/journey/init - Controller:
backend/src/MO.Ekyc.Api/Controllers/Common/ArrivalController.cs - Service:
backend/src/MO.Ekyc.Infrastructure/Services/Common/ArrivalService.cs - Backend docs (pending) - placeholder
Step 3 - Stage 1 Screen Builds Its UI
The registration screen shows the RISE logo, a hero illustration, mobile + name fields, a T&C checkbox, and a Proceed button. Form validation is local (regex checks).
flutter_app/lib/screens/stage1_registration.dartDart_isFormValid getter
bool get _isFormValid {
final mobile = _mobileController.text.trim();
final name = _nameController.text.trim();
return RegExp(r'^[6-9]\d{9}$').hasMatch(mobile) &&
name.length >= 2 &&
_acceptedTerms;
}
The Proceed button is disabled until _isFormValid is true. Typing triggers setState(() {}) to re-evaluate.
Step 4 - User Taps Proceed → _onProceed()
The button's onPressed handler calls _onProceed(), which validates the form, sets loading state, then calls the API.
flutter_app/lib/screens/stage1_registration.dartDart_onProceed
Future<void> _onProceed() async {
if (!_formKey.currentState!.validate() || !_acceptedTerms) return;
setState(() => _isLoading = true);
try {
final storage = await StorageService.getInstance();
final api = ApiService(storage: storage);
final mobile = _mobileController.text.trim();
final name = _nameController.text.trim();
final result = await api.registrationSignup(
mobileNumber: mobile,
fullName: name,
consentAccountOpening: true,
consentCommunication: true,
consentTerms: _acceptedTerms,
);
if (!mounted) return;
if (result.success) {
Navigator.pushReplacementNamed(
context,
AppRoutes.stage2Otp,
arguments: {'mobileNumber': mobile},
);
} else {
ErrorDialog.show(context, message: result.displayMessage);
}
} catch (e) {
ErrorDialog.show(context, message: 'Something went wrong.');
} finally {
if (mounted) setState(() => _isLoading = false);
}
}
Step 5 - ApiService.registrationSignup Delegates to _post
The ApiService method is a thin wrapper that delegates to the generic _post<T> helper. It knows the URL and the fromJson factory for the response type.
flutter_app/lib/services/api_service.dartDartregistrationSignup
Future<ApiResponse<Lead>> registrationSignup({
required String mobileNumber,
required String fullName,
required bool consentAccountOpening,
required bool consentCommunication,
required bool consentTerms,
}) async {
final result = await _post<Lead>(
ApiConfig.registrationSignup,
{
'mobileNumber': mobileNumber,
'fullName': fullName,
'sessionId': _storage.sessionId,
'consentAccountOpening': consentAccountOpening,
'consentCommunication': consentCommunication,
'consentTerms': consentTerms,
},
fromJson: Lead.fromJson,
);
if (result.success && result.data != null) {
await _storage.setLeadId(result.data!.leadId);
await _storage.setCurrentStage(1);
}
return result;
}
Step 6 - _post Injects Headers and Calls http.post
The generic helper adds JWT auth, leadId injection, session tracking, timeout, error handling.
flutter_app/lib/services/api_service.dartDart_post
Future<ApiResponse<T>> _post<T>(
String url,
Map<String, dynamic> body, {
required T Function(Map<String, dynamic>) fromJson,
}) async {
try {
final response = await http.post(
Uri.parse(url),
headers: _headers,
body: jsonEncode(body),
).timeout(ApiConfig.apiTimeout);
await _storage.updateActivity();
final json = jsonDecode(response.body) as Map<String, dynamic>;
if (response.statusCode == 200 && json['success'] == true) {
return ApiResponse.ok(fromJson(json['data']));
}
return ApiResponse.fail(json['message'], json['errorCode']);
} on SocketException {
return ApiResponse.fail('No internet connection');
} catch (e) {
return ApiResponse.fail('Something went wrong.');
}
}
Step 7 - HTTP Request Hits the Backend
The request reaches POST /api/v1/registration/signup on the backend.
- HTTP:
POST /api/v1/registration/signup - Controller:
backend/src/MO.Ekyc.Api/Controllers/Stage1/RegistrationController.cs:22 - Service:
backend/src/MO.Ekyc.Infrastructure/Services/Stage1/RegistrationService.cs - Writes:
leadstable, triggers OTP send (real SMS or fixed 1234 in mock mode) - Backend docs (pending) - placeholder
Step 8 - Backend Responds with ApiResponse<Lead>
JSONExample backend response
{
"success": true,
"message": "Lead created successfully",
"data": {
"leadId": "8f2a1c9e-...",
"mobileNumber": "9876543210",
"fullName": "John Doe",
"sessionId": "...",
"state": "INITIATED"
},
"journeyStatus": { "currentStage": 1, "progressPercent": 6 }
}
Step 9 - Lead.fromJson Turns JSON into Dart Object
flutter_app/lib/models/lead.dartDart
class Lead {
final String leadId;
final String? mobileNumber;
final String? fullName;
final String? state;
Lead({required this.leadId, this.mobileNumber, this.fullName, this.state});
factory Lead.fromJson(Map<String, dynamic> json) => Lead(
leadId: json['leadId'] as String,
mobileNumber: json['mobileNumber'] as String?,
fullName: json['fullName'] as String?,
state: json['state'] as String?,
);
}
Step 10 - ApiService Saves leadId to Storage
Back in registrationSignup, after the _post returns successfully, the method writes leadId to persistent storage so the whole app can reference it.
Look at Step 5's code again - after the _post call:
if (result.success && result.data != null) {
await _storage.setLeadId(result.data!.leadId);
await _storage.setCurrentStage(1);
}
Step 11 - Screen Calls setState and Navigates
Back in _onProceed, the result.success branch runs:
Navigator.pushReplacementNamed(
context,
AppRoutes.stage2Otp,
arguments: {'mobileNumber': mobile},
);
pushReplacementNamed replaces the current route, so pressing back on Stage 2 won't return to Stage 1 - it's a one-way journey.
Step 12 - Stage 2 Screen Mounts
AppRoutes.stage2Otp maps to Stage2OtpScreen in routes.dart. The MaterialApp router mounts it and its initState runs, starting the OTP flow.
flutter_app/lib/config/routes.dartDart
class AppRoutes {
static const stage0Arrival = '/';
static const stage1Registration = '/registration';
static const stage2Otp = '/otp';
// ...
static Map<int, String> stageRoutes = {
0: stage0Arrival,
1: stage1Registration,
2: stage2Otp,
// ...
};
}
Summary Diagram
For a visual sequence diagram of this exact flow, open frontend_architecture.html — Appendix: Functional Flow Sequences and expand the "Stage 1 Registration Flow" collapsible. It shows the same 12 steps as a UML-style sequence diagram.
Apply This to Any Stage
Every stage follows the same basic pattern:
- Screen builds UI with local state (loading, form fields)
- User interacts
- Screen validates locally
- Screen calls an
ApiServicemethod - ApiService calls
_post/_getwith URL + fromJson - HTTP hits the matching backend controller
- Response parsed into
ApiResponse<T> - ApiService may write to storage (setLeadId, setCurrentStage)
- Screen checks
result.successand navigates or shows an error
Open any of the 16 stage files in lib/screens/ and you will recognize this structure.