11 - How-To Guides
Cookbook-style recipes for common development tasks. Each recipe links to related pages for context.
How to Add a New Stage Screen
Suppose you need to add a new "Video KYC" stage between Stage 7 (Liveness) and Stage 8 (Signature). Here's the process.
- Create the screen file
lib/screens/stage7b_video_kyc.dart - Add a route constant in
lib/config/routes.dart:static const stage7bVideoKyc = '/video-kyc'; - Register the route in
lib/app.dartinside theroutesmap - Navigate from Stage 7: replace
pushReplacementNamed(stage8Signature)withpushReplacementNamed(stage7bVideoKyc) - Navigate to Stage 8 at the end of your new screen's proceed handler
- Update JourneyProgressBar stage mapping if needed (for display name)
lib/screens/stage7b_video_kyc.dartDart
import 'package:flutter/material.dart';
import '../config/routes.dart';
import '../services/api_service.dart';
import '../services/storage_service.dart';
import '../widgets/back_button_handler.dart';
import '../widgets/session_timer.dart';
import '../widgets/loading_overlay.dart';
import '../widgets/progress_bar.dart';
class Stage7bVideoKycScreen extends StatefulWidget {
const Stage7bVideoKycScreen({super.key});
@override
State<Stage7bVideoKycScreen> createState() => _Stage7bVideoKycState();
}
class _Stage7bVideoKycState extends State<Stage7bVideoKycScreen> {
bool _isLoading = false;
Future<void> _onProceed() async {
setState(() => _isLoading = true);
final storage = await StorageService.getInstance();
final api = ApiService(storage: storage);
final result = await api.submitVideoKyc(...);
if (!mounted) return;
setState(() => _isLoading = false);
if (result.success) {
Navigator.pushReplacementNamed(context, AppRoutes.stage8Signature);
}
}
@override
Widget build(BuildContext context) {
return BackButtonHandler(
child: SessionTimer(
child: Scaffold(
appBar: AppBar(title: const Text('Video KYC')),
body: LoadingOverlay(
isLoading: _isLoading,
child: Column(
children: [
const JourneyProgressBar(currentStage: 7),
const Expanded(child: Center(child: Text('Video KYC screen'))),
ElevatedButton(onPressed: _onProceed, child: const Text('Proceed')),
],
),
),
),
),
);
}
}
See 06 - Stage Walkthrough for the full anatomy of a stage screen.
How to Add a New API Method
Already covered in 07 - ApiService. Four steps:
- Add URL getter to
ApiConfig - Create a model with
fromJson(if new shape) - Add method to
ApiServicecalling_post/_get - Call from a screen
How to Add a New Domain Model
Models are plain Dart classes with a fromJson factory.
lib/models/video_kyc_result.dartDart
class VideoKycResult {
final bool verified;
final String? sessionId;
final String? videoUrl;
VideoKycResult({
required this.verified,
this.sessionId,
this.videoUrl,
});
factory VideoKycResult.fromJson(Map<String, dynamic> json) {
return VideoKycResult(
verified: json['verified'] as bool? ?? false,
sessionId: json['sessionId'] as String?,
videoUrl: json['videoUrl'] as String?,
);
}
}
Keep models immutable (final fields, no setters). No methods beyond the factory and optional toJson.
How to Add a New Wrapper Widget
Wrapper widgets take a child and render something around it.
lib/widgets/debug_banner.dartDart
import 'package:flutter/material.dart';
import '../config/api_config.dart';
/// Shows a yellow "DEV BUILD" banner at the top of the screen in dev mode.
class DebugBanner extends StatelessWidget {
final Widget child;
const DebugBanner({super.key, required this.child});
@override
Widget build(BuildContext context) {
if (!ApiConfig.isDev) return child;
return Column(
children: [
Container(
width: double.infinity,
color: Colors.amber,
padding: const EdgeInsets.symmetric(vertical: 4),
child: const Text(
'DEV BUILD - Mock Mode Active',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold),
),
),
Expanded(child: child),
],
);
}
}
Use it by wrapping your Scaffold body:
DartUsage
body: DebugBanner(
child: Column(children: [...]),
)
How to Add a New Environment
Suppose you want to add a "staging" environment that points to a staging server.
- Edit
lib/config/api_config.dart- add acaseto thebaseUrlgetter - Optional: add an
isStaginggetter - Build with
--dart-define=ENV=staging
lib/config/api_config.dartDart
static String get baseUrl {
switch (_env) {
case 'prod': return 'https://ekyc.motilaloswal.com';
case 'uat': return 'https://ekycuat.motilaloswaluat.com';
case 'staging': return 'https://ekycstaging.motilaloswaluat.com';
default: return 'http://localhost:5000';
}
}
static bool get isStaging => _env == 'staging';
How to Add a New Asset (Lottie / SVG / PNG)
- Copy the file to
flutter_app/assets/images/ - Add a constant to
lib/config/assets.dart:
lib/config/assets.dartDart
class AppAssets {
static const _base = 'assets/images';
// Existing assets...
static const videoKycIcon = '$_base/ic_video_kyc.svg'; // <-- add this
}
- Run
flutter pub get(theassets/images/folder is already registered, so new files are picked up automatically) - Use it in a screen:
DartUsage
SvgPicture.asset(AppAssets.videoKycIcon, width: 64),
// or for Lottie:
Lottie.asset(AppAssets.myAnimation),
// or for PNG:
Image.asset(AppAssets.myIcon),
How to Add a Navigation Route
Routes are registered in lib/app.dart in the MaterialApp.routes map.
lib/app.dartDart
MaterialApp(
routes: {
AppRoutes.stage0Arrival: (_) => const Stage0ArrivalScreen(),
AppRoutes.stage1Registration:(_) => const Stage1RegistrationScreen(),
AppRoutes.stage2Otp: (_) => const Stage2OtpScreen(),
// ... add your new route here
AppRoutes.stage7bVideoKyc: (_) => const Stage7bVideoKycScreen(),
},
initialRoute: AppRoutes.stage0Arrival,
)
How to Trigger a Build for a Specific Platform
See 09 - Config & Environments → Build Commands for the full command reference.
How to Run Tests
The project has a test/ folder but most tests are not yet written. Running the existing ones:
BashRun tests
cd flutter_app
C:\flutter\bin\flutter test
C:\flutter\bin\flutter test --coverage