SmartInventory Mobile App (Flutter) — Code Analysis
Purpose: Ground-truth map of the SmartInventory Flutter field app (fiber_net), to inform the Lepton Infrastructure Cloud rewrite by establishing exactly what the mobile client needs from the backend/API. Companion to SmartInventory_Legacy_Architecture_Study.md (web/.NET) and docs/database/* (live PG schema).
Subject: references/Flutter-SmartInventory/ @ branch SmartInvetory-main (its own git repo, distinct from the web SmartInventory-3.0). Static, read-only analysis on 2026-06-16. No devices/emulators were run.
Method: Read of the entry/config/network/auth layer directly, plus three parallel read-only code explorations (data models; features/workflows; offline & sync). Findings are cited to real file paths; line numbers are given where load-bearing.
Headline: This is a thin, online-first REST client over the legacy
SmartInventoryServicesOWIN Web API (the same/tokenROPC +/api/*controllers the web app uses), wrapped around Google Maps + a self-hosted MapServer WMS overlay, with a bolted-on offline mode (two parallel SQLite DBs + MBTiles + a blind replay queue). It is single-codebase, multi-flavor per customer (BharatNet/RVNL/HFCL/Syokinet/VGG/Converge/Safaricom/Jio), heavily mirroring the legacy data model field-for-field (system_id / network_id / entity_type / network_status / sp_geometry WKT / dynamic-control EAV). It carries the same security debt as the rest of the product: TLS validation disabled, cleartext HTTP allowed, base64 “encryption” of passwords, plaintext credentials in SharedPreferences, and many committed Google Maps API keys.
1. Overview & stack
- Framework: Flutter, Dart SDK constraint
>=2.12.0 <3.0.0(an old, pre-Dart-3 / EOL toolchain). Package namefiber_net(pubspec.yaml). - HTTP:
http: ^0.13.4is the primary client (plusdio: ^4.0.4used for file downloads). No generated client; all calls hand-rolled. - State management: mixed/inconsistent —
provider,flutter_bloc: ^9.1.0(newer features), and a large number of hand-rolled BLoC classes usingrxdartStreamController/PublishSubject(the dominant pattern inlib/blocs/andlib/ui/**/*_bloc.dart). No DI container. - Maps:
google_maps_flutter(Google basemap + markers/polylines) overlaid with WMS tiles from a self-hosted MapServer (legacymapserv.exe), plusflutter_polyline_points,maps_toolkit,google_maps_utils,fluster(clustering),flutter_cache_manager(tile cache). Places search viagoogle_place/flutter_google_places_hoc081098. - Local storage:
sqflite(two SQLite DBs — see §6),shared_preferences(session/flags, incl. plaintext password — §7),flutter_secure_storage: ^9.2.4(remember-me credentials), file cache + ZIP viaflutter_archive/archive, MBTiles for offline basemaps. - Field-data capture:
image_picker,flutter_image_compress,native_exif(geo-tag photos),flutter_barcode_scanner(a forked git dependency,itsAyyazdev/flutter_barcode_scanner) +qr_code_scanner+ocr_scan_text,painter(customer signature),geolocator/geocoding(GPS),speech_to_text(voice — disabled, code commented out). - Background/ops:
workmanager+flutter_local_notifications+battery_plus(background location),connectivity(online/offline detection),permission_handler,safe_device(root/jailbreak/mock-location detection),local_auth(biometric/pattern lock),package_info_plus,uni_links(deep links → pre-filled login),pin_code_fields+alt_sms_autofill(OTP),encrypt+pointycastle+crypto(AES backup of local DB → ZIP). - Identity: the app builds for one product across many customer flavors, selected by Android
packageNameat runtime (lib/app/config/environment.dart).README.mdlists ~10+ build flavors (fibernetBHARATNETSIT,...RVNL,...HFCLGGN,...HFCLGK2,...TERASOFT, Syokinet, VGG, etc.). Theme also switches onJIO(lib/main.dart:29).
2. Architecture (lib/ map)
875 Dart files. The tree shows two architectural generations layered on top of each other — an older lib/ui + lib/blocs + lib/networks + lib/models stack, and a newer lib/app/features/** + lib/core + lib/data stack. They coexist and interoperate (the newer offline features reuse the older Repository).
| Path | Role |
|---|---|
lib/main.dart | App entry. Inits Environment, sets HttpOverrides.global = MyHttpOverrides() (disables TLS cert validation — see §7), theme by flavor, registers workmanager callbackDispatcher. |
lib/app/config/ | environment.dart (flavor → base URL + Google Maps keys), app_config.dart (the resolved singleton AppConfig.shared). |
lib/networks/ | Old network layer: api_constant.dart (base URL + endpoint path constants), api.dart (ApiProvider — many bespoke post* variants), api_req_res_services.dart, repository.dart (god-object Repository, ~100 model imports, the central API facade), singleton.dart, provider_notifier.dart, ZipSingleton.dart. |
lib/app/services/ | Newer service layer: http_service.dart (HttpService.apiCall), api_services/* (per-feature services: converge_service, customer_mgmt_service, trouble_ticket_service, building_survey_service, home_service, network_manager_service, offline_network_manager_service, otp_service, geo_tagging_image_service, user_dashboard_service), local_database/ (one SQLite DB), cache_storage/, download_file_service, upload_file_service, dynamic_form_service. |
lib/app/features/** | Newer feature modules (screen+bloc+UI): building_survey, converge (CPE activation wizard), customer_mgmt, trouble_ticket, network_manager, offline_network_manager (the bulk of offline code), user_dashboard, geo_tagging_image, otp_verification, Add Network Ticket, home, splash, drawer. |
lib/app/repository/ | Newer response models grouped by feature (converge, customer_management, offline_network_manager, home_api_models, otp, trouble_ticket, building_survey, user_dashboard). |
lib/ui/** | Older screen layer: map_screen (incl. Splicing View, logical_view, search, voice_recognition, more/export_Report), entity (per-type forms: FMS, building, cabinet, coupler, fault, handhole, hoto, pop), isp_device, isp_view (building→floor→shaft→room), dynamic_attribute, login_&_language, shared_components. |
lib/blocs/ | Older hand-rolled BLoCs (login_bloc, map_bloc, map_entity_bloc, per-entity *_form_bloc/*_template_bloc, network_ticket_block). |
lib/models/ | Older request/response DTOs (request/, response/, response/entity_list_models/, response/geocode/). |
lib/core/database/ | Newer SQLite stack #2: dbhelper.dart, appdatabaseinfo.dart (SMART_INVENTORY_LOCAL.db, v3), migrations/migration_v1..v4, table_schema/* (~26 table defs). |
lib/data/datasource/local/ | DAOs + local models for the lib/core/database stack (dao/*, models/*). |
lib/utils/, lib/app/utils/ | common_utils/common_methods (incl. WMS URL builder, barcode scan), app_constants/app_constant (more endpoint constants), shared_preferences_utils, aes_encrypt_decrypt, app_secure (secure storage), location_tracking/, Isolates/. |
lib/background_services/, lib/wms/, lib/logger/ | Background tasks, WMS helpers, logging. |
Observation for the rewrite: the two-stack duplication (two SQLite DBs, two network layers, two model conventions) is the mobile-side analogue of the web app’s god-objects/duplication — a sign of accreted, unrefactored growth. A new client should standardize on one networking layer, one local store, one model convention, and a generated/typed API client.
3. API surface & auth
3.1 Base URL & flavoring
There is no single base URL — it is resolved at runtime from the Android package name (lib/app/config/environment.dart), e.g.:
- BharatNet SIT →
http://networkaccess.st.leptonsoftware.com:7032; UAT →:7779; product →…/product_services. - BharatNet customer sub-deployments →
http://networkaccess.bnet.leptonsoftware.com/<CUSTOMER>/Smartinventory_services(RVNL, RVNL_UPW, NCC_MP, HFCL_Punjab, NCC_Uttarakhand). - Syokinet (Kenya) →
https://foc.syokinet.co.ke/smartinventory_services[_UAT]; VGG →http://102.209.57.10:8092/; TeraSoft →https://networkaccess.leptonsoftware.com/Bharatnet_Terasoft/Smartinventory_service. lib/networks/api_constant.dartadditionally carries ~40 commented-out base URLs for Jio, Safaricom, VI/Vodafone-Idea, Converge, Axtel, NHAI, Ezecom, SITI, MADA, NT (Nepal), SpaceWorld, DEPL, NoorCom — a dead-but-revealing customer inventory. Note many are plainhttp://with raw IPs and nonstandard ports.
All endpoint paths are appended to this base. The mobile API is the legacy SmartInventoryServices OWIN Web API (/api/<Controller>/<Action> + /token), plus a separate /wfm/mobile/v1.0/* family (the legacy FEController, used by Converge/WFM/trouble-ticket).
3.2 Auth flow (ROPC bearer, no OIDC, no automatic refresh)
- Login (
lib/ui/login_&_language/login_screen.dart:967callLoginAPI): builds aLogInRequest(lib/models/request/logIn_request.dart) withgrant_type='password'(OAuth2 Resource-Owner-Password-Credentials),username/passwordbase64-encoded (CommonUtils.stringToBase64— not encryption, just encoding),macAddress(device id),appVersion,osType/osName,Source='Mobile',forceLogin(to bump an existing session). It is sent form-style (Uri(queryParameters:…)with?stripped) toPOST {base}/token. - Token response (
lib/models/response/login_response.dart):access_token,token_type(“Bearer”),expires_in,refresh_token, plususerId,userName,email,userRoleId/userRole,loginHistoryId,IsMasterLogin,.issued/.expires, andGlobalSettings(a JSON array of key/value server config incl.apkPathfor OTA APK update andappVersionfor forced-upgrade gating). - 2FA / OTP (post-login,
lib/app/features/otp_verification/otp_verification_bloc.dart): if enabled, app callsPOST /api/OTP/GetOTPthenPOST /api/OTP/VerifyOTP({source, user_id, user_name, OTP}). Only here is the refresh-token grant used:refreshAccessToken()postsgrant_type=refresh_token+refresh_tokento/tokenand swaps in a new access token (:639-660). There is no general-purpose silent refresh elsewhere — on 401 the app surfaces an error/relogin (http_service.dart:61,api.dart:434). - Token storage:
access_token/token_typeare persisted in SharedPreferences (SharedPreferencesUtil, keysaccess_token/token_type) and read on every request. Remember-me credentials go to FlutterSecureStorage viaAppSecure(lib/app/utils/app_secure.dart) — but the raw password is also written to SharedPreferences in plaintext (login_screen.dart:1071, see §7). - Auth header on requests:
Authorization: Bearer <token>+ custom headersSource: mobile, and for entity opsEntity_Type,Entity_Action,is_new_entity,source_ref_type=Network_Ticket/source_ref_id=<ticketId>/source_ref_description(so saves are attributable to a work ticket;lib/networks/api.dartentityPost*,lib/app/services/http_service.dart). Logout:POST /api/user/UserLogout.
3.3 Endpoint inventory
134 distinct /api/* paths are referenced, plus the /token and /wfm/mobile/v1.0/* families. Defined mainly in lib/networks/api_constant.dart and lib/app/utils/app_constants.dart. Representative inventory (method is POST for almost everything; a few are GET):
| Area | Endpoint (path) | Purpose / backend correspondence |
|---|---|---|
| Auth | POST /token | ROPC login + refresh (SmartInventoryServices OWIN OAuthProvider, opaque MachineKey bearer) |
| Auth | POST /api/OTP/GetOTP, /api/OTP/VerifyOTP | 2FA (OTP controller; RIL4G/SMS gateways server-side) |
| Auth | POST /api/user/UserLogout | end session / login_history |
| Bootstrap | POST /api/main/GetUserModule | per-user feature modules (module_abbr=NWT/WFM/SFS… + is_offline_enabled) — drives the home grid |
| Bootstrap | POST /api/main/GetNetworkLayers | the mobile layer registry = layer_details filtered to mobile-visible layers (the is_mobile_layer/is_visible_in_mobile_lib/is_visible_on_mobile_map flags) |
| Bootstrap | POST /api/Resources/GetLanguageResources | i18n (res_resources) |
| Map data | POST /api/VectorLayer/GetVectorDataByGeom, GetVectorDeltaByGeom, GetVectorProvinceData, GetVectorEntityStyle | GeoJSON vector features + styling for the map (newer vector path alongside the WMS image path) |
| Map data | WMS GET <mapServerURL>?…&SERVICE=WMS&LAYERFILTER=[network_status] in (…)… | raster overlay of vw_att_*_map views, filtered by network_status/block_code/source_ref_id (common_utils.dart:656 wmsEntityLayerUrl) |
| Nearby | POST /api/main/getnearbyentities, /api/Main/GetNearByCables, /GetNearByDucts, /GetNearbyAssociatedEntity, /api/main/GetNearByLandbaseEntities | proximity queries around GPS (snap-to-nearest, parent selection) |
| Entity read | POST /api/main/getentityinfo, /GetEntityActions, /GetEntityAdvanceAttribute, /getGeometryDetail, /getGeometryForEdit, /GetEntityLogicalView | feature detail, allowed actions, dynamic attributes, geometry-for-edit |
| Entity write | POST /api/Library/EntityOperations | the core create/update/delete for network entities (LibraryController server-side); headers carry Entity_Type/Entity_Action/is_new_entity |
| Entity write | POST /api/ItemTemplate/EntityTemplate | get/save the per-entity template/spec (vendor/brand/model/ports → item_template_*) |
| Entity write | POST /api/main/SaveEditGeometry, /GetGeometryForEdit; /api/main/SaveLandbaseEditGeometry, /GetLandbaseGeometryForEdit | move/reshape geometry (WKT) |
| Entity write | POST /api/main/ValidateEntityGeom, /api/Main/ValidateEntityForConversion | server-side geom + as-built conversion validation |
| Entity write | POST /api/Library/MergeCables, /Library/SplitCable | cable split/merge (fiber/tube/core re-association) |
| Entity write | POST /api/Main/DeleteEntity, /Main/DeleteEntityFromInfo, /Main/RevertEntityChanges | delete / revert (approval-workflow) |
| Codification | POST /api/main/NetworkStage, /GetRegionProvince, /GetRegionProvinceBasedOnLocation, /Report/BindProviceByRegionId | region/province/network_id hierarchy selection |
| Barcode | POST /api/main/UpdateEntityBarCode | attach scanned barcode/QR to entity (is_barcode_enabled) |
| Attachments | POST /api/main/UploadAttachment, /GetEntityImages, /GetEntityDocuments, /DownloadAttachment, /DeleteAttachment | entity photos/docs |
| Geo-tag images | POST /api/main/UploadGeoTaggedImages, /GetAllGeoTaggedImages, /DownloadGeoTaggedAttachment, /DeleteGeoTaggedImage | EXIF-geotagged field photos |
| Splicing / connectivity | POST /api/Connection/Splicing, /ODFToCable, /CableToCable, /SaveBulkConnection, /GetSplicingReports | write splices → connection_info |
| Splicing / connectivity | POST /api/main/GetConnectionInfo, /GetCPFelementPath, /GetEquipmentPortInfo, /GetEquipmentSearchResult, /TerminationEntity | upstream/downstream trace, connection path finder, port/core lookup (fn_get_schematic_view, fn_*get_connection_info) |
| ISP (in-building) | POST /api/ISP/getISPMasters, /GetDeviceDetails, /SaveISPDevices; /api/main/getStructureInfo, /getBuildingDetail, /getBuildingGeom | building→floor→shaft→room + indoor device/port mgmt (isp_* tables) |
| HOTO | POST /api/NetworkEntityAPI/GetOpticalHotoDetails, /SaveOpticalHotoDetails; /api//Offline/GetTubeFiberColor | handover-takeover: OTDR/LSPM tube/fiber readings (offline-capable) |
| Customer mgmt | POST /api/main/getCustomerInfo, /saveCustomerInfo, /savecustomerassociation, /getDistributionBoxEntityInfo, /getDistributionBoxInfo, /GetNearByDBInfo[_byticket], /getSplitterPortInfo, /getMobileLegendInfo, /GetWCRMaterial, /getontvendors | last-mile customer connect: pick distribution box/splitter, assign port, WCR material |
| Network tickets | POST /api/NetworkTicket/GetAllNetworkTicket, /CreateNetworkTicket, /GetNetworkTicketEntityList, /getNetworkTicketGeometry | as-built work tickets that scope edits |
| Survey | POST /api/surveyarea/surveyareainfo, /api/building/buildinginfo, /getBuildingDetailsById, /insertbuilding, /updatebuildinginfo, /updatebuildinggeometry, /checknearbybuilding, /getbuildingComments | building/home-pass survey |
| WFM / Converge | POST /wfm/mobile/v1.0/{getJobList, getTicketStepsDetail, getCustomerDetail, UpdateStatus, UpdateJobOrder, getCPEDetail, fetchCpeDetail, searchserialnumber, activatecpe, getadditionalmaterialmaster, saveadditionalmaterial, getadditionalmaterial, getjobstatustype, sendtoerp, GetStatusDetailByJobOrderId, getRescheduleSlots, UploadAttachment, DeleteAttachment, GetImageDocumentByJobId, GetImageDocumentByID} | field workforce job execution + CPE activation + ERP push (legacy FEController + Converge SOAP) |
| Trouble ticket | POST /wfm/mobile/v1.0/{getTTJobList, getTTTicketStepsDetail, getttrcrca, getttrcrcabyjobid, updatettstatus} | trouble-ticket execution (RC/RCA) |
| Reports/export | POST /api/Report/{GetExportReportDropdowns, BindProviceByRegionId, GetPlanningByProjectids, GetWokorderByPlanningids, GetPurposeByWorkOrderids, getEntityExportList, GetExportReportSummary, GetReportUsersByParentUser} | async export (Excel) |
| Offline bootstrap | POST /api/Offline/{GetProvinceDetail, GetAllVendors, GetAllVendorSpecifications, GetAllDropdownData, GetAllLayerMapping, GetEntityActions, getLayerColumnsSettingsDetail, getLegendDetail, GetTubeFiberColor, GetOfflineAllNetworkTicket, GetNetworkTicketEntityList, GetLandbaseMaster, GetNearbyLandbaseDetailsWithAtributes, GetAdditionalAttribute*} | bulk master/feature pulls to seed local DB |
| Offline map | GET /LegendDetails/DownloadMapTiles | download MBTiles basemap for a region |
| Tracking | POST /api/User/SaveUserLocation, /api/main/UpdateLocation | background GPS upload |
| Misc | POST /api/main/GetDropDownItems, /GetItemVendors, /GetItemCategoryDetail, /GetThirdPartyVendor, /GetIcons; GET /api/mapsapi/reversegeocode, /api/place/autocomplete/json; /api/crm/getStaticPageDetails (about page) | dropdowns, icon zip, geocode/places proxy, static pages |
Request/response shape: request bodies are JSON, very often a single field {"data": "<stringified-JSON>"} (the server expects a JSON-in-a-string envelope, mirroring the web app’s proc params). Responses are JSON with a top-level status (“OK”/error), results (the payload), and error_message; the client treats status != "OK" as an error even on HTTP 200 (http_service.dart:82).
4. Data models (mobile ↔ backend mapping)
The mobile DTOs mirror the legacy/PG model closely, confirming the masters + att_details + layer_details + EAV triad.
| Mobile model (file) | Key fields | Backend concept |
|---|---|---|
FeatureProperties + Geometry (lib/models/response/get_vector_data_response.dart) | systemId, networkId, entityType, networkStatus (A/D/P enum), displayName, createdBy; Geometry{type: POINT/LINESTRING/POLYGON/MULTIPOLYGON, coordinates}, centerLineGeom | point/line/polygon_master rows (GeoJSON form for the vector map path) |
Results (lib/models/response/point_response_model.dart) | entityId(=system_id), entityType, latitude/longitude (string), spGeometry (WKT, SRID4326), spCentroid, centerLineGeom, approvalFlag, approverId, approvalDate | master row incl. approval/audit envelope |
SaveEditGeometryRequest + TpDetail (lib/models/request/save_edit_geometry_request.dart) | systemId, entityType, geomType, longLat, txtGeom (WKT), networkStatus, userId, sourceRefType/Id; TpDetail{entityType, mode, networkId, systemId} (termination points) | geometry update payload — geometry shipped as WKT/longLat strings, not typed geometry |
ObjDynamicControls / LstFormControl (lib/models/response/obj_dynamic_controls.dart) | lstFormControls[]: fieldName, fieldLabel, controlType, isMandatory, isVisible, defaultValue, min/maxLength, otherInfo (JSON) | the dynamic-control / entity_additional_attributes EAV — custom fields rendered as a server-driven form |
LstOfflineSpecification (lib/data/datasource/local/models/lst_offline_specification.dart) | layerId, categoryReference, specification, vendorId, noOfTubes, noOfCoresPerTube | item_template_* (vendor/brand/model/port specs); cable strand counts |
MapEntityBottomSheetSelectionResult (lib/models/response/select_cable_res_model.dart) | cableSystemId, networkId, cableType, cableCores, totalLoopLength/Count, availableCableLength | att_details_cable_info strand inventory + capacity |
LstEquipementPort (lib/models/response/connection_path_search.dart) | portText, portValue, endpoint | connection_info / isp_port_info directed port endpoints |
NetworkTicketResponse / LstNWStatus (lib/models/response/network_ticket_response.dart) | lstNWStatus[] (status→count), lstNWDetails[] (Map<String,dynamic>) | ticket_master / att_details_networktickets aggregation |
LstUserModule (lib/app/repository/home_api_models/user_module_response.dart) | moduleAbbr (=NWT/WFM/SFS…), moduleName, isOfflineEnabled, formUrl, lstSubModule[] | per-user module/feature gate (web’s 90+ module codes) |
GetNetworkLayerModel (lib/data/datasource/local/models/get_network_layer_model.dart) | stores the whole GetNetworkLayers response as a raw JSON data blob + is_downloaded/is_deleted | layer_details mobile-visible subset, cached for offline |
EntitiesModel (lib/data/datasource/local/models/entities_model.dart) | systemId, entityData (full JSON blob), isDownloaded/isUploaded/isDeleted | offline cache of a feature + its sync flags |
Dynamic-attribute handling: the app does not hard-type custom attributes — it renders a server-driven form from LstFormControl (control type/label/validation) and round-trips values; offline equivalents in OfflineAdditionalAttributesModel. Wide/variable payloads (ticket details, geometry metadata, cached entities) are carried as Map<String,dynamic> / stringified JSON — pragmatic for SQLite caching but loosely typed.
Geometry handling: three representations coexist — WKT strings (spGeometry/txtGeom, the write path), GeoJSON (Geometry.coordinates, the vector-read path), and lat/lng strings (point centers). All SRID 4326. This matches the web app’s “ship WKT, let PostGIS compute” pattern — the client never does real spatial math server-authoritatively.
5. Features & field workflows
Field users are guided through map-centric, ticket-scoped workflows. Screens → workflows:
- Building survey / home-pass (
lib/app/features/building_survey/**): pick a survey area → building list/map → record structure details and home/business-pass counts. (survey/building_survey_screen.dart,building_list/…,survey_map_view/…,near_by_entity/….) - GIS entity create/edit on map (
lib/ui/entity/**,lib/ui/map_screen_entity.dart,lib/blocs/*_form_bloc.dart): tap/search a location → choose entity type → fill the template form (EntityTemplate) + dynamic attributes → capture point/line/polygon geometry from GPS or map →EntityOperationssave. Editing geometry/location viagetGeometryForEdit→SaveEditGeometry. Per-type forms exist for pole, cabinet, coupler, POP, FMS, manhole, duct, cable, handhole, building, ADB/BDB/CDB/FDB, splice closure, splitter, ONT, trench, slack, loop. - Barcode/QR scanning (
common_utils.dartFlutterBarcodeScanner.scanBarcode,entities_info.dart:~1563): for barcode-enabled layers (is_barcode_enabled), as-built validation requires a scan →UpdateEntityBarCode. QR widget indialog_box_with_image.dart; OCR viaocr_scan_text. - Splicing / fiber connectivity (
lib/ui/map_screen/Splicing View/**,lib/ui/isp_device/**,lib/ui/map_screen/more/): create splices (ODF↔cable, cable↔cable, bulk) →Connection/*; trace upstream/downstream and run the connection path finder (GetConnectionInfo,GetCPFelementPath); pick ports/cores (GetEquipmentPortInfo). - In-building (ISP) view (
lib/ui/isp_view/**,lib/ui/isp_device/**): navigate building → floor → shaft → room and manage indoor devices/ports (getStructureInfo,ISP/*). - Customer management / last-mile connect (
lib/app/features/customer_mgmt/**): from a customer ticket → building detail → choose distribution box (FDB/ODF) → splitter/port assignment → WCR material →saveCustomerInfo/savecustomerassociation. - WFM / Converge CPE-activation wizard (
lib/app/features/converge/**,lib/app/services/api_services/converge_service.dart): job list → customer info → CPE serial search → activate CPE → additional parts → connection testing → customer signature → upload documents → review order → send to ERP (the/wfm/mobile/v1.0/*family). This is the most elaborate multi-step flow and is Converge/Jio-style provisioning. - Trouble-ticket execution (
lib/app/features/trouble_ticket/**): TT list → ticket steps → capture issue (photos) → RC/RCA → status update (getTT*,updatettstatus). - Network tickets (“Add Network Ticket”,
lib/app/features/Add Network Ticket/**): create/scope as-built work tickets that all entity edits are attributed to (source_ref_*headers). - Geo-tagged photo capture (
lib/app/features/geo_tagging_image/**,native_exif): camera → EXIF GPS stamp →UploadGeoTaggedImages. - GPS / background tracking (
lib/utils/location_tracking/**,workmanager): periodic background location →SaveUserLocation/UpdateLocation; root/mock-location guard (safe_device). - Reports / export (
lib/ui/map_screen/more/export_Report/**): filter (region/province/planning/work-order/purpose) → server-side Excel export. - Search & voice (
lib/ui/map_screen/search/**,voice_recognition/**): entity/place search; voice-search is implemented but commented out/disabled. - Feasibility: not a real workflow in this client — only stray identifier hits; field service-qualification (SmartSQ) is not a mobile feature here.
Customer-specificity is handled by flavor + module gating + server config, not by separate apps (Converge folder is the clearest customer-shaped feature set; theming switches on Jio).
6. Offline & sync
Offline support is substantial but bolted-on and blind-replay, with two SQLite databases:
- DB #1 —
fiber_net.db(lib/app/services/local_database/database_service.dart): version = number of migration scripts (DatabaseVersion.migrationscurrently length 1). Holds master/lookup tables (lib/app/services/local_database/tables/):user_module_tbl,layer_mapping_tbl,layer_columns_settings_tbl,all_vendors_tbl,vendor_specification_tbl,drop_down_data_tbl,entity_action_tbl,legend_details_tbl,near_by_entity_tbl,near_by_landbase_tbl,province_details_tbl,create_elements_tbl(the offline change ledger),lb_layer_*. - DB #2 —
SMART_INVENTORY_LOCAL.db(lib/core/database/appdatabaseinfo.dart, v3, migrationsmigration_v1..v4): the newer DAO-backed store (lib/core/database/table_schema/*, ~26 tables) —EntitiesTable,EntitySaveRequestTable,FileTable,NetworkTicketsTable,GetNetworkLayersTable,IspDeviceTable/IspDeviceMasterTable,AllVendor*,AllDropdown,AllLayerMapping,Province,RegionProvinceBlock,Legend,GetOpticalHotoDetails,SaveOpticalHotoCableDetails,SaveOpticalHotoTubeOtdrLspm,GetTubeFiberColor,AdditionalAttributes[Landbase],LandbaseMasterDropdown,LandbaseSaveRequest. The two DBs overlap (vendors, dropdowns, layer mapping, legends) — duplicated stores. - Offline basemap: MBTiles SQLite (
lib/data/datasource/local/dao/mbtiles_dao.dart, standardtiles(zoom_level, tile_column, tile_row, tile_data)), downloaded viaGET /LegendDetails/DownloadMapTilesand served togoogle_maps_flutteras a tile overlay.
Download-for-offline (lib/app/features/offline_network_manager/offline_feature/offline_bloc.dart + download_network/bloc/download_network_bloc.dart): for a chosen province/region the app bulk-pulls and batch-inserts into the local DB: nearby entities + attributes (GetNearbyEntityDetailsWithAtributes), landbase (GetNearbyLandbaseDetailsWithAtributes), layer column settings, entity actions, vendors + specifications, dropdown data, layer mapping, legends, tube/fiber colors, and in-progress network tickets (GetOfflineAllNetworkTicket → only ticket_status='InProgress'). It also downloads the MBTiles basemap.
Offline editing & queue: users can create/update/convert/relocate entities offline. Each change is written to CreateElementsTbl (lib/app/services/local_database/tables/create_elements_tbl.dart) with status (0=unSyncData / 1=syncData), an action, the serialized request JSON, error, syncTime, systemId, networkId, layerId, ticketId, createdUserName, saved_request_id. The newer stack mirrors this with EntitySaveRequestTable (is_uploaded/is_deleted flags, JSON data) and FileTable for queued attachments. Offline forms write the same payload they would send online, just persisted locally (nearby_entity_details_bloc.dart:302 createElementAction).
Sync-to-server: sync is a manual, UI-triggered blind replay of the queued request JSON through the offline save path (OfflineNetworkManagerService.createElementOffline() → the online EntityOperations-style save, plus geometry/file upload), flipping the queue flag to uploaded/syncData and stamping syncTime on success, capturing the server message into error on failure (surfaced in history_screen.dart as Synced/UnSynced with sync time). The active path is the newer lib/core/database stack: unsynced rows are read via EntitySaveRequestDao.getUnSyncedEntitities() (is_uploaded = 0); on a successful create the server-issued system_id becomes canonical and is written back to the local row. Each local change is keyed by a client-side timestamp ID (DateTime.now().millisecondsSinceEpoch) and persisted with ConflictAlgorithm.replace. There is no conflict detection, no idempotency key, no retry, no ordering/transaction guarantee, and no automatic background sync (workmanager is imported but used for location, not sync) — it is last-write-wins blind replay, so a retried/duplicate submission can create duplicates. Files sync via FileDao/FileTable (is_synced 0/1) → UploadAttachment; attached ISP child devices sync via saveOfflineISPMaterData().
Storage split: SharedPreferences = session (token, userId/role, login flags, last selected province/ticket, and plaintext password — see §7); FlutterSecureStorage = remember-me username/password; sqflite (two DBs + MBTiles) = offline features/masters/queue/tiles; file cache + AES-ZIP = a sqlite_backup_<userId>.zip.aes backup of the local DB (aes_encrypt_decrypt.dart).
7. Security observations
(Report locations only; secret values not printed. These compound the web/.NET and DB findings — treat as part of the same live remediation incident.)
- TLS certificate validation disabled globally.
MyHttpOverrides.badCertificateCallback => trueinlib/main.dart:96-103accepts any server certificate → MITM exposure for every request, including/token. - Cleartext HTTP allowed and used.
android/app/src/main/AndroidManifest.xml:17setsandroid:usesCleartextTraffic="true", and most configured base URLs arehttp://(raw IPs + ports). Credentials and bearer tokens travel in plaintext on those deployments. - Passwords “encrypted” with base64. Login encodes username/password with
CommonUtils.stringToBase64(login_screen.dart:977-978) — encoding, not encryption. Trivially reversible. (Deep-link prefilled password also base64,login_screen.dart:135.) - Plaintext password persisted in SharedPreferences.
login_screen.dart:1071-1072writes the raw password toSharedPreferencesUtil.password(unencrypted app prefs) — separate from, and undermining, the FlutterSecureStorage “remember me” path.AppConstants.passwordis then reused (e.g. as the AES backup key seed,aes_encrypt_decrypt.dart:342-350). - Hardcoded Google Maps API keys in source. Multiple live keys are embedded per-flavor in
lib/app/config/environment.dart(iOS key reused across flavors), and ~13 commented-out keys sit inAndroidManifest.xml. (The active Android key correctly uses a${MAPS_API_KEY}build placeholder, but the Dart-side keys are committed.) Other files with embedded keys:lib/utils/app_constant.dart,poly_line_map.dart, map-search/job-map blocs. - Dev/internal infrastructure leaked.
app_constants.dart:72hardcodes a MapServer WMS URL pointing athttp://14.98.61.197:8090/cgi-bin/mapserv.exe?MAP=D:/Apps/Latest_7.9.0/MapFiles/(internal IP + a developer’s Windows path) — same class of leak as the web app’sdatashare.inc. - No automatic token refresh / opaque bearer. Tokens are opaque MachineKey bearers (per legacy study), stored in app prefs and attached manually; on 401 the app errors out rather than silently refreshing (refresh only used in the OTP path).
- Verbose logging of secrets.
api.dart/http_service.dartlog(...)/print(...)the full URL, headers (includingAuthorization: Bearer …), request body, and response for every call; optional plaintext log-to-file helpers exist (saveLogFile*).AppConstants.isLogEnabled=true. - ~40 customer base URLs + an inventory of deployments are committed (commented) in
api_constant.dart— information disclosure about the customer estate and internal hosts/ports.
Mitigations present (credit where due): flutter_secure_storage for remember-me, safe_device root/jailbreak/mock-location detection (lib/ui/map_screen/map_view/rooted_devices.dart), optional local_auth biometric/pattern lock, forced-app-version gating via server GlobalSettings, and a well-built AES-256-CBC local-DB backup (random per-file IV, SHA-256-derived key) in aes_encrypt_decrypt.dart — though its key is seeded from the (plaintext-stored) credentials.
8. Implications for the new platform — what the mobile client needs from the new backend/API
- A clean, versioned, JSON/typed API (not the OWIN proc-envelope). Replace the
{"data":"<stringified-JSON>"}+status/resultsconvention and per-action controllers with typed, versioned REST (or gRPC) contracts and a generated client. The mobile surface is large (~134 endpoints) but well-bounded — it maps to a coherent set of resources: layers, entities (CRUD + geometry + dynamic attributes), templates/specs, connectivity/splicing, ISP hierarchy, attachments, tickets/WFM, survey, masters/lookups, reports. - OIDC/JWT auth with real refresh + per-tenant issuer. Drop ROPC + base64 + opaque MachineKey bearers. Provide short-lived JWT access tokens + refresh, store in secure storage only, and never persist passwords. Keep the forced-app-version / config-on-login idea (
GlobalSettings) as a proper/bootstrapconfig endpoint. - Schema-driven layer + dynamic-form delivery as a first-class API. The client already consumes
GetNetworkLayers(=layer_detailsmobile subset, with mobile-visibility/barcode/template flags) andLstFormControl(dynamic-control EAV) to render forms. The new platform’s typed schema-driven entity model should be exposed as a clean “entity-type schema + form definition” endpoint — this is the single most reused mobile bootstrap and directly validates the config-not-code direction. - Geometry as typed GeoJSON, not WKT strings round-tripped through headers. The client ships WKT/
longLat; the new API should accept/return GeoJSON (4326) with server-side validation, and stop overloading HTTP headers (Entity_Type/Entity_Action/source_ref_*) for write semantics — put them in the request body. - First-class connectivity/topology endpoints. Splicing and trace are already explicit (
Connection/*,GetConnectionInfo,GetCPFelementPath) overconnection_info. The graph engine should expose upstream/downstream/impact traces and port/strand selection as native graph queries — the mobile client will consume them directly. - A proper offline-sync contract. Today’s mobile offline is a blind replay queue with no conflict handling, no idempotency, no ordering, no delta (and duplicated across two SQLite DBs). The new backend should provide: idempotency keys, server-issued IDs reconciled deterministically, delta/changefeed download (it already gestures at this with
GetVectorDeltaByGeom), optimistic-concurrency tokens (ETag/version) for conflict detection, and batch transactional sync. This is one of the clearest, highest-value requirements the mobile analysis surfaces. - Tenant + ticket scoping in the contract. Edits are scoped by
source_ref_type=Network_Ticket/source_ref_idand by region/provincenetwork_id+block_code. The new multi-tenant model should make tenant + work-order scope explicit and server-enforced rather than header-passed filter strings. - Security baseline (mandatory). HTTPS-only with pinning (remove
badCertificateCallbackandusesCleartextTraffic), no secrets in source (move all map keys to per-build secret injection), no plaintext credentials in prefs, redact auth headers/bodies from logs, per-tenant key management. Keepsafe_device/biometric gating and the encrypted local-DB backup pattern. - Map rendering modernization. The dual Google-basemap + self-hosted-MapServer-WMS + GeoJSON-vector approach (with a leaked dev MapServer URL) should consolidate on a vector-tile/GeoJSON service behind the gateway, with offline tiles delivered as MBTiles/PMTiles via an authenticated endpoint.
- Collapse the two client stacks. A new app should standardize one networking layer, one local store, one model convention, and a single state-management approach — but the feature/workflow inventory here (survey, entity CRUD, barcode, splicing, ISP, customer-connect, WFM/CPE, trouble-ticket, geo-photo, offline) is the authoritative scope of what field users do and should be carried forward intact.
Compiled 2026-06-16 from a read-only analysis of references/Flutter-SmartInventory@SmartInvetory-main. Pair with SmartInventory_Legacy_Architecture_Study.md (server SmartInventoryServices is this app’s backend) and docs/database/01 & 04 (the masters/registry and connection_info the mobile models mirror).