Skip to content

Conversation

@rachana-egov
Copy link
Collaborator

No description provided.

rachana-egov and others added 30 commits June 12, 2025 12:11
HCMPRE-2824 : Validation changes
Integrated fetching non mobile users
Added new events to attendance_bloc.dart to fetch non mobile users
Modified query to fetch same tagged users

Modified Non-Mobile users UI to integrate with dynamic data
Fixed code rabbit comments
Removed extra BeneficiaryTypes
Published transit_post
Updated digit_scanner
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 18, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • .github/workflows/build-apk.yml is excluded by !**/*.yml

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds remote localization flow and environment-driven locale handling; integrates forms engine and forms-based localization delegate; introduces digit_crud_bloc package with global search/CRUD and mapping/helpers; refactors complaints to a CRUD-based inbox with a unified dialog and removes the complaints registration UI/blocs; enriches and persists form schemas via MDMS; removes tenant boundary models; updates routing and DI (SearchEntityRepository); various UI/localization tweaks and model adjustments; adds workflows README.

Changes

Cohort / File(s) Summary
Workflows Docs
.github/workflows/README.md
New README documenting three APK build workflows, inputs, outputs, examples, and troubleshooting.
App Boot, DI & Env Banner
apps/.../lib/app.dart, apps/.../lib/blocs/app_initialization/app_initialization.dart
Injects RepositoryProvider<SearchEntityRepository> into DI; adds environment banner for non-prod environments; minor import/indent cleanup.
Localization Core & Events
apps/.../lib/blocs/localization/app_localization.dart, apps/.../lib/blocs/localization/localization.dart, .../localization.freezed.dart
Unconditionally clears localization cache on load; adds new remote localization event variant onRemoteLoadLocalization and handler to fetch, persist, retryModule handling, and then refresh active locale.
Localization Storage & Queries
apps/.../lib/data/repositories/local/localization.dart
Query changes to support multi-module (comma-separated) filtering via AND/isIn and upsert on create (insertAllOnConflictUpdate).
Localization Delegates & Keys
apps/.../lib/utils/localization_delegates.dart, apps/.../lib/utils/i18_key_constants.dart
Integrates forms-engine localization delegate; adds Login.qrLogin i18n key.
Localization Callers & Module Assembly
apps/.../lib/pages/home.dart, apps/.../lib/pages/language_selection.dart, apps/.../lib/pages/authenticated.dart, apps/.../lib/pages/project_selection.dart, apps/.../lib/pages/boundary_selection.dart
Switches tenantId source to envConfig.variables.tenantId; adds online/offline localization triggers (remote vs local load); constructs dynamic module lists from stored schema/templates and flow names; prefixes boundary label keys with hierarchyType; pre-flight localization before project/boundary navigation; injects FormsBloc where needed.
Project Schema Enrichment & MDMS
apps/.../lib/blocs/project/project.dart, apps/.../lib/data/repositories/remote/mdms.dart, apps/.../lib/models/app_config/*
Adds MdmsRepository.searchMDMS; enriches FormConfig with MDMS master data enums (enrichFormSchemaWithEnums); persists schemas with storeSchema and versioning; extends MdmsMasterDetailModel with optional filter (and generated boilerplate updates).
Tenant Boundary Model Removal & Tests
apps/.../lib/models/tenant_boundary/*, apps/.../test/models/tenant_boundary_test.dart, apps/.../test/integration_test/boundary_selection.dart
Deletes tenant boundary model source and generated Freezed/json files; removes unit test; integration test updated to import external data model package.
Router Updates (App-level)
apps/.../lib/router/app_router.dart, apps/.../lib/router/app_router.gr.dart
Adds FormsRoute module pages; consolidates complaints routes; adds scanner route; removes facility-selection route and some gm imports; wires forms pages into app router.
UI & Showcase Tweaks
apps/.../lib/pages/qr_details_page.dart, apps/.../lib/widgets/showcase/config/home_showcase.dart, apps/.../lib/widgets/showcase/showcase_button.dart
Adds NoResultCard fallback for QR details when no registers; adds dataShare and transitPost showcase items; removes two complaints-related showcase cases.
Utils Enhancements (App)
apps/.../lib/utils/utils.dart
Swaps BoundaryModel import to public type; removes a local PGR repo from sync list; adds transformJson (flatten templates) and triggerLocalizationIfUpdated helpers to compose modules and dispatch remote localization loads.
Attendance: Model & Docs
packages/attendance_management/lib/models/entities/attendance_register.dart, .../attendance_register.mapper.dart, packages/attendance_management/CHANGELOG.md, README.md
Removes tag field from AttendanceRegisterModel and mapper/copyWith; updates README and changelog.
Complaints: Models & Mappers (clientAuditDetails)
packages/complaints/lib/models/*, .../*.mapper.dart, .../*.g.dart
Adds optional clientAuditDetails propagation and copyWith support across multiple complaint models and response mappers; updates constructors and mapper fields to be optional and include clientAuditDetails.
Complaints: Routing & Pages (rework)
packages/complaints/lib/router/complaints_router*.dart, packages/complaints/lib/pages/inbox/*, packages/complaints/lib/pages/complaints_acknowledgement.dart
Collapses inbox filter/search/sort pages into a single ComplaintsInboxDialogRoute and dialog page; removes complaints registration flow pages; refactors inbox to template-driven UI integrating ComplaintWrapperBloc and FormsBloc; acknowledgement page reads template config.
Complaints: Blocs (add/remove)
packages/complaints/lib/blocs/complaint_wrapper/complaint_wrapper_bloc.dart, packages/complaints/lib/blocs/complaints_inbox/*, packages/complaints/lib/blocs/complaints_registration/*
Adds ComplaintWrapperBloc (CRUD-backed wrapper); deletes ComplaintsInboxBloc and ComplaintsRegistrationBloc and their generated Freezed files.
Complaints: Utils, Exports & Constants
packages/complaints/lib/complaints.dart, packages/complaints/lib/utils/constants.dart, packages/complaints/lib/utils/utils.dart
Reorients public exports to favor data-layer and selected pages; adds ComplaintKeys and ComplaintsInboxDialogType; stores template configs and complaintConfig in ComplaintsSingleton and exposes a router map entry.
Complaints: iOS xcconfig
packages/complaints/example/ios/Flutter/*.xcconfig
Adds optional #include? lines for CocoaPods xcconfig includes.
Complaints: Tests
packages/complaints/test/*
Deletes several Complaints inbox/registration bloc tests.
Digit CRUD Bloc (new package)
packages/digit_crud_bloc/*
New package: CrudBloc (+Freezed events/states), CrudService, GlobalSearchParameters models with JSON, helpers: QueryBuilder, HydrationHelper, RelationshipGraphHelper, SearchEntityRepository, EntityModel mapping utilities, singleton CrudBlocSingleton, README, CHANGELOG, metadata.
Digit Data Converter (scaffold)
packages/digit_data_converter/*
Adds package scaffolding (CHANGELOG, .metadata, .gitignore); adds EntityModelJsonMapper and scaffolding for data conversion (registry) in code files.

Sequence Diagram(s)

sequenceDiagram
  participant UI as UI (Home / Language / Project)
  participant LBloc as LocalizationBloc
  participant Remote as Localization API
  participant LocalRepo as LocalizationLocalRepo

  UI->>LBloc: onRemoteLoadLocalization(module, tenantId, locale, path)
  activate LBloc
  LBloc->>Remote: fetch localization for modules
  alt success
    Remote-->>LBloc: localization payload
    LBloc->>LocalRepo: upsert persisted localizations
  else failure
    LBloc-->>UI: set retryModule / log error
  end
  LBloc-->>UI: _loadLocale(parsedLocaleCodes)
  deactivate LBloc
Loading
sequenceDiagram
  participant Inbox as ComplaintsInboxPage
  participant WBloc as ComplaintWrapperBloc
  participant Crud as CrudBloc (global)
  participant Service as DigitCrudService
  participant Repo as SearchEntityRepository
  participant SQL as Local SQL

  Inbox->>WBloc: loadFromGlobal()
  WBloc->>Crud: CrudEvent.search(GlobalSearchParameters)
  Crud->>Service: searchEntities(query)
  Service->>Repo: searchEntities(filters, graph, nested, select, pagination)
  Repo->>SQL: Query root + related tables (QueryBuilder.queryRawTable)
  Repo->>Repo: Hydrate nested rows (HydrationHelper)
  Repo-->>Service: (results by model, totalCount)
  Service-->>Crud: returned results
  Crud-->>WBloc: CrudStateLoaded(results, totalCount)
  WBloc-->>Inbox: emit loaded wrappers
Loading
sequenceDiagram
  participant Project as ProjectBloc
  participant MDMS as MdmsRepository
  participant Store as SharedPreferences

  Project->>MDMS: searchMDMS(formConfigRequest)
  alt success
    MDMS-->>Project: formConfig results
    Project->>Project: enrichFormSchemaWithEnums(formConfig)
    Project->>Store: storeSchema(enrichedSchemaJson, version)
  else failure
    Project-->>Project: emit syncError(appConfig)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Poem

I thump my paw—new routes appear,
A CRUD-y warren, mapped and clear.
Forms hum, locales fetched in line,
MDMS sprinkles enums like thyme.
Banners wave — dev, qa, and prod;
Rabbity cheers: build, test, deploy! 🥕✨

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch build-apk-action

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 33

🔭 Outside diff range comments (8)
packages/digit_data_converter/.gitignore (1)

30-30: Remove stray “30” line — it will ignore files named ‘30’

Line 30 appears to be a formatting artifact and will unintentionally ignore any file/dir literally named “30”. Remove it.

Apply this diff:

-30
apps/health_campaign_field_worker_app/lib/pages/project_selection.dart (1)

253-263: Avoid indefinite wait on BoundaryBloc stream; add timeout and user feedback

firstWhere without a timeout can hang indefinitely if no boundary arrives. Add a timeout and surface a user-visible error on failure.

Apply:

-    try {
-      await boundaryBloc.stream
-          .firstWhere((element) => element.boundaryList.isNotEmpty);
+    try {
+      await boundaryBloc.stream
+          .firstWhere((element) => element.boundaryList.isNotEmpty)
+          .timeout(const Duration(seconds: 15));
       if (mounted) {
         context.router.replaceAll([
           BoundarySelectionRoute(),
         ]);
       }
-    } catch (e) {
-      debugPrint('error $e');
+    } on TimeoutException {
+      if (mounted) {
+        Toast.showToast(
+          context,
+          message: localizations.translate(
+            i18.projectSelection.fetchBoundaryFailed,
+          ),
+          type: ToastType.error,
+        );
+      }
+    } catch (e) {
+      debugPrint('error $e');
     }

Add this import at the top of the file:

import 'dart:async';
apps/health_campaign_field_worker_app/lib/pages/boundary_selection.dart (2)

745-752: Fix: Preserve label keys when resetting codes after selection changes

LocalizationParams().setCode currently sets only boundary codes, potentially evicting label codes from the active code set. Include the hierarchy-prefixed label codes as well.

Apply this diff:

   if (state.boundaryList.isNotEmpty) {
-    leastLevelBoundaries = (state.boundaryList.map((e) => e.code!).toList());
-    LocalizationParams()
-        .setCode(state.boundaryList.map((e) => e.code!).toList());
+    leastLevelBoundaries = state.boundaryList.map((e) => e.code!).toList();
+    final labelCodeList = state.selectedBoundaryMap.keys
+        .map((key) => '${envConfig.variables.hierarchyType}_$key')
+        .toList();
+    LocalizationParams().setCode([
+      ...leastLevelBoundaries,
+      ...labelCodeList,
+    ]);
   }

787-796: Bug: Set proper ‘required’ error key to show validation message

setErrors({'': true}) won’t trigger your "required" validation message. Use the same key.

Apply this diff:

-      if (formControl.value == null) {
-        formControl.setErrors({'': true});
+      if (formControl.value == null) {
+        formControl.setErrors({'required': true});
apps/health_campaign_field_worker_app/lib/blocs/localization/localization.dart (1)

32-103: Don’t clear retryModule in finally; and avoid rethrow that can crash the Bloc.

  • Finally block resets retryModule to null, erasing failure context emitted earlier, making it hard for the UI to prompt a retry.
  • Rethrowing from the catch can crash the Bloc handler unnecessarily; you already emit failure.
   } catch (error) {
-      rethrow;
+      debugPrint('error in _onLoadLocalization $error');
   } finally {
     final List codes = event.locale.split('_');
     await _loadLocale(codes);
-    emit(state.copyWith(loading: false, retryModule: null));
+    // Only clear loading here; preserve retryModule if set during error handling.
+    emit(state.copyWith(loading: false));
   }
apps/health_campaign_field_worker_app/lib/app.dart (2)

177-185: Fix selectedLocale handling (null-safety and type correctness) before using Locale()

Currently, selectedLocale may be null or a String while used as Locale(selectedLocale). This can crash at runtime and also leads to type ambiguities later.

Refactor to:

  • Derive a sane default Locale from appConfig.languages.
  • Keep both a string code (for repository/events) and a Locale object (for MaterialApp and LocalizationParams).

Apply:

-                    var firstLanguage;
-                    firstLanguage = appConfig.languages?.lastOrNull?.value;
-                    final selectedLocale =
-                        AppSharedPreferences().getSelectedLocale ??
-                            firstLanguage;
-                    LocalizationParams().setLocale(Locale(selectedLocale));
-                    final languages = appConfig.languages;
+                    final languages = appConfig.languages;
+                    // Pick a default from the last language entry, or fallback.
+                    final defaultLocale = _parseLocaleFromCode(
+                          languages?.lastOrNull?.value,
+                        ) ??
+                        const Locale('en', 'IN');
+                    // Keep both string code and Locale form.
+                    final selectedLocaleCode =
+                        AppSharedPreferences().getSelectedLocale ??
+                            _localeToCode(defaultLocale);
+                    final selectedLocale =
+                        _parseLocaleFromCode(selectedLocaleCode)!;
+                    LocalizationParams().setLocale(selectedLocale);

Add these helpers once per file (outside the build method):

Locale? _parseLocaleFromCode(String? code) {
  if (code == null || code.isEmpty) return null;
  final parts = code.split('_');
  if (parts.length >= 2 && parts[1].isNotEmpty) {
    return Locale(parts.first, parts[1]);
  }
  return Locale(parts.first);
}

String _localeToCode(Locale locale) {
  return locale.countryCode != null && locale.countryCode!.isNotEmpty
      ? '${locale.languageCode}_${locale.countryCode}'
      : locale.languageCode;
}

189-205: Use the string code for localization event, not the Locale object

The event expects a locale code string. After the refactor above, pass selectedLocaleCode; also the null-check against selectedLocale is no longer needed.

-                          create: (localizationModulesList != null &&
-                                  selectedLocale != null)
+                          create: (localizationModulesList != null)
                               ? (context) => LocalizationBloc(
                                   const LocalizationState(),
                                   LocalizationRepository(
                                       widget.client, widget.sql),
                                   widget.sql)
                                 ..add(
                                   LocalizationEvent.onLoadLocalization(
                                     module:
                                         "hcm-boundary-${envConfig.variables.hierarchyType.toLowerCase()},${localizationModulesList.interfaces.where((element) => element.type == Modules.localizationModule).map((e) => e.name.toString()).join(',')}",
-                                    tenantId: envConfig.variables.tenantId,
-                                    locale: selectedLocale,
+                                    tenantId: envConfig.variables.tenantId,
+                                    locale: selectedLocaleCode,
                                     path: Constants.localizationApiPath,
                                   ),
                                 )
apps/health_campaign_field_worker_app/lib/utils/utils.dart (1)

407-434: Use the renamed helper and guard against null codes to avoid NPEs

The recursion and matching logic are fine, but boundary.code! can throw if code is null.

Apply:

-List<String> findLeastLevelBoundaries(List<BoundaryModel> boundaries) {
+List<String> findLeastLevelBoundaries(List<BoundaryModel> boundaries) {
   // Find the least level boundary type
-  String leastLevelType = _findLeastLevelBoundaryCode(boundaries);
+  final leastLevelType = _findLeastLevelBoundaryType(boundaries);

   // Initialize a list to store the matching boundary codes with lowest level boundary type
-  List<String> leastLevelBoundaryCodes = [];
+  final leastLevelBoundaryCodes = <String>[];

   // Iterate through the boundaries to find matching codes
   if (leastLevelType.isNotEmpty) {
     for (var boundary in boundaries) {
       // Check if the boundary matches the least-level type and has no children (leaf node)
       if ((boundary.boundaryType == leastLevelType ||
               boundary.label == leastLevelType) &&
           boundary.children.isEmpty) {
         // Found a least level boundary with no children (leaf node), add its code
-        leastLevelBoundaryCodes.add(boundary.code!);
+        if (boundary.code != null && boundary.code!.isNotEmpty) {
+          leastLevelBoundaryCodes.add(boundary.code!);
+        }
       } else if (boundary.children.isNotEmpty) {
         // Recursively search in the children
         List<String> childVillageCodes =
             findLeastLevelBoundaries(boundary.children);
         leastLevelBoundaryCodes.addAll(childVillageCodes);
       }
     }
   }
   …
 }
♻️ Duplicate comments (2)
packages/complaints/lib/models/pgr_complaints.dart (1)

11-23: Regeneration check for mappers including clientAuditDetails

Given these models rely on dart_mappable (generated files), ensure codegen artifacts include clientAuditDetails to avoid runtime mapping gaps. Based on past learnings, avoid manual edits to generated mapper files and regenerate instead.

#!/bin/bash
set -euo pipefail
# Verify clientAuditDetails is accounted for in generated mappers
rg -n -C2 'clientAuditDetails' packages/complaints/lib/models/

Note: As learned previously, mapper files are generated; update templates/configs and re-run codegen rather than editing generated outputs.

Also applies to: 85-97

packages/complaints/lib/models/pgr_complaints.mapper.dart (1)

40-60: LGTM: clientAuditDetails field wired into PgrComplaintModel mapper

Field declaration, instantiation, and fields map all include clientAuditDetails with opt: true. This aligns with the broader client audit introduction.

Note: As this is generated code, ensure changes originate from generator/templates, not hand-edits, to keep regeneration stable.

Comment on lines +7 to +15
### 1. **Build APK with Custom Environment** (`build-apk.yml`)
Build a single APK with completely custom environment configuration.

### 2. **Build Multiple APKs** (`build-multiple-apks.yml`)
Build multiple APKs for different environments in parallel.

### 3. **Build APK (Predefined Environments)** (`build-predefined-environments.yml`)
Build APK using predefined environment configurations (unified-dev, unified-qa, etc.).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify referenced workflow files and triggers exist (build-apk.yml, build-multiple-apks.yml, build-predefined-environments.yml).

Before merging, confirm the three workflows actually exist under .github/workflows, expose workflow_dispatch with the documented inputs, and are named as referenced here.

Run this script from repo root:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "List workflow files:"
fd -a --glob ".github/workflows/*.yml" || true

echo -e "\nCheck for expected workflow filenames:"
for f in build-apk.yml build-multiple-apks.yml build-predefined-environments.yml; do
  if [ -f ".github/workflows/$f" ]; then
    echo "FOUND: .github/workflows/$f"
  else
    echo "MISSING: .github/workflows/$f"
  fi
done

echo -e "\nShow 'on:' and 'inputs:' for each workflow:"
fd --glob ".github/workflows/*.yml" -x sh -c '
  echo "---- $1 ----"
  sed -n "1,120p" "$1" | rg -n -C2 "^(on:|workflow_dispatch:|inputs:)" || true
' sh {}

Length of output: 1649


To accurately verify the presence and contents of your workflows, let’s list the actual files in .github/workflows (including hidden names) and then inspect their on: and inputs: sections:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Files in .github/workflows ==="
ls -1A .github/workflows || { echo "No .github/workflows directory found"; exit 0; }

echo -e "\n=== Inspecting 'on:' and 'inputs:' in each workflow ==="
for file in .github/workflows/*.yml; do
  [ -f "$file" ] || continue
  echo "---- $file ----"
  awk 'NR<=120' "$file" \
    | rg -n -C2 "^(on:|workflow_dispatch:|inputs:)" \
    || echo "(no matching sections within first 120 lines)"
done

Length of output: 362


Missing workflow files and triggers
It looks like your .github/workflows directory only contains README.md—the three workflows referenced in that file are absent.

• Add the following YAML files under .github/workflows/:

  • build-apk.yml
  • build-multiple-apks.yml
  • build-predefined-environments.yml
    • In each workflow, include an on: workflow_dispatch section exposing the inputs documented in your README.
🧰 Tools
🪛 LanguageTool

[grammar] ~7-~7: There might be a mistake here.
Context: ...h Custom Environment** (build-apk.yml) Build a single APK with completely custo...

(QB_NEW_EN)


[grammar] ~10-~10: There might be a mistake here.
Context: ...tiple APKs** (build-multiple-apks.yml) Build multiple APKs for different enviro...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...** (build-predefined-environments.yml) Build APK using predefined environment c...

(QB_NEW_EN)

🤖 Prompt for AI Agents
In .github/workflows/README.md around lines 7 to 15, the README references three
workflow files that are missing; create the three YAML files
.github/workflows/build-apk.yml, .github/workflows/build-multiple-apks.yml and
.github/workflows/build-predefined-environments.yml and in each file add an on:
workflow_dispatch section that defines the inputs documented in the README
(names, types, defaults and required flags) so the workflows can be manually
triggered with the same parameters described; ensure each workflow file also
contains the appropriate job skeleton and uses those inputs in job steps.

Comment on lines +38 to +44
3. Enter comma-separated values for:
- Environments: `DEV,QA,UAT`
- Base URLs: `https://dev.domain.com/,https://qa.domain.com/,https://uat.domain.com/`
- Tenant IDs: `dev-tenant,qa-tenant,uat-tenant`
4. Click **Run workflow**
5. Download all generated APKs from **Artifacts**

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Multi-APK section misses required inputs per environment.

You only list Environments, Base URLs, Tenant IDs. The Required Parameters table includes MDMS API Path, Actions API Path, and Hierarchy Type. Readers won’t know how to pass those for each environment.

Add the missing fields to the “Enter comma-separated values” list:

 3. Enter comma-separated values for:
    - Environments: `DEV,QA,UAT`
    - Base URLs: `https://dev.domain.com/,https://qa.domain.com/,https://uat.domain.com/`
    - Tenant IDs: `dev-tenant,qa-tenant,uat-tenant`
+   - MDMS API Paths: `egov-mdms-service/v1/_search,egov-mdms-service/v1/_search,egov-mdms-service/v1/_search`
+   - Actions API Paths: `access/v1/actions/mdms/_get,access/v1/actions/mdms/_get,access/v1/actions/mdms/_get`
+   - Hierarchy Types: `ADMIN,ADMIN,ADMIN`

If the workflow provides defaults, explicitly document them here.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
3. Enter comma-separated values for:
- Environments: `DEV,QA,UAT`
- Base URLs: `https://dev.domain.com/,https://qa.domain.com/,https://uat.domain.com/`
- Tenant IDs: `dev-tenant,qa-tenant,uat-tenant`
4. Click **Run workflow**
5. Download all generated APKs from **Artifacts**
3. Enter comma-separated values for:
- Environments: `DEV,QA,UAT`
- Base URLs: `https://dev.domain.com/,https://qa.domain.com/,https://uat.domain.com/`
- Tenant IDs: `dev-tenant,qa-tenant,uat-tenant`
- MDMS API Paths: `egov-mdms-service/v1/_search,egov-mdms-service/v1/_search,egov-mdms-service/v1/_search`
- Actions API Paths: `access/v1/actions/mdms/_get,access/v1/actions/mdms/_get,access/v1/actions/mdms/_get`
- Hierarchy Types: `ADMIN,ADMIN,ADMIN`
4. Click **Run workflow**
5. Download all generated APKs from **Artifacts**
🧰 Tools
🪛 LanguageTool

[grammar] ~38-~38: There might be a mistake here.
Context: ...w** 3. Enter comma-separated values for: - Environments: DEV,QA,UAT - Base URL...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...eparated values for: - Environments: DEV,QA,UAT - Base URLs: `https://dev.domain.com/,http...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...ironments: DEV,QA,UAT - Base URLs: https://dev.domain.com/,https://qa.domain.com/,https://uat.domain.com/ - Tenant IDs: `dev-tenant,qa-tenant,uat-te...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...tps://uat.domain.com/ - Tenant IDs:dev-tenant,qa-tenant,uat-tenant` 4. Click Run workflow 5. Download all g...

(QB_NEW_EN)

Comment on lines +60 to +71
### Required Parameters

| Parameter | Description | Example |
|-----------|-------------|---------|
| **Base URL** | Your backend service URL | `https://your-domain.com/` |
| **MDMS API Path** | MDMS service endpoint | `egov-mdms-service/v1/_search` |
| **Tenant ID** | Your tenant identifier | `your-tenant-id` |
| **Actions API Path** | Actions API endpoint | `access/v1/actions/mdms/_get` |
| **Hierarchy Type** | Boundary hierarchy type | `ADMIN` or `MICROPLAN` |
| **Environment Name** | Target environment | `DEV`, `QA`, `UAT`, `DEMO`, `PROD` |
| **Build Type** | APK build configuration | `release` or `profile` |

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Clarify URL/path formatting to avoid malformed endpoints.

Base URL examples end with a slash. Some API path examples include a leading slash elsewhere. Mixing styles easily creates double-slashes or missing slashes at runtime. Document the contract.

Append a clear note after the Required Parameters table:

 | **Build Type** | APK build configuration | `release` or `profile` |
 
+Note:
+- Ensure Base URL ends with a trailing slash, e.g., `https://your-domain.com/`
+- Ensure all API Paths do NOT start with a leading slash, e.g., `egov-mdms-service/v1/_search`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Required Parameters
| Parameter | Description | Example |
|-----------|-------------|---------|
| **Base URL** | Your backend service URL | `https://your-domain.com/` |
| **MDMS API Path** | MDMS service endpoint | `egov-mdms-service/v1/_search` |
| **Tenant ID** | Your tenant identifier | `your-tenant-id` |
| **Actions API Path** | Actions API endpoint | `access/v1/actions/mdms/_get` |
| **Hierarchy Type** | Boundary hierarchy type | `ADMIN` or `MICROPLAN` |
| **Environment Name** | Target environment | `DEV`, `QA`, `UAT`, `DEMO`, `PROD` |
| **Build Type** | APK build configuration | `release` or `profile` |
### Required Parameters
| Parameter | Description | Example |
|-------------------------|--------------------------------|--------------------------------------------------|
| **Base URL** | Your backend service URL | `https://your-domain.com/` |
| **MDMS API Path** | MDMS service endpoint | `egov-mdms-service/v1/_search` |
| **Tenant ID** | Your tenant identifier | `your-tenant-id` |
| **Actions API Path** | Actions API endpoint | `access/v1/actions/mdms/_get` |
| **Hierarchy Type** | Boundary hierarchy type | `ADMIN` or `MICROPLAN` |
| **Environment Name** | Target environment | `DEV`, `QA`, `UAT`, `DEMO`, `PROD` |
| **Build Type** | APK build configuration | `release` or `profile` |
Note:
- Ensure Base URL ends with a trailing slash, e.g., `https://your-domain.com/`
- Ensure all API Paths do NOT start with a leading slash, e.g., `egov-mdms-service/v1/_search`
🧰 Tools
🪛 LanguageTool

[grammar] ~62-~62: There might be a mistake here.
Context: ...s | Parameter | Description | Example | |-----------|-------------|---------| | ...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ... | |-----------|-------------|---------| | Base URL | Your backend service UR...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...rvice URL | https://your-domain.com/ | | MDMS API Path | MDMS service endpo...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...point | egov-mdms-service/v1/_search | | Tenant ID | Your tenant identifier...

(QB_NEW_EN)


[grammar] ~66-~66: There might be a mistake here.
Context: ...r tenant identifier | your-tenant-id | | Actions API Path | Actions API end...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ...dpoint | access/v1/actions/mdms/_get | | Hierarchy Type | Boundary hierarch...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...ierarchy type | ADMIN or MICROPLAN | | Environment Name | Target environm...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ...t | DEV, QA, UAT, DEMO, PROD | | Build Type | APK build configurati...

(QB_NEW_EN)

🤖 Prompt for AI Agents
.github/workflows/README.md around lines 60 to 71: the parameter table omits a
contract for trailing/leading slashes which can cause double or missing slashes
when concatenating Base URL and API paths; append a short note immediately after
the table that specifies the expected format (e.g., Base URL should not end with
a slash OR API paths should start with a slash), provide one concrete
concatenation rule and one example of correct and incorrect combinations, and
advise normalizing inputs at runtime (trim trailing slash from Base URL or
ensure API paths begin with a single leading slash).

| **Connection Timeout** | Network timeout (ms) | `120000` | 2 minutes |
| **Receive Timeout** | Response timeout (ms) | `120000` | 2 minutes |
| **Send Timeout** | Request timeout (ms) | `120000` | 2 minutes |
| **Check Bandwidth API** | Bandwidth check endpoint | `/health-project/check/bandwidth` | API for checking connection speed |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Normalize example for Check Bandwidth API path (remove leading slash).

To align with the proposed “no leading slash for paths” rule.

-| **Check Bandwidth API** | Bandwidth check endpoint | `/health-project/check/bandwidth` | API for checking connection speed |
+| **Check Bandwidth API** | Bandwidth check endpoint | `health-project/check/bandwidth` | API for checking connection speed |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| **Check Bandwidth API** | Bandwidth check endpoint | `/health-project/check/bandwidth` | API for checking connection speed |
| **Check Bandwidth API** | Bandwidth check endpoint | `health-project/check/bandwidth` | API for checking connection speed |
🤖 Prompt for AI Agents
.github/workflows/README.md around line 81: the example path for the "Check
Bandwidth API" includes a leading slash (`/health-project/check/bandwidth`) but
the repo convention is to omit leading slashes; update the table entry to use
the normalized path `health-project/check/bandwidth` (remove the leading slash)
so it matches the "no leading slash for paths" rule and other examples in the
README.

Comment on lines +363 to 366
final selectedLocale =
AppSharedPreferences().getSelectedLocale ??
firstLanguage;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid re-deriving selectedLocale as a possibly-null String inside the builder

Recompute a proper Locale using the same helpers to keep consistency with the top-level computation.

-                            final selectedLocale =
-                                AppSharedPreferences().getSelectedLocale ??
-                                    firstLanguage;
+                            final selectedLocale = _parseLocaleFromCode(
+                                  AppSharedPreferences().getSelectedLocale,
+                                ) ??
+                                defaultLocale;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final selectedLocale =
AppSharedPreferences().getSelectedLocale ??
firstLanguage;
final selectedLocale = _parseLocaleFromCode(
AppSharedPreferences().getSelectedLocale,
) ??
defaultLocale;
🤖 Prompt for AI Agents
In apps/health_campaign_field_worker_app/lib/app.dart around lines 363 to 366,
the builder is re-deriving selectedLocale as a possibly-null String; instead
compute a proper Locale instance using the same helper(s) used at the top level
(apply the same parse/locale-construction logic and fall back to firstLanguage
when null), replace the String usage with that Locale, and ensure null-safety by
keeping the identical fallback behavior so the builder and top-level locale
resolution remain consistent.

Comment on lines +337 to +343
rows = rows.where((row) {
final lat = row['latitude'] as double?;
final lon = row['longitude'] as double?;
if (lat == null || lon == null) return false;
return haversine(centerLat!, centerLon!, lat, lon) <= radiusInKm!;
}).toList();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden lat/lon casting to handle int/num gracefully

row['latitude'] as double? will fail if the DB stores integers. Convert via num.

-      rows = rows.where((row) {
-        final lat = row['latitude'] as double?;
-        final lon = row['longitude'] as double?;
-        if (lat == null || lon == null) return false;
-        return haversine(centerLat!, centerLon!, lat, lon) <= radiusInKm!;
-      }).toList();
+      rows = rows.where((row) {
+        final latNum = row['latitude'] as num?;
+        final lonNum = row['longitude'] as num?;
+        if (latNum == null || lonNum == null) return false;
+        return haversine(centerLat!, centerLon!, latNum.toDouble(), lonNum.toDouble()) <= radiusInKm!;
+      }).toList();
🤖 Prompt for AI Agents
In packages/digit_crud_bloc/lib/repositories/helpers/query_builder.dart around
lines 337 to 343, the current casts use `as double?` which will throw if DB
values are integers; change the extraction to cast to `num?` then call
`toDouble()` (e.g. `final latNum = row['latitude'] as num?; final lat =
latNum?.toDouble();`) and do the same for longitude, keep the null-checks and
haversine comparison using the converted doubles.

Comment on lines +53 to +58
final rootTable = filters.first.root;
final queriedModels = <String>{rootTable};
final allResults = <Map<String, dynamic>>[];
var totalCount = 0;
final modelToResults = <String, List<Map<String, dynamic>>>{};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard empty filters and prefer primaryTable as root.

Using filters.first will throw on empty lists and may not align with primaryTable intent if provided.

Apply this diff:

-    final rootTable = filters.first.root;
+    if (filters.isEmpty && primaryTable == null) {
+      throw ArgumentError('At least one filter or a primaryTable must be provided');
+    }
+    final rootTable = primaryTable ?? filters.first.root;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final rootTable = filters.first.root;
final queriedModels = <String>{rootTable};
final allResults = <Map<String, dynamic>>[];
var totalCount = 0;
final modelToResults = <String, List<Map<String, dynamic>>>{};
if (filters.isEmpty && primaryTable == null) {
throw ArgumentError('At least one filter or a primaryTable must be provided');
}
final rootTable = primaryTable ?? filters.first.root;
final queriedModels = <String>{rootTable};
final allResults = <Map<String, dynamic>>[];
var totalCount = 0;
final modelToResults = <String, List<Map<String, dynamic>>>{};
🤖 Prompt for AI Agents
In packages/digit_crud_bloc/lib/repositories/local/search_entity_repository.dart
around lines 53-58, avoid calling filters.first when filters can be empty and
prefer using primaryTable as the root if provided: determine rootTable by using
primaryTable if it's non-null/non-empty, otherwise use filters.first.root only
when filters.isNotEmpty; if both are absent, handle the error or return an empty
result early. Initialize queriedModels and modelToResults based on that
rootTable (or skip/return when rootTable is null) so you no longer directly
access filters.first and you respect primaryTable as the preferred root.

Comment on lines +143 to +151
for (final row in allResults) {
final modelName = row['modelName'] as String;
if (!select.contains(modelName)) continue;

final entity = CrudBlocSingleton.instance.dynamicEntityModelListener
.dynamicEntityModelFromMap(
modelName, QueryBuilder.snakeToCamelDeep(row));
groupedResults.putIfAbsent(modelName, () => []).add(entity!);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid non-null assertion on dynamicEntityModelFromMap; guard nulls.

dynamicEntityModelFromMap can return null for unknown model names. Avoid a crash by guarding.

Apply this diff:

-      final entity = CrudBlocSingleton.instance.dynamicEntityModelListener
-          .dynamicEntityModelFromMap(
-              modelName, QueryBuilder.snakeToCamelDeep(row));
-      groupedResults.putIfAbsent(modelName, () => []).add(entity!);
+      final entity = CrudBlocSingleton.instance.dynamicEntityModelListener
+          .dynamicEntityModelFromMap(
+              modelName, QueryBuilder.snakeToCamelDeep(row));
+      if (entity != null) {
+        groupedResults.putIfAbsent(modelName, () => []).add(entity);
+      } else {
+        debugPrint('Unknown model "$modelName" encountered during hydration, skipping.');
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (final row in allResults) {
final modelName = row['modelName'] as String;
if (!select.contains(modelName)) continue;
final entity = CrudBlocSingleton.instance.dynamicEntityModelListener
.dynamicEntityModelFromMap(
modelName, QueryBuilder.snakeToCamelDeep(row));
groupedResults.putIfAbsent(modelName, () => []).add(entity!);
}
for (final row in allResults) {
final modelName = row['modelName'] as String;
if (!select.contains(modelName)) continue;
final entity = CrudBlocSingleton.instance.dynamicEntityModelListener
.dynamicEntityModelFromMap(
modelName, QueryBuilder.snakeToCamelDeep(row));
if (entity != null) {
groupedResults.putIfAbsent(modelName, () => []).add(entity);
} else {
debugPrint('Unknown model "$modelName" encountered during hydration, skipping.');
}
}
🤖 Prompt for AI Agents
In packages/digit_crud_bloc/lib/repositories/local/search_entity_repository.dart
around lines 143 to 151, the code uses a non-null assertion on
dynamicEntityModelFromMap which can return null for unknown model names; change
the logic to check the returned entity for null before adding it to
groupedResults (skip or optionally log when null) instead of using `!`, ensuring
the loop continues safely without throwing; keep the existing modelName filter
and add a null check like `if (entity == null) continue;` before
putIfAbsent/add.

Comment on lines +157 to +183
Expression<bool> buildDynamicExpression({
required Expression col,
required String operator,
required dynamic value,
}) {
final symbol = Symbol(operator);

try {
// For null-based methods like isNull(), isNotNull()
if (operator == 'isNull' || operator == 'isNotNull') {
return Function.apply(col.noSuchMethod, [Invocation.method(symbol, [])])
as Expression<bool>;
}

// For list-based methods like isIn(), isNotIn()
if ((operator == 'isIn' || operator == 'isNotIn') && value is! List) {
throw Exception("Operator '$operator' expects a List value");
}

// Normal method with one argument
return Function.apply(col.noSuchMethod, [
Invocation.method(symbol, [value])
]) as Expression<bool>;
} catch (e) {
throw Exception("Failed to apply operator '$operator' on column: $e");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Critical: buildDynamicExpression uses noSuchMethod incorrectly; replace with explicit operator mapping.

Invoking col.noSuchMethod with Invocation won’t call the intended drift DSL methods and will likely throw. Also, operator strings elsewhere (e.g., 'in') won’t match method names ('isIn').

If this function is unused, remove it. If intended for QueryBuilder, implement a safe, explicit mapping.

Apply this diff to implement a minimal, safe mapping:

-Expression<bool> buildDynamicExpression({
-  required Expression col,
-  required String operator,
-  required dynamic value,
-}) {
-  final symbol = Symbol(operator);
-
-  try {
-    // For null-based methods like isNull(), isNotNull()
-    if (operator == 'isNull' || operator == 'isNotNull') {
-      return Function.apply(col.noSuchMethod, [Invocation.method(symbol, [])])
-          as Expression<bool>;
-    }
-
-    // For list-based methods like isIn(), isNotIn()
-    if ((operator == 'isIn' || operator == 'isNotIn') && value is! List) {
-      throw Exception("Operator '$operator' expects a List value");
-    }
-
-    // Normal method with one argument
-    return Function.apply(col.noSuchMethod, [
-      Invocation.method(symbol, [value])
-    ]) as Expression<bool>;
-  } catch (e) {
-    throw Exception("Failed to apply operator '$operator' on column: $e");
-  }
-}
+Expression<bool> buildDynamicExpression<T>({
+  required Expression<T> col,
+  required String operator,
+  required dynamic value,
+}) {
+  switch (operator) {
+    case 'equals':
+    case '=':
+      return col.equals(value as T);
+    case 'notEquals':
+    case '!=':
+      return col.equals(value as T).not();
+    case 'like':
+      return (col as Expression<String>).like(value as String);
+    case 'in':
+    case 'isIn':
+      return col.isIn((value as List).cast<T>());
+    case 'notIn':
+    case 'isNotIn':
+      return col.isNotIn((value as List).cast<T>());
+    case 'isNull':
+      return col.isNull();
+    case 'isNotNull':
+      return col.isNotNull();
+    default:
+      throw UnsupportedError("Unsupported operator: $operator");
+  }
+}

Follow-up:

  • Align operator strings produced in SearchFilter (e.g., 'in') with this mapping.
  • If this helper is unused by QueryBuilder, consider deleting it to avoid drift.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Expression<bool> buildDynamicExpression({
required Expression col,
required String operator,
required dynamic value,
}) {
final symbol = Symbol(operator);
try {
// For null-based methods like isNull(), isNotNull()
if (operator == 'isNull' || operator == 'isNotNull') {
return Function.apply(col.noSuchMethod, [Invocation.method(symbol, [])])
as Expression<bool>;
}
// For list-based methods like isIn(), isNotIn()
if ((operator == 'isIn' || operator == 'isNotIn') && value is! List) {
throw Exception("Operator '$operator' expects a List value");
}
// Normal method with one argument
return Function.apply(col.noSuchMethod, [
Invocation.method(symbol, [value])
]) as Expression<bool>;
} catch (e) {
throw Exception("Failed to apply operator '$operator' on column: $e");
}
}
Expression<bool> buildDynamicExpression<T>({
required Expression<T> col,
required String operator,
required dynamic value,
}) {
switch (operator) {
case 'equals':
case '=':
return col.equals(value as T);
case 'notEquals':
case '!=':
return col.equals(value as T).not();
case 'like':
return (col as Expression<String>).like(value as String);
case 'in':
case 'isIn':
return col.isIn((value as List).cast<T>());
case 'notIn':
case 'isNotIn':
return col.isNotIn((value as List).cast<T>());
case 'isNull':
return col.isNull();
case 'isNotNull':
return col.isNotNull();
default:
throw UnsupportedError("Unsupported operator: $operator");
}
}
🤖 Prompt for AI Agents
In packages/digit_crud_bloc/lib/repositories/local/search_entity_repository.dart
around lines 157-183, the current buildDynamicExpression uses col.noSuchMethod
with Invocation which is incorrect and will not call Drift DSL methods; replace
it with an explicit, safe mapping from operator strings to the actual Expression
methods (e.g., 'isNull' -> col.isNull(), 'isNotNull' -> col.isNotNull(), 'isIn'
or 'in' -> col.isIn(value), 'isNotIn' or 'notIn' -> col.isNotIn(value), 'equals'
or '=' -> col.equals(value), '!=' or 'notEquals' -> col.isNot(value) or
equivalent, '<' -> col.lessThan(value), '<=' -> col.lessOrEqual(value), '>' ->
col.greaterThan(value), '>=' -> col.greaterOrEqual(value)); validate argument
types (e.g., List for in/isIn) and throw clear exceptions for unsupported
operators, and if this helper is not used by QueryBuilder remove it to avoid
drift issues.

Comment on lines +16 to +30
late final Map<String, List<RelationshipMapping>> _relationshipGraph;
late final Map<String, Map<String, NestedFieldMapping>> _nestedMappingLookup;

CrudService({
this.relationshipMap,
this.nestedModelMappings,
this.searchEntityRepository,
});

/// Initializes relationship graph and nested mappings for CRUD operations.
void init() {
_buildRelationshipGraph();
_initNestedMappings();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid LateInitializationError; initialize internals lazily and make init idempotent

searchEntities/create/update/delete access _relationshipGraph/_nestedMappingLookup that are late and only built by init(). If callers forget to call init(), you’ll hit a LateInitializationError at runtime. Also, CRUD methods silently no-op when no repository is found.

Make initialization idempotent and ensure it runs automatically from public methods. Also consider surfacing a clear error when repository resolution fails.

Apply:

 class CrudService {
   final List<RelationshipMapping>? relationshipMap;
   final List<NestedModelMapping>? nestedModelMappings;
   final SearchEntityRepository? searchEntityRepository;

-  late final Map<String, List<RelationshipMapping>> _relationshipGraph;
-  late final Map<String, Map<String, NestedFieldMapping>> _nestedMappingLookup;
+  late final Map<String, List<RelationshipMapping>> _relationshipGraph;
+  late final Map<String, Map<String, NestedFieldMapping>> _nestedMappingLookup;
+  bool _initialized = false;

   CrudService({
     this.relationshipMap,
     this.nestedModelMappings,
     this.searchEntityRepository,
   });

   /// Initializes relationship graph and nested mappings for CRUD operations.
-  void init() {
-    _buildRelationshipGraph();
-    _initNestedMappings();
-  }
+  void init() {
+    if (_initialized) return;
+    _buildRelationshipGraph();
+    _initNestedMappings();
+    _initialized = true;
+  }
+
+  void _ensureInitialized() {
+    if (!_initialized) init();
+  }

   /// Searches entities using the provided query parameters.
   Future<(Map<String, List<EntityModel>>, int)> searchEntities({
     required GlobalSearchParameters query,
   }) async {
+    _ensureInitialized();
     if (searchEntityRepository == null) {
       return (<String, List<EntityModel>>{}, 0);
     }
     return searchEntityRepository!.searchEntities(
       filters: query.filters,
       relationshipGraph: _relationshipGraph,
       nestedModelMapping: _nestedMappingLookup,
       select: query.select,
       primaryTable: query.primaryModel,
       pagination: query.pagination,
     );
   }

   /// Creates new entities in the data store.
   Future<void> createEntities(List<EntityModel> entities) async {
+    _ensureInitialized();
     for (final entity in entities) {
       final repository = getRepositoryForEntity(entity);
-      await repository?.create(entity);
+      if (repository == null) {
+        // Consider throwing or logging. See follow-up below.
+        continue;
+      }
+      await repository.create(entity);
     }
   }

   /// Updates existing entities in the data store.
   Future<void> updateEntities(List<EntityModel> entities) async {
+    _ensureInitialized();
     for (final entity in entities) {
       final repository = getRepositoryForEntity(entity);
-      await repository?.update(entity);
+      if (repository == null) {
+        continue;
+      }
+      await repository.update(entity);
     }
   }

   /// Deletes entities from the data store.
   Future<void> deleteEntities(List<EntityModel> entities) async {
+    _ensureInitialized();
     for (final entity in entities) {
       final repository = getRepositoryForEntity(entity);
-      await repository?.delete(entity);
+      if (repository == null) {
+        continue;
+      }
+      await repository.delete(entity);
     }
   }

Follow-up: If you prefer fail-fast, change the silent continues to throw StateError('No repository for ${entity.runtimeType}').

Also applies to: 54-69, 71-99

🤖 Prompt for AI Agents
packages/digit_crud_bloc/lib/service/crud_service.dart lines 16-30 (and apply
same changes across lines 54-69 and 71-99): the two fields _relationshipGraph
and _nestedMappingLookup are declared late and only built by init(), causing
LateInitializationError if public CRUD methods are called before init; also CRUD
methods silently noop when repository resolution fails. Make initialization lazy
and idempotent by initializing those maps on declaration or via a private
ensureInitialized() that checks an _initialized flag and calls
_buildRelationshipGraph()/_initNestedMappings() only once; call
ensureInitialized() at the start of every public method that relies on these
internals; replace silent continues on missing repository with a clear error
(or, optionally, throw StateError('No repository for ${entity.runtimeType}') for
fail-fast behavior) so repository resolution failures are surfaced.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
README.md (6)

1483-1539: Unify heading style to match the rest of the README (markdownlint MD003).

The rest of the document uses setext-style headings; this block introduces multiple ATX headings (##/###). To keep consistency and silence MD003, convert these to setext. Below patch updates all new headings to setext style.

-## **🎯 Quick Start**
+🎯 Quick Start
+----------------
@@
-## **⚙️ Configuration Parameters**
+⚙️ Configuration Parameters
+---------------------------
@@
-## **🔧 Local vs Published Packages**
+🔧 Local vs Published Packages
+------------------------------
@@
-### **📦 Published Packages (Recommended)**
+📦 Published Packages (Recommended)
+-----------------------------------
@@
-### **🛠️ Local Packages (Development)**
+🛠️ Local Packages (Development)
+--------------------------------
@@
-## **📱 Getting Your APK**
+📱 Getting Your APK
+--------------------
@@
-## **⚡ Build Time**
+⚡ Build Time
+-------------
@@
-## **🔍 Supported Features**
+🔍 Supported Features
+---------------------
@@
-## **💡 Pro Tips**
+💡 Pro Tips
+-----------

1495-1503: Add the missing “Override Dependencies” input and clarify Base URL format.

This section references an “Override Dependencies” toggle later but the parameters table doesn’t list it. Also, Base URL examples include a trailing slash, which often trips env handling and URL join logic.

 | Parameter | Description | Example |
 |-----------|-------------|---------|
-| **Base URL** | Your backend service URL | `https://your-environment.digit.org/` |
+| **Base URL** | Your backend service URL (no trailing slash) | `https://your-environment.digit.org` |
 | **Environment** | Target environment | `DEV`, `QA`, `UAT`, `DEMO`, `PROD` |
 | **Tenant ID** | Your tenant identifier | `pg`, `uk`, `mz` |
 | **Build Type** | APK build variant | `release` (production), `profile` (testing) |
 | **APK Name** | Custom name for your APK | `my-hcm-app` |
+| **Override Dependencies** | Use local package overrides instead of published versions | `true` / `false` |
+
+Note: Avoid a trailing slash in Base URL to prevent duplicate-slash issues when joining with API paths.

1481-1482: Cross-link the workflows documentation for advanced usage.

Add a pointer to .github/workflows/README.md so users can discover multi-environment builds and other workflows called out in the PR description.

-Build your HCM Field Worker App APK directly from GitHub without any local setup! Our automated workflow supports building from any branch with your custom environment configuration.
+Build your HCM Field Worker App APK directly from GitHub without any local setup! Our automated workflow supports building from any branch with your custom environment configuration.
+
+For advanced options (multi-environment, predefined envs), see .github/workflows/README.md.

1485-1485: Prefer absolute links to the Actions tab to avoid brittle relative paths.

Relative links to ../../actions can break depending on the viewer path (blob vs tree). Use the repository-absolute URL.

-1. **Navigate to Actions** → Go to the [Actions tab](../../actions) in this repository
+1. **Navigate to Actions** → Go to the [Actions tab](https://github.com/egovernments/health-campaign-field-worker-app/actions) in this repository
@@
-**Need help?** Check the [workflow logs](../../actions) for detailed build information or create an issue for support.
+**Need help?** Check the [workflow logs](https://github.com/egovernments/health-campaign-field-worker-app/actions) for detailed build information or create an issue for support.

Also applies to: 1546-1546


1544-1544: Use the repository’s horizontal rule style (markdownlint MD035).

The linter expects the long dashed hr. Replace the three-dash hr with eight or more dashes.

----
+--------

1538-1547: Add “Signing & Security Notes” to set expectations for release usage.

Help readers understand whether the produced APKs are signed and how to restrict workflow access and secrets. Suggest inserting the subsection below.

 💡 Pro Tips
 -----------
@@
 - APK name helps identify different environment builds
-
--------
-
-**Need help?** Check the [workflow logs](https://github.com/egovernments/health-campaign-field-worker-app/actions) for detailed build information or create an issue for support.
+ 
+Signing & Security Notes
+------------------------
+- If you need a signed Release APK/AAB, configure Android signing secrets (storeFile, storePassword, keyAlias, keyPassword) and wire them in the workflow. Otherwise, builds may be debug-signed or unsigned (not suitable for distribution).
+- Restrict workflow runs to trusted branches or use GitHub Environments with required reviewers to control who can trigger builds with your configs.
+- Scope secrets to environments and avoid exposing environment-specific URLs or tokens in logs.
+- Set a sensible artifact retention period (e.g., 7–30 days) to balance availability and storage/security.
+
+--------
+
+**Need help?** Check the [workflow logs](https://github.com/egovernments/health-campaign-field-worker-app/actions) for detailed build information or create an issue for support.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 37540a9 and 167b78d.

⛔ Files ignored due to path filters (1)
  • .github/workflows/build-apk.yml is excluded by !**/*.yml
📒 Files selected for processing (1)
  • README.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
README.md

1483-1483: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1493-1493: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1503-1503: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1505-1505: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1511-1511: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1517-1517: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1526-1526: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1530-1530: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1538-1538: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)


1544-1544: Horizontal rule style
Expected: --------; Actual: ---

(MD035, hr-style)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-apk
  • GitHub Check: build
🔇 Additional comments (2)
README.md (2)

1478-1482: Clear, high-value addition.

The new “Build APK with GitHub Actions” section is a helpful, self-serve path for non-mobile engineers to obtain APKs, and the flow is well explained end-to-end. Nice work.


1521-1524: Verify workflow name and artifact naming

I wasn’t able to locate any GitHub Actions workflow YAML files under .github/workflows/, so please manually confirm that:

  • The workflow’s name: field (e.g. “Build APK with your Environment”) matches what you reference in step 1.
  • The artifact’s name: (under actions/upload-artifact) matches your-app-name-ENV.apk in step 3.

If your workflows live elsewhere or use different naming, update the README to avoid confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants