Naming conventions
Consistent names are what make the Screen / State / View split navigable. When every screen follows the same scheme, finding the State for a View - or the hook for a State - is mechanical. These are the rules the codebase holds to.
The four parts of a screen
A screen named <Feature> produces four symbols. Derive all of them from the feature name:
| Part | Name | Example |
|---|---|---|
| State class | <Feature>State | CheckoutScreenState |
| State hook | use<Feature>State | useCheckoutScreenState |
| View | <Feature>View | CheckoutScreenView |
| Screen (coordinator) | <Feature>Screen / <Feature>Page | CheckoutScreen |
// DON'T - unrelated names; the View and its State are not obviously a pair
class CheckoutData { /* ... */ }
CheckoutData useCheckoutLogic() { /* ... */ }
class CheckoutWidget extends StatelessWidget { /* ... */ }
// DO - one feature name threaded through all four
class CheckoutScreenState { /* ... */ }
CheckoutScreenState useCheckoutScreenState() { /* ... */ }
class CheckoutScreenView extends StatelessWidget { /* ... */ }
class CheckoutScreen extends HookWidget { /* ... */ }
Hooks start with use
Every hook - built-in, custom, screen-level, or global - starts with use. The prefix is what marks a function as obeying the rules of hooks (stable call order, no conditionals). A function that returns state but is not itself a hook should not borrow the prefix.
// DON'T
CheckoutScreenState buildCheckoutState() { /* calls useState, useProvided... */ }
CheckoutScreenState checkoutState() { /* ... */ }
// DO
CheckoutScreenState useCheckoutScreenState() { /* ... */ }
The class name matches the file basename
The top-level class in a .dart file is the file's basename, PascalCased; the matching hook is the basename, camelCased. The path encodes the feature scope, so the name reflects that scope - not just the local concept. This keeps names unique across the app and makes a class findable from its file alone.
// file: account_onboarding/state/account_onboarding_champions_state.dart
// DON'T - drops the parent feature; collides with any other ChampionsState
class ChampionsState { /* ... */ }
AccountOnboardingChampionsState useChampionsState() { /* ... */ }
// DO - class = basename PascalCased, hook = basename camelCased
class AccountOnboardingChampionsState { /* ... */ }
AccountOnboardingChampionsState useAccountOnboardingChampionsState() { /* ... */ }
Private helper classes inside the file are exempt.
Global states name the domain, not the screen
A global state is shared app-wide, so it is named after the concern it owns - AuthState, SettingsState, CartState - never after a screen or a UI surface. Its hook follows the same use<Name>State rule.
// DON'T - a screen name on app-wide state, or a "Manager"/"Service" suffix
class LoginScreenAuthState { /* ... */ }
class AuthManager { /* ... */ } // <- that's a service, registered via useInjected
// DO - the domain it represents
class AuthState { /* ... */ }
AuthState useAuthState() { /* ... */ }
Reserve Service / Repository / Client suffixes for the injected dependencies a state hook reads via useInjected - they are a different layer from the State a hook produces.
Actions are named onX
Action fields on a State class - the callbacks the View invokes - are named for the user gesture that triggers them, prefixed on: onSavePressed, onItemTapped, onRefresh. Data fields are nouns. The prefix tells you at a glance which fields are behavior and which are values.
// DON'T - verbs that read like the hook's internals, or a plain noun for an action
class CartScreenState {
final void Function() checkout; // <- is this a value or a callback?
final void Function() doRemoveItem;
}
// DO - on<Gesture>; the View reads it as "what happens when the user does X"
class CartScreenState {
final void Function() onCheckoutPressed;
final void Function(String id) onRemovePressed;
}
Callbacks the Screen passes into the hook describe intent rather than a gesture - navigateToDetail, showErrorSnackbar, close. The hook turns those into the onX actions it exposes on the State.
Files are snake_case
Dart files use snake_case, and a screen's three parts sit in a predictable layout:
screens/checkout
|- checkout_screen.dart - Screen (coordinator)
|- state
| |- checkout_screen_state.dart - State class + hook
|- view
| |- checkout_screen_view.dart - View
// DON'T
CheckoutScreen.dart
checkoutScreenState.dart
// DO
checkout_screen.dart
checkout_screen_state.dart
See also
- Screen / State / View - the structure these names describe
- Modeling state - what goes in the
<Feature>Stateclass - Global state - registering and naming app-wide states
- Dependency injection & services - the
Service/Repositorylayer