Flutter application development: feedback (1/2)
While developing the PeerTube application, we gained experience in choosing technologies and encountered obstacles created by certain decisions. We share these insights here.
Why flutter?
When it comes to developing mobile applications, the question of which technologies to use quickly arises: should we create a separate application for each platform, or adopt an approach that allows us to pool our efforts? This is where cross-platform development comes in: a method that involves creating a single code base for several operating systems, primarily Android and iOS.
Cross-platform development offers many advantages. First and foremost, it considerably reduces costs and maintenance efforts since only one code base is required, limiting the need for bug fixes and multiple updates. It also enables faster and wider deployment, reaching a broader audience without the need to develop separate applications.
As our developer was not initially specialized in mobile development, he had to train himself to master the necessary tools. Two main technologies emerged: React Native and Flutter.
We therefore conducted a comparative study to select the solution best suited to our needs. After analysis, we chose Flutter. You can consult the complete study for more details by following this link: https://framagit.org/wicklow/peertube-prototypes
After making this decision, our developer set about learning and mastering Flutter. He shares his experience in the rest of this article.
Learn Dart and Flutter
The first step was to understand how Flutter works. Flutter applications are written in Dart, an object-oriented programming language. I started by exploring the official Dart and Flutter documentation to grasp the fundamentals.
Here are the different resources I used to get started:
- The official Flutter documentation: An excellent official introduction to understanding the fundamental concepts of Flutter.
- Flutteris blog: Detailed articles and practical tutorials to deepen my knowledge.
- Code with Andrea: Guides and practical examples to help me structure and optimize my Flutter projects.
Choosing your architecture
Once I had established the fundamentals, I turned my attention to the ideal structure for the project. After exploring various approaches, I opted for a 'feature-first' architecture. This method involves organising code by functionality rather than by file type, such as models, views, or controllers.
The 'Feature First' approach offers several advantages. It brings greater clarity to the project by isolating each feature in its own folder, which makes the structure of the code easier to navigate and understand. Additionally, this method promotes modularity by making features independent, enabling them to be reused or modified without impacting other parts of the project. Finally, in the context of an open-source project, this organisation makes it easier for external developers to contribute, as they can focus on specific functionalities without interfering with the rest of the code.
Choosing dependencies
Each library integrated into the project must be reviewed to ensure that it meets the project's functional requirements, can be maintained over the long term, and does not introduce technical risks.
As Flutter is still a relatively new technology, caution should be exercised when selecting dependencies. Some libraries may lack maturity or community support, which could lead to bugs or issues with future updates.
The following are some general criteria for choosing a dependency on pub.dev:
- Check the number of people actively contributing to the project on GitHub. A larger team of contributors often indicates a more robust project in the long term.
- Ensure the project is active with recent commits and regular pull requests, ideally from different users.
- Look at the overall score on pub.dev, which measures package quality.
- Check the frequency of publications.
- Give preference to packages published by a verified publisher.
Selected dependencies
Taking these criteria into account, I carefully selected the libraries needed to develop the PeerTube mobile application.
The main libraries chosen for the project are listed below, along with the reasons for their selection.
The state manager
A state manager is a tool used to manage the state of an application. In the context of Flutter, state refers to dynamic data or information that can change during application execution, such as user input, data retrieved from an API, or the state of an animation.
There are several approaches and libraries are available for state management in Flutter applications, each with its own advantages and limitations.
State management approaches
StatefulWidget
: The simplest built-in mechanism for managing local state.InheritedWidget
: A native solution for sharing state between widgets.- Global variables: An approach where state is stored in global variables that can be accessed throughout the application.
Popular libraries
- Pros: Simple, lightweight and widely used by the Flutter community.
- Cons: Requires standard code and lacks advanced features.
Bloc:
- Pros: Highly structured, promotes separation of concerns and is ideal for managing complex application logic.
- Cons: Steep learning curve. Requires more code base than other solutions.
- Pros: A modern alternative to Provider with a simpler API, better testability and support for dependency injection. It removes Flutter's widget tree constraints, making it more flexible.
- Cons: It is a more recent library, but development is very active.
Riverpod was chosen for its simplicity, flexibility, and scalability, and it offers global state management that is independent of the widget tree.
I like Riverpod's global variable approach. In addition, integrating code generation into the project promises to improve performance in the future. However, it's important to choose a solution that matches your preferences and specific needs.
The router
A router is an essential component that manages navigation between the various screens of an application. It allows routes to be defined and transitions to be managed, and it supports deep links and the transmission of parameters via URLs.
Several libraries are available to manage navigation in Flutter applications:
- Navigator: Included in the Flutter SDK but lacks features such as deep links.
- AutoRoute: Generates code to simplify route management, but can be difficult to configure.
- GoRouter: An official library supported by the Flutter team, combining ease of use with advanced deep-linking support.
After analysis, GoRouter proved to be the best choice for this project, particularly because of:
- active support from the Flutter team and comprehensive documentation, guaranteeing a reliable long-term solution.
- Deep link support, which is essential for redirecting users between PeerTube platform web pages and the application.
- It is based on the Navigator 2.0 API included in the Flutter SDK.
The video player
The video player is an essential component of the PeerTube application, forming the heart of the user experience. From the very start, it was crucial for us to make the right choice of library to ensure smooth playback, compatibility with different video formats, and seamless integration with the application's functionality.
Several libraries are available that are used by the Flutter community to implement video playback functionality.
- video_player: An official library supported by the Flutter team that provides basic video playback functionality but requires extensive customisation for advanced features.
- Chewie: Is a powerful overlay for video_player, supported by the Flutter community, which provides a pre-built, highly customisable player interface and supports basic controls such as play/pause, full-screen mode, and subtitles.
- BetterPlayer: Is an advanced video player built on top of Chewie with additional functionality, but it is not very well maintained.
- MediaKit: Is a newer package that aims to provide a consistent, feature-rich video playback experience across all platforms. However, its ecosystem and community support are still developing.
Following our analysis, Chewie emerged as our top choice thanks to its balance of simplicity and functionality.
- It is easy to integrate and provides a ready-to-use player interface with minimal configuration.
- It is also customizable, allowing developers to adapt the user interface and behavior to the needs of the application.
- It is maintained by the Flutter community, which guarantees reliability.
However, after several months of use, we found that Chewie's maintenance was weaker than expected. Some features and bug fixes contained in merge requests were not being integrated, which limited our ability to respond quickly to the application's needs.
Consequently, we decided to migrate to video_player. Although implementing a customized user interface and advanced functionality takes more time with this library, it offers more granular control and greater stability. This transition has enabled us to design a customised user experience while guaranteeing greater long-term reliability.

Flavors
We needed two distinct applications:
- stable: The production version intended for deployment in public stores.
- nightly: The version incorporating the latest changes, based on the development branch.
Flavors make it possible to manage these two versions separately by creating two applications. To set this up, you need to configure Flavors on each targeted native platform (Android and iOS). Additionally, each application must be signed separately for each flavour. Finally, the Dart code shared by all platforms can be configured according to the environment using the --dart-define-from-file
argument, which provides a .env
file containing the necessary variables.
The example command for building the stable environment is:
flutter build apk --flavor stable --dart-define-from-file=env-stable.json
For more details on configuring flavours and signing applications, see this report, which details how we set up the flavour system.
Mistakes made
Writing tests too early
One mistake I made at the start of the project was trying to write unit and integration tests too early in the development process. While tests are crucial for ensuring code quality and stability, writing them prematurely can be counterproductive, particularly when the application's architecture and functionality are still in the process of being defined. Indeed, many changes were made to the code structure and functionalities at the start of the project, quickly rendering the tests obsolete.
From this experience, I learned that the stability of the architecture must be prioritised. Before writing tests, it is crucial to ensure that the application architecture is well defined and stable. This reduces the need for frequent test modifications.
Underestimating the complexity of dependencies
Initially, we integrated several libraries without fully assessing their maturity and compatibility with our project. Some of these dependencies turned out to be unstable or poorly maintained, which led to unforeseen problems and delays. A more thorough dependency analysis would have prevented these issues.
This Flutter learning path has enabled me to lay a solid foundation for developing the PeerTube mobile application.
In the next article, we will look at a crucial stage: publishing the application on the stores (Google Play, the App Store, and F-Droid). We'll detail the necessary steps, best practices, and potential pitfalls to help you successfully launch a mobile application like PeerTube.
We would like to take this opportunity to remind you that you can participate to the crowdfunding campaign for the development of the PeerTube application until 17 June 2025!