This is a conference app for Open Conf 2025 Flutter workshop
This project is intentionally structured poorly to serve as an educational example of what not to do in a Flutter application. It is designed to highlight common pitfalls that new developers might fall into and to demonstrate the importance of a well-organized project structure.
DO NOT use this project as a template for your own applications.
*** You can grab the "The Better Structure Example"
This is a simple conference application that displays information about:
- A home screen with event details
- A list of speakers
- The event agenda
- Conference sponsors
The app fetches data from local JSON files and displays it in various screens.
As a project grows, its structure becomes crucial for maintainability, scalability, and collaboration. This project violates several best practices:
All Dart files (main.dart, home.dart, speakers.dart, agenda.dart, sponsors.dart, speaker.dart, etc.) are located directly inside the lib/ directory.
- Problem: This is manageable for a handful of files, but as the app grows to 20, 50, or 100+ files, it becomes incredibly difficult to find what you're looking for.
- Better Approach: Group files by feature (e.g.,
lib/speakers/,lib/agenda/) or by layer (e.g.,lib/models/,lib/ui/screens/,lib/data/services/).
Each screen file (e.g., speakers.dart) contains everything related to that feature:
-
The data model (
Speakerclass) -
The UI widget (
SpeakersPage) -
The data fetching logic (
_getSpeakers()) -
State management (
setState) -
Problem: This tight coupling makes the code hard to read, test, and reuse. If you wanted to fetch speakers in another part of the app, you would have to duplicate the code. Testing the data logic requires rendering the entire UI.
-
Better Approach: Separate these concerns into different layers. For example:
- Data Layer: A
servicesorrepositoriesdirectory to handle fetching data from APIs or local sources. - Model Layer: A
modelsdirectory for your data structures. - UI Layer: A
uiorpresentationdirectory for your widgets, separated intoscreensand reusablewidgets.
- Data Layer: A
The app exclusively uses StatefulWidget and setState() to manage its state.
- Problem: While
setState()is fine for simple, local state (like a checkbox being ticked), it becomes cumbersome for managing app-wide or complex state. It requires passing data down the widget tree, leading to rebuilds of widgets that don't need to change. - Better Approach: Use a dedicated state management solution like Provider, BLoC, Riverpod, or others. These solutions provide a more robust and scalable way to manage and access your application's state.
Styling and widgets are often duplicated. For example, the Card and ListTile styling in speakers.dart and agenda.dart could be extracted into reusable widgets.
- Problem: Duplicated code is harder to maintain. If you need to change a color or font size, you have to find and update it in multiple places.
- Better Approach: Identify repeating UI patterns and extract them into their own reusable widgets. For example, you could create a
StyledCardorSpeakerListItemwidget.
A much better, layered architecture would look something like this: https://github.com/SimpleAppsgr/flutter_skeleton