Flutter 3.41 is live! Check out theFlutter 3.41 blog post!
Data layer
A walk-through of the data layer of an app that implements MVVM architecture.
The data layer of an application, known as themodel in MVVM terminology, is the source of truth for all application data. As the source of truth, it's the only place that application data should be updated.
It's responsible for consuming data from various external APIs, exposing that data to the UI, handling events from the UI that require data to be updated, and sending update requests to those external APIs as needed.
The data layer in this guide has two main components,repositories andservices.

- Repositories are the source of the truth for application data, and contain logic that relates to that data, like updating the data in response to new user events or polling for data from services. Repositories are responsible for synchronizing the data when offline capabilities are supported, managing retry logic, and caching data.
- Services are stateless Dart classes that interact with APIs, like HTTP servers and platform plugins. Any data that your application needs that isn't created inside the application code itself should be fetched from within service classes.
Define a service
#A service class is the least ambiguous of all the architecture components. It's stateless, and its functions don't have side effects. Its only job is to wrap an external API. There's generally one service class per data source, such as a client HTTP server or a platform plugin.

In the Compass app, for example, there's anAPIClient service that handles the CRUD calls to the client-facing server.
classApiClient{// Some code omitted for demo purposes.Future<Result<List<ContinentApiModel>>>getContinents()async{/* ...*/}Future<Result<List<DestinationApiModel>>>getDestinations()async{/* ...*/}Future<Result<List<ActivityApiModel>>>getActivityByDestination(Stringref)async{/* ...*/}Future<Result<List<BookingApiModel>>>getBookings()async{/* ...*/}Future<Result<BookingApiModel>>getBooking(intid)async{/* ...*/}Future<Result<BookingApiModel>>postBooking(BookingApiModelbooking)async{/* ...*/}Future<Result<void>>deleteBooking(intid)async{/* ...*/}Future<Result<UserApiModel>>getUser()async{/* ...*/}} The service itself is a class, where each method wraps a different API endpoint and exposes asynchronous response objects. Continuing the earlier example of deleting a saved booking, thedeleteBooking method returns aFuture<Result<void>>.
Some methods return data classes that are specifically for raw data from the API, such as theBookingApiModel class. As you'll soon see, repositories extract data and expose it in a different format.
Define a repository
#A repository's sole responsibility is to manage application data. A repository is the source of truth for a single type of application data, and it should be the only place where that data type is mutated. The repository is responsible for polling new data from external sources, handling retry logic, managing cached data, and transforming raw data into domain models.

You should have a separate repository for each different type of data in your application. For example, the Compass app has repositories calledUserRepository,BookingRepository,AuthRepository,DestinationRepository, and more.
The following example is theBookingRepository from the Compass app, and shows the basic structure of a repository.
classBookingRepositoryRemoteimplementsBookingRepository{BookingRepositoryRemote({requiredApiClientapiClient,}):_apiClient=apiClient;finalApiClient_apiClient;List<Destination>?_cachedDestinations;Future<Result<void>>createBooking(Bookingbooking)async{...}Future<Result<Booking>>getBooking(intid)async{...}Future<Result<List<BookingSummary>>>getBookingsList()async{...}Future<Result<void>>delete(intid)async{...}} The class in the previous example isBookingRepositoryRemote, which extends an abstract class calledBookingRepository. This base class is used to create repositories for different environments. For example, the compass app also has a class calledBookingRepositoryLocal, which is used for local development.
You can see the differences between theBookingRepository classes on GitHub.
TheBookingRepository takes theApiClient service as an input, which it uses to get and update the raw data from the server. It's important that the service is a private member, so that the UI layer can't bypass the repository and call a service directly.
With theApiClient service, the repository can poll for updates to a user's saved bookings that might happen on the server, and makePOST requests to delete saved bookings.
The raw data that a repository transforms into application models can come from multiple sources and multiple services, and therefore repositories and services have a many-to-many relationship. A service can be used by any number of repositories, and a repository can use more than one service.

Domain models
# TheBookingRepository outputsBooking andBookingSummary objects, which aredomain models. All repositories output corresponding domain models. These data models differ from API models in that they only contain the data needed by the rest of the app. API models contain raw data that often needs to be filtered, combined, or deleted to be useful to the app's view models. The repo refines the raw data and outputs it as domain models.
In the example app, domain models are exposed through return values on methods likeBookingRepository.getBooking. ThegetBooking method is responsible for getting the raw data from theApiClient service, and transforming it into aBooking object. It does this by combining data from multiple service endpoints.
// This method was edited for brevity.Future<Result<Booking>>getBooking(intid)async{try{// Get the booking by ID from server.finalresultBooking=await_apiClient.getBooking(id);if(resultBookingisError<BookingApiModel>){returnResult.error(resultBooking.error);}finalbooking=resultBooking.asOk.value;finaldestination=_apiClient.getDestination(booking.destinationRef);finalactivities=_apiClient.getActivitiesForBooking(booking.activitiesRef);returnResult.ok(Booking(startDate:booking.startDate,endDate:booking.endDate,destination:destination,activity:activities,),);}onExceptioncatch(e){returnResult.error(e);}} In the Compass app, service classes returnResult objects.Result is a utility class that wraps asynchronous calls and makes it easier to handle errors and manage UI state that relies on asynchronous calls.
This pattern is a recommendation, but not a requirement. The architecture recommended in this guide can be implemented without it.
You can learn about this class in theResult cookbook recipe.
Complete the event cycle
# Throughout this page, you've seen how a user can delete a saved booking, starting with an event—a user swiping on aDismissible widget. The view model handles that event by delegating the actual data mutation to theBookingRepository. The following snippet shows theBookingRepository.deleteBooking method.
Future<Result<void>>delete(intid)async{try{return_apiClient.deleteBooking(id);}onExceptioncatch(e){returnResult.error(e);}} The repository sends aPOST request to the API client with the_apiClient.deleteBooking method, and returns aResult. TheHomeViewModel consumes theResult and the data it contains, then ultimately callsnotifyListeners, completing the cycle.
Feedback
#As this section of the website is evolving, wewelcome your feedback!
Unless stated otherwise, the documentation on this site reflects Flutter 3.38.6. Page last updated on 2025-09-05.View source orreport an issue.