HOCO B2B
Mobile (Flutter)

Conventions

Code conventions of hoco-mobile: lint rules, naming, folders, commit messages and scopes, pull requests, localisation keys, assets, and how task and screen ids appear in code.

These conventions apply to everything in hoco-mobile. The branch model, merge policy and hooks are shared with hoco and described in the git workflow; review rules are in pull requests and reviews.

Lints and formatting

Recommendation: very_good_analysis (strict, maintained) over flutter_lints (permissive defaults) — one developer and two apps benefit from the analyzer catching what a reviewer would. Root analysis_options.yaml, included by every package:

include: package:very_good_analysis/analysis_options.yaml

formatter:
  page_width: 100

analyzer:
  exclude:
    - "**/*.g.dart"
    - "**/*.freezed.dart"
    - "packages/hoco_api/lib/src/generated/**"
    - "packages/hoco_l10n/lib/src/generated/**"
  errors:
    invalid_annotation_target: ignore   # freezed + json_key on constructor parameters
  language:
    strict-casts: true
    strict-raw-types: true

linter:
  rules:
    public_member_api_docs: false       # apps and internal packages; document what is not obvious
    lines_longer_than_80_chars: false   # the formatter's page width rules
  • dart format (page width 100) runs in the pre-commit hook and in CI; nobody argues about formatting in review.
  • --fatal-infos: an info-level lint fails CI. Suppress a lint only on the line, with a reason: // ignore: avoid_dynamic_calls — JSON from sync.batch item body.
  • riverpod_lint is recommended for provider mistakes (missing ref.mounted, wrong provider types) if it runs with the pinned analyzer.

Naming

ThingConventionExample
Files, folders, packagessnake_caseorder_detail_screen.dart, hoco_offline
Classes, enums, extensions, typedefsUpperCamelCaseOrderDetailScreen, SyncItemResult
Members, variables, parameters, enum valueslowerCamelCasenextCursor, OrderStatus.validated
Screens (routes)<Name>ScreenCheckoutScreen (RA-18)
Large sub-trees of a screen<Name>View, <Name>SectionCartTotalsSection
List and grid items<Name>Tile, <Name>Card, <Name>RowVisitRow, ProductCard
Design-system widgetsHoco<Name> (inventory name)HocoQuantityStepper
Repositories<Entity>RepositoryOrdersRepository
Notifiers (commands, screen state)Verb or screen name, provider generatedCancelOrder → cancelOrderProvider
Read providersNoun, generatedorderDetail(orderId) → orderDetailProvider
Domain modelsBusiness name, no suffixOrder, Visit, ResellerCard
Generated DTOsAs generated, never renamedOrderDto or the schema name
MapperstoDomain() / toRequest() extensionsorderDto.toDomain()
Booleansis, has, can, shouldcanCancel, hasMore
RoutesLowercase paths shared with the reseller web/orders/:orderId

Code identifiers are in English; French appears only in ARB files and in comments quoting the product (« Valider la commande »).

Folders

  • Apps: lib/main.dart, lib/src/{app.dart, bootstrap.dart, router/, features/<feature>/{data,domain,presentation}} (architecture).
  • A feature folder name is the product area in English: catalog, cart, checkout, orders, returns, support, tour, visits, prospects, sync_centre.
  • Code used by two features of one app moves to lib/src/shared/; code used by both apps moves to a package. Nothing imports another feature's data/ or presentation/ — go through a provider exposed by that feature's domain/ or a shared package.
  • Tests mirror lib/: test/src/features/orders/data/orders_repository_test.dart. Fixtures in test/fixtures/ (JSON responses copied from the API examples).

Commit messages

Conventional Commits, checked by commitlint in the commit-msg hook (same configuration as hoco). The task id goes at the end of the subject when there is one.

ScopeCovers
resellerapps/reseller
delegateapps/delegate
corepackages/hoco_core
apipackages/hoco_api (regenerations, the pinned contract)
uipackages/hoco_ui, apps/gallery
l10npackages/hoco_l10n
offlinepackages/hoco_offline
mapspackages/hoco_maps
catalogpackages/hoco_catalog
ci.github/ workflows
workspace, android, ios, deps, releaseRoot tooling, platform folders, dependency bumps, version bumps (shared list)
feat(delegate): offline visit check-in with GPS tolerance [MDA-05]
fix(offline): reset in-flight outbox items after a crash [MOB-09]
feat(reseller): guest price lock and sign-up sheet [MOB-06]
chore(api): contract 1.3.0 [MOB-03]
feat(ui): availability indicator without quantities [MOB-04]
build(ci): upload staging APKs to App Distribution [MOB-01]

Types: feat, fix, perf, refactor, test, docs, build, ci, chore, revert. Subject in the imperative, lowercase, no final period, header at most 100 characters.

Pull requests

  • Branch feature/<TASK-ID>-short-desc (feature/MRA-04-checkout) into dev; title in Conventional Commit form with the task id; squash merge.
  • Small and reviewable: under about 400 changed lines excluding generated files; a task longer than two days ships as several PRs, unfinished screens behind a release flag (feature flags).
  • Description: task link, screen ids with links to their specs, screenshots or a short screen recording (before/after for UI), test notes (mock, staging, device), contract version if it changed, the review checklist.
  • Labels: reseller, delegate, design-qa (Akram reviews the gallery or the screenshots), contract (regeneration), pilot, hotfix.
  • Sensitive paths (hoco_api, hoco_core, hoco_offline, platform folders, .github) wait for Anis's approval; elsewhere Imad may merge his own PR once CI is green (branch protection).

Localisation keys

ARB files live in hoco_l10n (app_fr.arb is the template; app_ar.arb and app_en.arb are scaffolded). Generated with flutter gen-l10n (melos run l10n).

KindPatternExample
Screen text<feature><Element><Variant>checkoutSubmitButton: « Valider la commande »
Shared actionscommon<Action>commonRetry: « Réessayer »
Enum labelsenum<EnumName><Value> — generated from enums.json by tool/gen_enum_l10n.dart, never edited by handenumOrderStatusValidated: « Validée », enumCustomerTypeDemiGros: « Demi-gros »
Client-side error messageserror<Code> for codes the app words itselferrorNetworkOffline, errorGpsOutOfTolerance
Notification channelschannel<Name>channelTransactional
{
  "cartMinAmountRemaining": "Plus que {amount} pour commander",
  "@cartMinAmountRemaining": {
    "description": "Cart progress bar under the minimum order amount (RA-17). amount is formatted, e.g. 1 545 DA.",
    "placeholders": { "amount": { "type": "String" } }
  },
  "syncPendingCount": "{count, plural, =0{Synchronisé} =1{1 action en attente} other{{count} actions en attente}}",
  "@syncPendingCount": {
    "description": "Sync status chip (DA-27)",
    "placeholders": { "count": { "type": "int" } }
  }
}
  • Every key has a @ description saying where it appears (screen id) — translators and Akram need the context.
  • Money and dates are formatted by the helpers and passed as String placeholders; ICU plurals for counts.
  • French typography: non-breaking space before :, ;, !, ? and inside « »; DA after amounts.
  • The server already localises error messages; the app only words network errors and the codes it presents specially.

Assets

AssetLocationNaming
Icons (custom)hoco_ui/assets/icons/ (SVG, currentColor)ic_<name>.svg — ic_price_lock.svg
Illustrations (empty states, onboarding)hoco_ui/assets/illustrations/ (SVG or WebP)illus_<context>.svg — illus_empty_orders.svg
Raster imagesassets/images/ of the app, WebP with 2.0x/ and 3.0x/ variantsimg_<feature>_<name>.webp
Fontshoco_ui/assets/fonts/ (subset)Family folders, as declared in pubspec.yaml
Map marker artDrawn in code from tokens—
Launcher icons per flavorGenerated into android/app/src/<flavor>/res by flutter_launcher_icons—

Assets come from Akram's handoff (handoff › assets export); no asset is redrawn or recoloured in code.

Task and screen ids in code

Ids belong to commits, branches and PRs. In code they appear sparingly, only where they help the next reader find the specification:

  • Route declarations carry the screen id: GoRoute(path: '/checkout', builder: …), // RA-18.
  • Business rules cite the requirement where they are implemented: // FLD-07: arrival only within field.gpsToleranceMeters.
  • Golden and integration test names use the screen id: testWidgets('RA-18 shows the pending-validation banner', …).
  • Not in class names, file names or every widget; never a task id in code (MRA-04) — task ids go stale when the plan changes.
  • TODO always points to an issue: // TODO(#57): remove after contract 1.4 adds deliveryWindow — never a bare TODO.

On this page