-
Notifications
You must be signed in to change notification settings - Fork 33
Build apk action #767
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Build apk action #767
Conversation
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
Removed curllogger
…istration & Delivery Acknowledgement pages (#734)
Fixed code rabbit comments Removed extra BeneficiaryTypes
Published transit_post Updated digit_scanner
Added role check for qr page
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (1)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughAdds 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
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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120+ minutes Poem
✨ Finishing Touches🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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:
-30apps/health_campaign_field_worker_app/lib/pages/project_selection.dart (1)
253-263: Avoid indefinite wait on BoundaryBloc stream; add timeout and user feedbackfirstWhere 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().setCodecurrently 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 objectThe 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 NPEsThe 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 clientAuditDetailsGiven 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 mapperField 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.
| ### 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.). | ||
|
|
There was a problem hiding this comment.
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)"
doneLength 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.ymlbuild-multiple-apks.ymlbuild-predefined-environments.yml
• In each workflow, include anon: workflow_dispatchsection 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.
| 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** | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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)
| ### 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` | | ||
|
|
There was a problem hiding this comment.
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.
| ### 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 | |
There was a problem hiding this comment.
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.
| | **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.
| final selectedLocale = | ||
| AppSharedPreferences().getSelectedLocale ?? | ||
| firstLanguage; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| 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(); | ||
| } |
There was a problem hiding this comment.
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.
| final rootTable = filters.first.root; | ||
| final queriedModels = <String>{rootTable}; | ||
| final allResults = <Map<String, dynamic>>[]; | ||
| var totalCount = 0; | ||
| final modelToResults = <String, List<Map<String, dynamic>>>{}; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| 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!); | ||
| } |
There was a problem hiding this comment.
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.
| 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.
| 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"); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
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.
| 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.
| 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(); | ||
| } | ||
|
|
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
⛔ Files ignored due to path filters (1)
.github/workflows/build-apk.ymlis 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 namingI 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:(underactions/upload-artifact) matchesyour-app-name-ENV.apkin step 3.If your workflows live elsewhere or use different naming, update the README to avoid confusion.
No description provided.