Skip to content

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 SmartInventoryServices OWIN Web API (the same /token ROPC + /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 name fiber_net (pubspec.yaml).
  • HTTP: http: ^0.13.4 is the primary client (plus dio: ^4.0.4 used 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 using rxdart StreamController/PublishSubject (the dominant pattern in lib/blocs/ and lib/ui/**/*_bloc.dart). No DI container.
  • Maps: google_maps_flutter (Google basemap + markers/polylines) overlaid with WMS tiles from a self-hosted MapServer (legacy mapserv.exe), plus flutter_polyline_points, maps_toolkit, google_maps_utils, fluster (clustering), flutter_cache_manager (tile cache). Places search via google_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 via flutter_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 packageName at runtime (lib/app/config/environment.dart). README.md lists ~10+ build flavors (fibernetBHARATNETSIT, ...RVNL, ...HFCLGGN, ...HFCLGK2, ...TERASOFT, Syokinet, VGG, etc.). Theme also switches on JIO (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).

PathRole
lib/main.dartApp 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.dart additionally 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 plain http:// 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:967 callLoginAPI): builds a LogInRequest (lib/models/request/logIn_request.dart) with grant_type='password' (OAuth2 Resource-Owner-Password-Credentials), username/password base64-encoded (CommonUtils.stringToBase64not 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) to POST {base}/token.
  • Token response (lib/models/response/login_response.dart): access_token, token_type (“Bearer”), expires_in, refresh_token, plus userId, userName, email, userRoleId/userRole, loginHistoryId, IsMasterLogin, .issued/.expires, and GlobalSettings (a JSON array of key/value server config incl. apkPath for OTA APK update and appVersion for forced-upgrade gating).
  • 2FA / OTP (post-login, lib/app/features/otp_verification/otp_verification_bloc.dart): if enabled, app calls POST /api/OTP/GetOTP then POST /api/OTP/VerifyOTP ({source, user_id, user_name, OTP}). Only here is the refresh-token grant used: refreshAccessToken() posts grant_type=refresh_token + refresh_token to /token and 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_type are persisted in SharedPreferences (SharedPreferencesUtil, keys access_token/token_type) and read on every request. Remember-me credentials go to FlutterSecureStorage via AppSecure (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 headers Source: mobile, and for entity ops Entity_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.dart entityPost*, 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):

AreaEndpoint (path)Purpose / backend correspondence
AuthPOST /tokenROPC login + refresh (SmartInventoryServices OWIN OAuthProvider, opaque MachineKey bearer)
AuthPOST /api/OTP/GetOTP, /api/OTP/VerifyOTP2FA (OTP controller; RIL4G/SMS gateways server-side)
AuthPOST /api/user/UserLogoutend session / login_history
BootstrapPOST /api/main/GetUserModuleper-user feature modules (module_abbr=NWT/WFM/SFS… + is_offline_enabled) — drives the home grid
BootstrapPOST /api/main/GetNetworkLayersthe 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)
BootstrapPOST /api/Resources/GetLanguageResourcesi18n (res_resources)
Map dataPOST /api/VectorLayer/GetVectorDataByGeom, GetVectorDeltaByGeom, GetVectorProvinceData, GetVectorEntityStyleGeoJSON vector features + styling for the map (newer vector path alongside the WMS image path)
Map dataWMS 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)
NearbyPOST /api/main/getnearbyentities, /api/Main/GetNearByCables, /GetNearByDucts, /GetNearbyAssociatedEntity, /api/main/GetNearByLandbaseEntitiesproximity queries around GPS (snap-to-nearest, parent selection)
Entity readPOST /api/main/getentityinfo, /GetEntityActions, /GetEntityAdvanceAttribute, /getGeometryDetail, /getGeometryForEdit, /GetEntityLogicalViewfeature detail, allowed actions, dynamic attributes, geometry-for-edit
Entity writePOST /api/Library/EntityOperationsthe core create/update/delete for network entities (LibraryController server-side); headers carry Entity_Type/Entity_Action/is_new_entity
Entity writePOST /api/ItemTemplate/EntityTemplateget/save the per-entity template/spec (vendor/brand/model/ports → item_template_*)
Entity writePOST /api/main/SaveEditGeometry, /GetGeometryForEdit; /api/main/SaveLandbaseEditGeometry, /GetLandbaseGeometryForEditmove/reshape geometry (WKT)
Entity writePOST /api/main/ValidateEntityGeom, /api/Main/ValidateEntityForConversionserver-side geom + as-built conversion validation
Entity writePOST /api/Library/MergeCables, /Library/SplitCablecable split/merge (fiber/tube/core re-association)
Entity writePOST /api/Main/DeleteEntity, /Main/DeleteEntityFromInfo, /Main/RevertEntityChangesdelete / revert (approval-workflow)
CodificationPOST /api/main/NetworkStage, /GetRegionProvince, /GetRegionProvinceBasedOnLocation, /Report/BindProviceByRegionIdregion/province/network_id hierarchy selection
BarcodePOST /api/main/UpdateEntityBarCodeattach scanned barcode/QR to entity (is_barcode_enabled)
AttachmentsPOST /api/main/UploadAttachment, /GetEntityImages, /GetEntityDocuments, /DownloadAttachment, /DeleteAttachmententity photos/docs
Geo-tag imagesPOST /api/main/UploadGeoTaggedImages, /GetAllGeoTaggedImages, /DownloadGeoTaggedAttachment, /DeleteGeoTaggedImageEXIF-geotagged field photos
Splicing / connectivityPOST /api/Connection/Splicing, /ODFToCable, /CableToCable, /SaveBulkConnection, /GetSplicingReportswrite splices → connection_info
Splicing / connectivityPOST /api/main/GetConnectionInfo, /GetCPFelementPath, /GetEquipmentPortInfo, /GetEquipmentSearchResult, /TerminationEntityupstream/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, /getBuildingGeombuilding→floor→shaft→room + indoor device/port mgmt (isp_* tables)
HOTOPOST /api/NetworkEntityAPI/GetOpticalHotoDetails, /SaveOpticalHotoDetails; /api//Offline/GetTubeFiberColorhandover-takeover: OTDR/LSPM tube/fiber readings (offline-capable)
Customer mgmtPOST /api/main/getCustomerInfo, /saveCustomerInfo, /savecustomerassociation, /getDistributionBoxEntityInfo, /getDistributionBoxInfo, /GetNearByDBInfo[_byticket], /getSplitterPortInfo, /getMobileLegendInfo, /GetWCRMaterial, /getontvendorslast-mile customer connect: pick distribution box/splitter, assign port, WCR material
Network ticketsPOST /api/NetworkTicket/GetAllNetworkTicket, /CreateNetworkTicket, /GetNetworkTicketEntityList, /getNetworkTicketGeometryas-built work tickets that scope edits
SurveyPOST /api/surveyarea/surveyareainfo, /api/building/buildinginfo, /getBuildingDetailsById, /insertbuilding, /updatebuildinginfo, /updatebuildinggeometry, /checknearbybuilding, /getbuildingCommentsbuilding/home-pass survey
WFM / ConvergePOST /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 ticketPOST /wfm/mobile/v1.0/{getTTJobList, getTTTicketStepsDetail, getttrcrca, getttrcrcabyjobid, updatettstatus}trouble-ticket execution (RC/RCA)
Reports/exportPOST /api/Report/{GetExportReportDropdowns, BindProviceByRegionId, GetPlanningByProjectids, GetWokorderByPlanningids, GetPurposeByWorkOrderids, getEntityExportList, GetExportReportSummary, GetReportUsersByParentUser}async export (Excel)
Offline bootstrapPOST /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 mapGET /LegendDetails/DownloadMapTilesdownload MBTiles basemap for a region
TrackingPOST /api/User/SaveUserLocation, /api/main/UpdateLocationbackground GPS upload
MiscPOST /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 fieldsBackend 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}, centerLineGeompoint/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, approvalDatemaster 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, noOfCoresPerTubeitem_template_* (vendor/brand/model/port specs); cable strand counts
MapEntityBottomSheetSelectionResult (lib/models/response/select_cable_res_model.dart)cableSystemId, networkId, cableType, cableCores, totalLoopLength/Count, availableCableLengthatt_details_cable_info strand inventory + capacity
LstEquipementPort (lib/models/response/connection_path_search.dart)portText, portValue, endpointconnection_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_deletedlayer_details mobile-visible subset, cached for offline
EntitiesModel (lib/data/datasource/local/models/entities_model.dart)systemId, entityData (full JSON blob), isDownloaded/isUploaded/isDeletedoffline 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:

  1. 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/….)
  2. 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 → EntityOperations save. Editing geometry/location via getGeometryForEditSaveEditGeometry. 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.
  3. Barcode/QR scanning (common_utils.dart FlutterBarcodeScanner.scanBarcode, entities_info.dart:~1563): for barcode-enabled layers (is_barcode_enabled), as-built validation requires a scan → UpdateEntityBarCode. QR widget in dialog_box_with_image.dart; OCR via ocr_scan_text.
  4. 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).
  5. In-building (ISP) view (lib/ui/isp_view/**, lib/ui/isp_device/**): navigate building → floor → shaft → room and manage indoor devices/ports (getStructureInfo, ISP/*).
  6. 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.
  7. WFM / Converge CPE-activation wizard (lib/app/features/converge/**, lib/app/services/api_services/converge_service.dart): job list → customer info → CPE serial searchactivate CPE → additional parts → connection testingcustomer signature → upload documents → review ordersend to ERP (the /wfm/mobile/v1.0/* family). This is the most elaborate multi-step flow and is Converge/Jio-style provisioning.
  8. Trouble-ticket execution (lib/app/features/trouble_ticket/**): TT list → ticket steps → capture issue (photos) → RC/RCA → status update (getTT*, updatettstatus).
  9. 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).
  10. Geo-tagged photo capture (lib/app/features/geo_tagging_image/**, native_exif): camera → EXIF GPS stamp → UploadGeoTaggedImages.
  11. GPS / background tracking (lib/utils/location_tracking/**, workmanager): periodic background location → SaveUserLocation/UpdateLocation; root/mock-location guard (safe_device).
  12. Reports / export (lib/ui/map_screen/more/export_Report/**): filter (region/province/planning/work-order/purpose) → server-side Excel export.
  13. Search & voice (lib/ui/map_screen/search/**, voice_recognition/**): entity/place search; voice-search is implemented but commented out/disabled.
  14. 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.migrations currently 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, migrations migration_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, standard tiles(zoom_level, tile_column, tile_row, tile_data)), downloaded via GET /LegendDetails/DownloadMapTiles and served to google_maps_flutter as 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.)

  1. TLS certificate validation disabled globally. MyHttpOverrides.badCertificateCallback => true in lib/main.dart:96-103 accepts any server certificate → MITM exposure for every request, including /token.
  2. Cleartext HTTP allowed and used. android/app/src/main/AndroidManifest.xml:17 sets android:usesCleartextTraffic="true", and most configured base URLs are http:// (raw IPs + ports). Credentials and bearer tokens travel in plaintext on those deployments.
  3. 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.)
  4. Plaintext password persisted in SharedPreferences. login_screen.dart:1071-1072 writes the raw password to SharedPreferencesUtil.password (unencrypted app prefs) — separate from, and undermining, the FlutterSecureStorage “remember me” path. AppConstants.password is then reused (e.g. as the AES backup key seed, aes_encrypt_decrypt.dart:342-350).
  5. 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 in AndroidManifest.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.
  6. Dev/internal infrastructure leaked. app_constants.dart:72 hardcodes a MapServer WMS URL pointing at http://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’s datashare.inc.
  7. 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).
  8. Verbose logging of secrets. api.dart/http_service.dart log(...)/print(...) the full URL, headers (including Authorization: Bearer …), request body, and response for every call; optional plaintext log-to-file helpers exist (saveLogFile*). AppConstants.isLogEnabled=true.
  9. ~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

  1. A clean, versioned, JSON/typed API (not the OWIN proc-envelope). Replace the {"data":"<stringified-JSON>"} + status/results convention 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.
  2. 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 /bootstrap config endpoint.
  3. Schema-driven layer + dynamic-form delivery as a first-class API. The client already consumes GetNetworkLayers (= layer_details mobile subset, with mobile-visibility/barcode/template flags) and LstFormControl (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.
  4. 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.
  5. First-class connectivity/topology endpoints. Splicing and trace are already explicit (Connection/*, GetConnectionInfo, GetCPFelementPath) over connection_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.
  6. 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.
  7. Tenant + ticket scoping in the contract. Edits are scoped by source_ref_type=Network_Ticket/source_ref_id and by region/province network_id + block_code. The new multi-tenant model should make tenant + work-order scope explicit and server-enforced rather than header-passed filter strings.
  8. Security baseline (mandatory). HTTPS-only with pinning (remove badCertificateCallback and usesCleartextTraffic), 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. Keep safe_device/biometric gating and the encrypted local-DB backup pattern.
  9. 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.
  10. 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).