Things You Should Know When Developing with Flutter
・ 40 min
Below is a compilation of questions you'd likely encounter as a Flutter developer.
I used AI to help organize the answers, so take them as a reference!
The Difference Between StatelessWidget and StatefulWidget#
StatelessWidget and StatefulWidget are the two main widget types used to build UI in Flutter. The biggest difference between them is "can the internal state change?"
At a Glance
Category
StatelessWidget
StatefulWidget
State management
Not possible
Possible
Screen changes
Can't change directly
Can change with setState
Use cases
Text, icons, logos, etc.
Counters, checkboxes, animations, etc.
Analogy
StatelessWidget is like a "photo in a frame." Once you put it in, the content doesn't change.
StatefulWidget is like a "whiteboard." You can erase and rewrite anytime, so it can change depending on the situation.
Code Examples
StatelessWidget Example
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Text('This is text that never changes!'); }}
BuildContext is an object in Flutter that tells a widget where it's located within the widget tree.
Simply put, it's like an "address" that lets a widget know its position and surrounding environment.
Analogy
A Flutter app is a tree structure where many widgets are connected like branches. BuildContext is like a location pin on each widget saying "I'm right here" in the tree.
This location marker is needed so a widget can find information from parent widgets (e.g., theme colors, screen size) or get data from other widgets in the app.
In other words, BuildContext is both an address that tells the widget "where am I" and a navigator that helps it find the information it needs.
Code Example
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { // Use BuildContext to get the theme color final primaryColor = Theme.of(context).primaryColor; // Can also get the screen size final screenWidth = MediaQuery.of(context).size.width; return Center( child: Text( 'Screen width is $screenWidth', style: TextStyle(color: primaryColor, fontSize: 20), ), ); }}
In this code, context is used to find parent widgets (Theme, MediaQuery, etc.) from the current widget's location and retrieve information from them.
Summary
BuildContext is an object that tells you a widget's location and surrounding information.
It lets you easily access parent widget data and app environment info.
Think of it like a "location pin" that tells the widget where it is and helps it find what it needs.
Similarities and Differences Between Future and Stream#
Both Future and Stream are objects used for handling data asynchronously. However, they differ slightly in how they work and what they're used for.
Similarities
Both help you wait for time-consuming tasks (e.g., network requests, file reads) and process the results.
Both are widely used in asynchronous programming.
You can use keywords like async and await with both to receive results.
Differences
Category
Future
Stream
Result count
Receives a result only once
Receives results multiple times, continuously
Example
Receiving a package delivery
Listening to radio, receiving chat messages
Usage
await, then()
await for, listen()
Future is like "receiving a package" -- the result comes once and that's it.
Stream is like "listening to the radio" -- data can keep coming in multiple times.
Code Examples
Future Example
Future<String> fetchData() async { await Future.delayed(Duration(seconds: 2)); return 'Data arrived!';}void main() async { print('Request sent'); String result = await fetchData(); print(result); // Prints 'Data arrived!' after 2 seconds}
Stream Example
Stream<int> numberStream() async* { for (int i = 1; i <= 3; i++) { await Future.delayed(Duration(seconds: 1)); yield i; }}void main() async { await for (int number in numberStream()) { print('Number: $number'); } // Prints Number: 1, Number: 2, Number: 3 every second}
Analogy
Future is "receiving a package." Once the package arrives, that's it.
Stream is "listening to the radio." Just as the broadcast keeps flowing, data can come in multiple times.
In summary:
Use Future when you only need a result once.
Use Stream when data comes in repeatedly and continuously!
When you use setState, you're telling Flutter that the StatefulWidget's state has changed.
Upon receiving this signal, Flutter re-executes the widget's build method so the changed state is reflected on screen.
Analogy
setState is like erasing what's on a whiteboard and drawing something new.
If the number 0 is written on a whiteboard and someone wants to change it to 1 by pressing a button, you use setState to erase the 0 and write 1 instead.
This way, others (users) can see the updated number.
When you press the button in this code, setState is called and the count value increases by 1.
Thanks to setState, Flutter re-runs the build method, and the updated number is immediately displayed on screen.
Summary
Using setState tells Flutter that the state has changed.
Flutter re-runs the build method to render the changes on screen.
Think of it as erasing a whiteboard and writing new content.
If you don't use setState, even if the value changes, nothing will change on screen!
What Are Design Patterns Like Clean Architecture, MVVM, and DI?#
You can apply design patterns like Clean Architecture, MVVM, and DI in Flutter.
Clean Architecture
Analogy: Building blueprints
Blueprints (Domain): Pure business logic (Dart only)
Higher code reusability by separating UI and logic
DI (Dependency Injection)
Analogy: Replacing car parts
Injecting the engine (dependency) from outside -> Easy part replacement
Example (get_it package)
// 1. Register dependenciesfinal getIt = GetIt.instance;void setup() { getIt.registerSingleton<ApiService>(ApiService());}// 2. Use injected dependencyclass UserRepository { final ApiService api = getIt<ApiService>(); // Injected from outside}
Benefits:
Separating object creation and usage -> Easy testing
Minimal code changes when swapping modules
Pattern Application Tips
Small apps: MVVM + Provider
Large apps: Clean Architecture + DI
Common principles:
"Separation of concerns" (each layer has an independent role)
"Dependency inversion" (depend on abstractions)
Recommendation: Start with MVVM -> Gradually expand!
As your app grows, design patterns significantly improve maintainability.
Firebase Auth, Storage, and Dynamic Links
Using Firebase, you can quickly implement core features your app needs without building complex backend functionality yourself.
Auth, Storage, and Dynamic Links in particular are must-have features when developing Flutter apps.
**Firebase Auth (Authentication)**
Analogy: An apartment key card
With a key card (authentication), you can safely use all the facilities (features) of the apartment (app).
Why do you need it?
You can implement sign-up/login in just 10 minutes.
It provides various login methods including email, Google, Facebook, phone number, etc.
Firebase handles security management for you, reducing development burden.
With just one address (link), when the recipient (user) encounters the delivery driver (app install check), they're automatically delivered to the right door (specific screen).
Why do you need it?
Navigate to a specific screen regardless of whether the app is installed.
(e.g., Event page -> App install -> Go directly to event screen)
Essential for marketing and sharing features.
Analytics let you track link clicks and app install rates.
Flutter Code Example
// 1. Create a link (server-side)// Create via REST API (e.g., https://myapp.page.link/share?event=summer_sale)// 2. Handle link in the appFirebaseDynamicLinks.instance.onLink.listen((dynamicLink) { Navigator.pushNamed(context, dynamicLink.link.path); // Navigate to event screen});
**Overall Summary**
Feature
Key Benefits
Analogy
Auth
90% time savings on login dev + automated security
Apartment key card
Storage
Large file management + DB performance protection
Cloud USB drive
Dynamic Links
Connect to desired screen regardless of install
Smart mail
Why I recommend it:
Firebase lets you implement complex features in under 1 hour without backend development knowledge:
User management (Auth)
File storage (Storage)
Sharing features (Dynamic Links)
App Performance Optimization and Code Refactoring
Let me explain actual approaches and examples for app performance optimization and code refactoring.
Code Refactoring: "Building with LEGO"
Analogy: Modularizing duplicate LEGO blocks makes assembly easier!
Problem: 3 buttons = 50 lines of code -> Poor readability, hard to maintain
Solution: Extract common UI into a CustomButton class
// StatefulWidget -> StatelessWidget + ProviderConsumer<DateModel>( builder: (context, model, child) => Text('${model.selectedDate}'), // Only updates the changed part)
Result:
Frame rate improved from 30fps to 60fps
Battery consumption reduced by 20%
Tips:
Start with "widget splitting" (class extraction)
Add const wherever possible
Long lists always use ListView.builder!
Refactoring and optimization solve both maintenance convenience and user experience at the same time.
Git Commit Conventions and the Difference Between Rebase, Merge, and Squash
Git Commit Conventions
Analogy:
Commit messages are like a table of contents in a book.
Just as a well-organized table of contents lets you quickly understand the book's content, clear commit messages make code history easy to understand.
Examples
feat: Implement login screen UI
fix: Fix email duplicate check error during sign-up
refactor: Separate schedule management logic code
docs: Add usage instructions to README
feat: New feature addition
fix: Bug fix
refactor: Code structure improvement
docs: Documentation changes
When you agree on and follow clear rules like these with your team, collaboration becomes much easier.
Difference Between Rebase, Merge, and Squash
Feature
Analogy
Description
Merge
Where two rivers meet
Combines work from two branches, leaving a trace (merge commit).
Rebase
Reordering diary entries
Replays your commits on top of the latest (main) branch, making the history clean.
Squash
Combining multiple photos into one
Combines multiple commits into one, reducing unnecessary commits and keeping records concise.
Examples
Merge
git checkout maingit merge feature/login# All commits from feature/login are merged into main, and a merge commit is created.
Rebase
git checkout feature/logingit rebase main# Commits from feature/login are replayed after the latest commit on main.
Squash
git rebase -i HEAD~3# You can combine the last 3 commits into one.
Summary
Following commit conventions makes it easy to track code change history during collaboration.
Merge leaves a trace of the merge, while Rebase keeps history clean.
Squash combines multiple commits into one for simpler records.
In team projects, it's common to Squash commits when sending a PR after feature development, and use Merge or Rebase on the main branch depending on the situation!
How to Collaborate with Planners and Designers, and How to Reflect Developer Opinions
Collaboration Analogy
Analogy:
App planning is similar to creating a cooking recipe.
The planner decides what dish to make, the designer thinks about plating. The developer actually has to cook, so they need to speak up about ingredients (tech) and cooking methods (feasibility).
Real Example
Situation:
When adding a new schedule registration feature to the app,
the planner wanted "date, time, and repeat options all on one screen,"
and the designer proposed "a design showing all input fields at once."
Developer's opinion:
Putting all input fields on one screen would:
Make the screen too complex
Increase input mistakes on mobile
So I suggested "step-by-step input (screen transitions per step)."
How the opinion was incorporated:
Prototype demo:
I quickly built a step-by-step input UI and showed it to the team.
Explaining pros and cons:
I explained that step-by-step input allows users to fill in without mistakes, and is easier to develop.
Final decision:
After the team tried it themselves, they concluded "step-by-step input is more user-friendly," and the developer's opinion was reflected in the final plan.
Summary
Developers should share opinions considering technical limitations and user experience.
Quick prototypes or demos make your suggestions more persuasive.
It's important to communicate with team members and coordinate ideas to reach the best outcome.
When you collaborate like this, you can build an app that everyone's happy with!
How to Share Your Problem-Solving Process
Analogy
Analogy:
When you discover a new recipe, if you keep it to yourself, you might make the same mistake again next time. But if you share the recipe, everyone can avoid repeating the same mistakes and make even better dishes.
Real Example
Situation:
The app was slowing down due to an image caching issue in Flutter.
Multiple images were loading fresh every time, degrading user experience.
Problem solved:
I found a solution using the cached_network_image package to cache images.
How I shared it:
Wiki documentation:
I posted "How to Apply Image Caching" step by step on the company wiki.
Code examples attached:
I included actual code with usage explanations like below:
Slack/group chat notification:
I shared the link and summary in the team chat so new team members could easily find it.
Study/seminar:
I did a short screen-share session showing the actual application process, with Q&A.
Summary
Sharing what you've learned through documentation, chat, seminars, and other methods elevates the entire team's skills.
Including actual code and examples makes understanding much easier.
By sharing like this, everyone avoids repeating the same mistakes and can grow faster!
How to Implement a Community or SNS App
Analogy
Analogy:
Running a community service is similar to being a "club president."
The president makes club rules, organizes events so members can communicate well, and steps in to solve problems.
A service operator does the same -- creating an environment where users can comfortably participate and managing the service to keep it running smoothly.
Roles and Results in Practice
1. Roles
Service design and planning:
You need to design core SNS features like user profiles, posts, comments, likes, and follows
Establishing operational policies:
Create community usage rules, introduce report/block systems to foster a safe environment
Event planning and execution:
Plan and run various online events like photo contests and comment events to boost user engagement
Real-time feedback and improvements:
Quickly identify user inquiries and complaints, improve pain points, and add new features
When a user uploads a post, it's safely stored on the server and reflected in the feed in real time.
Summary
Running a community/SNS service is like being a "club president" -- managing everything and helping users enjoy their activities.
In practice, I experienced service planning, policy creation, event operations, and real-time feedback.
Through these experiences, I was able to drive both user satisfaction and service growth!
How to Collaborate with Backend Developers
Let me explain common problems and solutions from the collaboration process using analogies and real examples.
Collaboration Analogy
Analogy:
Collaborating with backend developers is like the teamwork between a "head chef and a server."
When the chef prepares a dish, the server must deliver it correctly to the customer.
If the dish name the chef uses doesn't match what the server knows, the wrong food might end up at the table.
That's why it's crucial to communicate and align on menu names and ordering methods.
Real Collaboration Process and Problem Resolution
1. Problem: API spec mismatch
The API documentation provided by the backend didn't match actual behavior,
Causing errors when the app tried to fetch data.
2. Solution
Regular meetings and document sharing
Through short weekly meetings, we shared API changes and documented API specs using tools like Swagger or Postman.
Using a Mock server
Before backend development was complete, we created a temporary Mock server to develop the frontend in parallel.
Sharing error logs
By forwarding app error logs to the backend team, we enabled quick root cause identification and fixes.
Flutter API Call Example
Future<User> fetchUser() async { final response = await http.get(Uri.parse('https://api.example.com/user/123')); if (response.statusCode == 200) { return User.fromJson(jsonDecode(response.body)); } else { throw Exception('Failed to load user info'); }}
When an API changes, URLs or response data structures change too,
So it's crucial that backend and frontend share the same documentation and test environment.
Summary
Collaborating with backend developers comes down to clear communication about roles and expectations.
We solved problems through API documentation, regular meetings, Mock servers, and error log sharing.
Thanks to this collaborative approach, both development speed and quality improved significantly!
Explaining Technical Issues to Non-Technical Team Members
Analogy
Analogy:
Explaining a technical issue is like putting together a complex puzzle as a team.
First show the complete puzzle picture (big picture), then explain where each piece (detailed feature) fits.
Even someone seeing the puzzle for the first time can then understand the overall structure and where the problem is.
Real Example
Situation
The app's login speed had slowed down.
The planner and designer were curious about "why it got so slow" and "how it could be fixed."
Explanation Approach
Start with the big picture
"Currently, multiple server communications happen during the login process.
That's why the app doesn't respond immediately when users press the button."
Use simple language and analogies
"It's like receiving a package that has to go through multiple warehouses.
The more warehouses, the later the delivery.
Our app also takes longer to log in because it goes through the server multiple times."
Break down the problem step by step
"The login process can be broken into:
User enters information
Information sent to server
Server verifies and responds
Steps 2 and 3 are where the delays are happening."
Use visual aids
I drew a simple diagram on a whiteboard
showing exactly where delays were occurring at each step.
Suggest a solution direction
"If we reduce the number of server communications,
it's like the package going through fewer warehouses,
so login speed should improve."
Summary
I used analogies and simple language to break down technical jargon.
I broke the problem into steps and showed it at a glance with visual aids.
Explaining this way helps non-technical team members easily understand the cause and solution.
Flutter Rendering Pipeline
Flutter's rendering pipeline is the multi-step process from code-written widgets to what actually appears on screen.
Let me explain with analogies, real examples, and organized notes.
Analogy
Flutter rendering is similar to constructing a building.
Draw blueprints (Widget Tree)
Build the frame (Element Tree)
Lay bricks and paint (Render Tree & Paint)
Finally show the completed building (screen)
Actual Rendering Structure and Steps
Flutter draws screens through three trees and four stages.
1. Widget Tree
Drawing the blueprints
Code written by the developer (Text, Container, etc.) stacks up in a hierarchical structure.
Example:
Container( child: Text('Hello World'))
The above creates a widget tree like:
Container
└─ Text('Hello World')
2. Element Tree
Building the actual frame
The "actual instances" of each widget are created.
When a widget changes, the element tree efficiently updates only the relevant parts.
3. Render Object Tree
Laying bricks and painting
Handles visual information like position, size, and color for how each element appears on screen.
This is the stage where preparation for actual screen rendering happens.
The 4 Rendering Stages
Stage
Description
Layout
Calculates the size and position of each widget, parent to child.
Paint
Each widget draws on its own canvas.
Compositing
Combines multiple layers to prepare the final screen.
Rasterization
The GPU converts layers to pixels and displays them on screen.
Render Tree: Visual information like position, size, color of Container and Text
The 4 rendering stages to display 'Hello Flutter!' on a blue background
Summary
Flutter draws screens through Widget Tree -> Element Tree -> Render Tree
followed by Layout -> Paint -> Compositing -> Rasterization.
Think of it as: Blueprints -> Frame -> Bricks & Paint -> Completed Building.
This architecture is what allows Flutter to update screens quickly and efficiently!
Flutter Architecture
Flutter's architecture can be broadly divided into three main layers.
Each layer can be compared to a building's foundation, internal frame, and interior.
Flutter's 3 Main Layers
Layer
Description
Analogy
Embedder
Connects to the OS, handles display and input
Building foundation
Engine
Implements core features: rendering, animation, text, etc.
Building frame
Framework
Dart libraries, widgets, etc. that developers primarily use
Interior, rooms & furniture
1. Embedder
Directly connects to the operating system to draw on screen and receive touch input.
Written in platform-specific languages (e.g., Java/C++ for Android, Swift/Objective-C for iOS).
Think of it as the foundation that anchors the building to the ground.
2. Engine
Mostly written in C++, it handles core features like actual rendering, animation, text processing, and networking.
Flutter's core APIs and the Dart runtime are also included in this layer.
Like the building's frame -- the central structure that makes the app work properly.
3. Framework
Written in Dart, this is the part developers primarily work with.
Provides high-level features like various widgets, layouts, animations, and gestures.
This is the interior, rooms, and furniture. Developers design the app's appearance and build features in this layer.
Here, MaterialApp, Scaffold, AppBar, Text, ElevatedButton are all widgets.
Widgets are like modular blocks that you stack to build the app's UI.
Understanding Through Analogy
Embedder: Foundation work that anchors the building to the ground
Engine: Building frame (pillars, walls, etc.)
Framework: Rooms, furniture, interior (the space people actually live in)
Widgets: Individual furniture and decor items (chairs, tables, lights, etc.)
App developer: Interior designer (arranges furniture and decorates)
Key Takeaways
Flutter apps use widgets to build screens, and these widgets live in the Framework layer.
For the app to work, the Engine draws on screen, and the Embedder connects to the OS.
Each layer is independent but depends on the layer below it.
In short, Flutter's architecture is like building construction -- lay the foundation (Embedder), build the frame (Engine), then furnish and decorate (Framework, Widgets).
Dart Syntax
Dart is a language created to work with Flutter, and it's been getting lots of specialized features and new capabilities recently.
Let me explain with real examples and analogies.
Dart Language Specializations
1. Cross-Platform Compatibility
Dart is designed so that code written once can run on web, mobile, desktop, server, and more.
It's like having one LEGO set that can build a house, car, or airplane.
2. Concise and Safe Syntax
Thanks to Null Safety, you can prevent errors caused by null (no value) in advance.
Think of it like seatbelts installed by default in a car to prevent accidents.
3. Fast Development with Hot Reload
When used with Flutter, modifying code is instantly reflected in the app thanks to Hot Reload.
It feels like changing a color while painting and seeing it immediately on the canvas.
New Technologies and Recent Additions in Dart
Feature/Technology
Description and Examples
Records
A data structure that can return multiple values at once.Example: var user = ('Hong Gildong', 20);Analogy: Like putting apples and bananas in one box and passing them together.
Pattern Matching
Easily decompose and inspect value structures.Example: switch (user) { case ('Hong Gildong', int age): ... }Analogy: Like an X-ray that checks package contents without unwrapping.
Sealed Classes
Restrict inheritance to create predictable type hierarchies.Analogy: Like a restricted-access space where only certain classes can enter.
Enhanced Enums
Add methods and properties to enums.Example: enum Color { red, blue; String get label => 'color'; }Analogy: Like adding instructions and usage guides to paint that previously only had color names.
Null-aware Elements
Automatically add only non-null values to collections.Example: var list = [1, null, if (user != null) user];Analogy: A production line that automatically filters out defects.
Wildcard Variables
Declare _ multiple times to ignore unnecessary values.Analogy: Like tossing aside unneeded ingredients while cooking.
Digit Separators
Add _ to numbers for readability.Example: var big = 1_000_000;Analogy: Like separating a long phone number with hyphens.
Extension Types
Wrap existing types to use them as new types.Example: extension type Meters(int value) { ... }Analogy: Like putting a new design cover on existing clothes to make them look different.
Code Examples
// Records example(String, int) getUser() { return ('Hong Gildong', 20);}void main() { var (name, age) = getUser(); print('Name: $name, Age: $age');}// Pattern Matching examplevoid printUser(dynamic user) { switch (user) { case ('Hong Gildong', int age): print('Hong Gildong is $age years old.'); break; default: print('Unknown user.'); }}
Understanding Through Analogy
Dart's Records are like a lunch box that holds multiple pieces of information at once.
Pattern Matching is like a transparent box that lets you see inside without opening the wrapping.
Null Safety is like a seatbelt that prevents accidents from mistakes.
Hot Reload is like a magic brush that applies changes to your painting in real time.
Dart continues to introduce new technologies and specialized features that support easy, safe, and fast development.
If you break your neck, if you have nothing to eat, if your house is on fire, then you got a problem. Everything else is inconvenience.