Sparround

Handling error and loading states

Every async screen has at least four states, and the third and fourth are the ones most often forgotten:

  • Loading — data is on the way
  • Data — there is a result
  • Empty — the request succeeded but returned nothing (this is NOT an error)
  • Error — the request failed

Rendering an empty list as simply nothing is the classic UX mistake: users assume the app is broken. An empty state should explain — why it is empty and what they can do.

There are two ways to surface errors to the UI:

  • Throwing exceptions — Dart's native mechanism, caught with try/catch. Simple, but the signature does not reveal that failure is possible
  • Returning a Result/Either typesealed class Result { Success | Failure }; the possibility of failure is visible in the type and switch forces you to handle it

Both are legitimate. What matters is consistency: mixing the two in one codebase is the worst option.

An equally important rule: never show a raw technical error to the user. SocketException: Failed host lookup means nothing to them — map errors to domain types in the data layer and let the UI show a human message.

Interview tip. Saying try/catch is not enough for "how do you handle errors?". A strong answer has three elements: (1) where the error is caught — in the data layer, mapped to a domain type; (2) what the user sees — a human message and a retry action; (3) what the developer sees — logging or crash reporting. Mention separating the empty state from the error state too — few candidates do.

📚 Sources and documentation