5 min read
Development
The Best Practices Found by Backend Engineers While Developing Multiple Flutter Applications at Once

This summary was generated by AI. Please refer to the article for accurate details.
Read the summary
We solved the challenge of efficiently developing seven Flutter apps in parallel with backend work by creating a clear, shared architecture and development flow. Our team, all backend engineers, built a common Flutter architecture using MVVM and repository patterns with a layer-first directory structure. This setup separates presentation, domain, and infrastructure layers. Each app starts with the same template, so there’s no architectural mismatch between projects. We skipped Atomic Design and didn’t build many reusable UI components. This decision came from practical experience—our team struggled to align on component hierarchies, and we prioritized fast page development over abstract widgets. With frequent changes to specs, we found it more efficient to focus on page-level implementation. We standardized the tech stack across all apps: Riverpod for state management, Freezed for model definitions, GoRouter for navigation, Dio and OpenAPI-Generator for API calls, and Melos for project management. This consistency reduced confusion for new members and sped up onboarding. For example, Riverpod providers are generated with riverpod_generator, then used to connect domain interfaces to infrastructure. ViewModels use AsyncNotifierProvider to manage data flow, and UIs react to AsyncValue updates. This process is now tied to sprint tasks, making development more structured. As a result, our team now delivers frontend features faster and with fewer errors. With seven apps in progress, this approach has allowed us to maintain quality while adapting to changing needs. We’re already planning to use code generators to automate list, detail, and edit screens—common in web management apps—so future work will be even more efficient.
Hello. I am Osugi from the Woven Payment Solution Development Group.
My team is developing the payment system used by Woven by Toyota for Toyota Woven City. We typically use Kotlin/Ktor for backend development and Flutter for the frontend. In a previous article, I discussed the process of selecting the frontend technology prior to the development of our web application. Since then, we have expanded our operations and are currently working on seven Flutter applications, including both web and mobile platforms.
In this article, I will talk about how our team, which had only backend engineers, came up with ways to efficiently develop multiple Flutter applications in parallel with backend development.
Flutter and the related logo are trademarks of Google LLC. We are not endorsed by or affiliated with Google LLC.
Overview
As mentioned, our team does backend and frontend development for a payment system. It will be a payment application to be used by people, accessible through either a web-based management interface or a Proof of Concept (PoC) on mobile devices at Woven City. In order to develop the Flutter app efficiently in parallel with the backend development of the payment system, we decided to take the following steps.
- Design a Common Application Architecture
- Form a Design Policy for Lazy UI Components
- Define the Tech Stack Unification and Development Flow
Design a Common Application Architecture
There were various proposals for both backend and frontend architectures over the years, but I think it is best to pick one that suits the development team and product phase and improve along the way. We have adopted a clean architecture for backend development and applied an architecture for Flutter applications that uses only MVVM and repository patterns with a similar layer-first directory structure. To be specific, the directory structure is as follows.
Directory Configuration
lib/
├── presentations
│ ├── pages
│ │ └── home_page
│ │ ├── home_page.dart
│ │ ├── home_page_vm.dart
│ │ ├── home_page_state.dart
│ │ └── components
│ ├── components
│ ├── style.dart // a common style definition
│ └── app.dart
├── domains
│ ├── entities
│ └── repositories // repository interface
├── infrastructures
│ └── repositories
└── main.dart
Directory Roles
There are three main directories that make up the layer structure. Here are some brief descriptions of each directory's role.
| Directory | Layer | Role |
|---|---|---|
| presentations | Presentation Layer | Defines View, ViewModel, and if necessary, the states |
| domains | Domain Layer | Defines interfaces for domain models, logic, and repositories |
| infrastructures | Infrastructure Layer | Defines repository tier implementations, including those for API calls |
When designing with a layer pattern, you may want a use case layer, but there is currently very little business logic in the frontend, so we have included it in ViewModel.
The application we are developing does not have complex functions yet, and is basically page = one domain, so we are proceeding smoothly with this design. When creating a new app with a PoC, we usually start with this template so that there are no differences in architecture between applications.
Form a Design Policy for Lazy UI Components
When designing UI components, we decided not to adopt Atomic Design and to not make too many common components. There are some drawbacks, but we did it this way for the for the following reasons:
- It was difficult for all members to get the same sense of the levels in the Atomic Design classifications
- We wanted to focus on page implementation rather than making common components
- Most importantly, it takes a lot of energy to build an abstract widget with Flutter
I think that making common components is more common, but at this point in time, we are in the phase of making changes to the application while flexibly changing specifications, and we decided that it would be more beneficial to not standardize it much in the short term.
Define the Tech Stack Unification and Development Flow
Many different technologies for state management and screen transition frameworks have come and gone. Beginners get confused about which library to use because there is so much information. I have experienced it as well and can relate. So we decided to use the following tech stack across all applications.
Tech Stack
| Target | Library |
|---|---|
| State management and provider creation | riverpod |
| Model definition | freezed |
| Screen transition | go_router |
| API client | dio, openapi-genrator |
| Project management | melos |
:::message We are doing schema-driven development using OpenAPI and automatically generate frontend API clients using openapi-generator based on the OpenAPI schema yaml file created in during backend development. :::
We use Riverpod for the state management and provider creation. Riverpod's concept of providers is not well-suited for backend development, and since it is possible to implement the provider as you like by coding by hand, the implementation flow and application location are defined somewhat strictly.
- Make sure to use riverpod_generator to generate the provider
- The provider is used when binding the infrastructure layer repository to the domain layer interface
Future<HogeRepository> hogeRepository(HogeRepositoryRef ref) {
final apiClient = await ref.watch(openApiClientProvider);
return HogeRepositoryImpl(
apiClient: apiClient.getHogeApi(),
);
}
- The ViewModel is implemented with AsyncNotifierProvider, and the provider of the repository required by View is aggregated into ViewModel
class HogePageViewModel extends _$HogePageViewModel {
Future<List<Hoge>> build() {
return _fetchData();
}
Future<List<Hoge>> _fetchData() {
repository = ref.watch(hogeRepositoryProvider);
return repository.getList();
}
Future<void> registerData(Hoge hoge) {
repository = ref.watch(hogeRepositoryProvider);
return repository.register(hoge);
}
}
- View monitors the AsyncValue from the ViewModel and displays the UI. Alternatively, CRUD is processed to the repository via ViewModel
As mentioned above, we defined the process from repository implementation to embedding the UI and backend. Tickets are divided by particle size according to the process when making sprint tasks.
Conclusion
As the client application development priority rose in the project, we established a frontend development policy and came up with ways to develop smoothly as a team. Since many web management screens have a basic set with a list screen, details screen, and editing screen, we are also thinking about implementing measures to implement UI more efficiently using code generators in the future.
関連記事 | Related Posts
We are hiring!
生成AI・AIエージェントの業務実装(FDE/Deployment Strategist)/AIファーストG/東京・名古屋・大阪・福岡
概要トヨタグループの業務現場に入り込み、生成AI・AIエージェントを「使われ続ける状態」まで届ける仕事です。こうした働き方は近年、Forward Deployed Engineer(FDE)やDeployment Strategist(DS)と呼ばれる動き方に近いポジションで、案件の上流の課題定義から、プロトタイプ構築、経営層への提言までを担います。
【アプリケーションエンジニア】プラットフォームG/東京・大阪
プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。認知負荷の軽減を目的としたプロダクト開発、運用を企画・実行するグループになります。
Follow Us
KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。
