Things You Should Know When Developing with Flutter

 ・ 40 min

photo by Dong Xie(https://unsplash.com/@chuchongju?utm_source=templater_proxy&utm_medium=referral) on Unsplash

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!');
  }
}

StatefulWidget Example

class MyStatefulWidget extends StatefulWidget {
  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
 
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int counter = 0;
 
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Button press count: $counter'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              counter++;
            });
          },
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Summary

  • Use StatelessWidget when showing screens that don't change or fixed information.
  • Use StatefulWidget when the screen needs to change based on user interaction like button clicks or checkboxes.

BuildContext#

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!

The Lifecycle of State in StatefulWidget#

The State lifecycle of a StatefulWidget is the process a widget goes through from creation to destruction.

Lifecycle Stages Explained

  1. createState()

    • Called only once when the StatefulWidget is created.
    • Its role is to create the State object.
    class MyWidget extends StatefulWidget {
      @override
      _MyWidgetState createState() => _MyWidgetState(); // Create State
    }
  2. mounted = true

    • When the State is connected to the widget tree, mounted is set to true.
    • Now setState() calls become possible.
  3. initState()

    • Runs only once during State initialization.
    • Used for initial setup like API calls, registering listeners.
    @override
    void initState() {
      super.initState();
      _fetchData(); // Initial data loading
    }
  4. didChangeDependencies()

    • Called right after initState() or when dependencies change.
    • Example: Detecting changes in parent widget data.
  5. build()

    • The most important method that constructs the UI.
    • Called repeatedly when state changes and returns a widget.
    @override
    Widget build(BuildContext context) {
      return Text('Current count: $_count');
    }
  6. didUpdateWidget()

    • Called when the parent widget updates and triggers a rebuild.
    • You can compare the old widget with the new one.
  7. setState()

    • The trigger that notifies the framework of state changes.
    ElevatedButton(
      onPressed: () {
        setState(() => _count++); // State change + UI update
      },
    )
  8. deactivate()

    • Called when a widget is temporarily removed from the tree.
    • A typical example is a list item being dismissed by swiping.
  9. dispose()

    • Called when the State is permanently destroyed.
    • Performs cleanup work like unregistering listeners and disposing controllers.
    @override
    void dispose() {
      _animationController.dispose(); // Release animation resources
      super.dispose();
    }
  10. mounted = false

    • After the State is completely removed, mounted is set to false.
    • Calling setState() now will throw an error.

Understanding Through Analogy

The State lifecycle is similar to a human life.

  • Birth: createState() (a baby is born)
  • Childhood: initState() (early education)
  • Growth: build() repeated (continuous growth)
  • Change: didUpdateWidget() (career/life changes)
  • Death: dispose() (wrapping up life)

Lifecycle Flow Example

class CounterWidget extends StatefulWidget {
  @override
  _CounterWidgetState createState() => _CounterWidgetState();
}
 
class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;
 
  @override
  void initState() {
    super.initState();
    print('initState: Initialization');
  }
 
  @override
  void dispose() {
    print('dispose: Cleanup');
    super.dispose();
  }
 
  @override
  Widget build(BuildContext context) {
    print('build: UI reconstruction');
    return Column(
      children: [
        Text('$_count'),
        ElevatedButton(
          onPressed: () => setState(() => _count++),
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Key Takeaways

  • Initialization: createState() -> initState()
  • Active: build() (repeated) + setState()
  • Termination: deactivate() -> dispose()
  • Important: You must release resources in dispose()!

Understanding the lifecycle helps you prevent memory leaks and unexpected behavior.

What Happens When You Use SetState?#

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.

Code Example

class CounterWidget extends StatefulWidget {
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}
 
class _CounterWidgetState extends State<CounterWidget> {
  int count = 0;
 
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Current number: $count'),
        ElevatedButton(
          onPressed: () {
            setState(() {
              count++; // Increment the number by 1
            });
          },
          child: Text('Increment'),
        ),
      ],
    );
  }
}

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)
  • Materials (Data): External data (API, DB)
  • Interior (UI): Screen composition (Flutter widgets)

Example

// Domain layer (business logic)
class User {
  final String name;
  User(this.name);
}
 
// Data layer (API integration)
class UserRepository {
  Future<User> fetchUser() async {
    // API call code
  }
}
 
// UI layer (screen)
class UserScreen extends StatelessWidget {
  final UserRepository repository;
  UserScreen(this.repository); // DI injection
 
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: repository.fetchUser(),
      builder: (_, snapshot) => Text(snapshot.data!.name),
    );
  }
}

Benefits:

  • Easy maintenance through layer separation
  • Easy testing (domain is Flutter-independent)

MVVM (Model-View-ViewModel)

Analogy: A restaurant

  • Kitchen (Model): Data processing
  • Server (ViewModel): Connects kitchen to dining area
  • Dining Area (View): Customer interface

Example (Using Provider)

// Model
class CounterModel {
  int count = 0;
}
 
// ViewModel
class CounterViewModel extends ChangeNotifier {
  final CounterModel _model;
  CounterViewModel(this._model);
 
  void increment() {
    _model.count++;
    notifyListeners(); // Refresh UI
  }
}
 
// View
class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final viewModel = Provider.of<CounterViewModel>(context);
    return Column(
      children: [
        Text('${viewModel.count}'),
        ElevatedButton(
          onPressed: viewModel.increment,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Benefits:

  • 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 dependencies
final getIt = GetIt.instance;
void setup() {
  getIt.registerSingleton<ApiService>(ApiService());
}
 
// 2. Use injected dependency
class 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

  1. Small apps: MVVM + Provider
  2. Large apps: Clean Architecture + DI
  3. 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.

Flutter Code Example

// 1. Initialize Firebase
await Firebase.initializeApp();
 
// 2. Google Sign-In
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
final GoogleSignInAuthentication googleAuth = await googleUser!.authentication;
final credential = GoogleAuthProvider.credential(
  accessToken: googleAuth.accessToken,
  idToken: googleAuth.idToken,
);
await FirebaseAuth.instance.signInWithCredential(credential); // Login complete!

Firebase Storage (File Storage)

Analogy: A cloud USB drive

  • You can safely store photos and videos on a USB (Storage) and pull them out whenever you need them.

Why do you need it?

  • You can easily upload/download user profile photos, post images, etc.
  • Protects DB performance: Storing large files in Storage instead of the DB prevents DB slowdowns.
  • CDN support allows fast file loading from anywhere in the world.

Flutter Code Example

// 1. Upload a file
final file = File('image.jpg');
final storageRef = FirebaseStorage.instance.ref().child('users/${user.uid}/profile.jpg');
await storageRef.putFile(file);
 
// 2. Download a file
final url = await storageRef.getDownloadURL();
Image.network(url); // Display image

Firebase Dynamic Links (Deep Links)

Analogy: Smart mail

  • 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 app
FirebaseDynamicLinks.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

Before vs After Refactoring

// Before refactoring (duplicate code)
ElevatedButton(
  style: ElevatedButton.styleFrom(primary: Colors.blue),
  onPressed: () {},
  child: Row(children: [
    Image.asset('assets/mail.png'),
    Text('Login with Email'),
  ]),
)
 
// After refactoring (class extraction)
class CustomButton extends StatelessWidget {
  final String text;
  final String iconPath;
 
  const CustomButton({required this.text, this.iconPath});
 
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(primary: Colors.blue),
      onPressed: () {},
      child: Row(children: [
        if (iconPath != null) Image.asset(iconPath),
        Text(text),
      ]),
    );
  }
}
 
// Usage example
CustomButton(text: 'Login', iconPath: 'assets/mail.png')

Benefits:

  • Code reduced from 50 lines to 10 lines
  • Only one place to modify when changing button design

Performance Optimization: "Lightweight Backpacking"

Analogy: Dropping unnecessary baggage makes your app faster!

1. Using const

// Before optimization
Text('Hello'); // Creates a new widget every time
 
// After optimization
const Text('Hello'); // Fixed at compile time

Effect: Prevents widget recreation -> Lower CPU usage

2. Using ListView.builder

ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(
    title: Text('Item $index'),
  ),
)

Effect: Only renders visible items while scrolling -> 70% less memory

3. Image Caching

CachedNetworkImage(
  imageUrl: 'https://example.com/image.jpg',
  placeholder: (context, url) => CircularProgressIndicator(),
)

Effect: Fewer network requests, faster loading

Real-World Case: Schedule Management App

  • Problem: Screen stuttering when changing dates
  • Cause: Full widget rebuild
  • Solution:
    // StatefulWidget -> StatelessWidget + Provider
    Consumer<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:

  1. Start with "widget splitting" (class extraction)
  2. Add const wherever possible
  3. 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 main
    git merge feature/login
    # All commits from feature/login are merged into main, and a merge commit is created.
  • Rebase

    git checkout feature/login
    git 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:

    1. Prototype demo:
      I quickly built a step-by-step input UI and showed it to the team.
    2. Explaining pros and cons:
      I explained that step-by-step input allows users to fill in without mistakes, and is easier to develop.
    3. 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:

    1. Wiki documentation:
      I posted "How to Apply Image Caching" step by step on the company wiki.
    2. Code examples attached:
      I included actual code with usage explanations like below:
      CachedNetworkImage(
        imageUrl: 'https://example.com/image.jpg',
        placeholder: (context, url) => CircularProgressIndicator(),
      )
    3. Slack/group chat notification:
      I shared the link and summary in the team chat so new team members could easily find it.
    4. 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

2. Flutter Code Example

// Post upload feature example
Future<void> uploadPost(String content, String imageUrl) async {
  await FirebaseFirestore.instance.collection('posts').add({
    'content': content,
    'imageUrl': imageUrl,
    'createdAt': Timestamp.now(),
    'userId': FirebaseAuth.instance.currentUser!.uid,
  });
}
  • 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

  1. 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."
  2. 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."
  3. Break down the problem step by step

    • "The login process can be broken into:
      1. User enters information
      2. Information sent to server
      3. Server verifies and responds
        Steps 2 and 3 are where the delays are happening."
  4. Use visual aids

    • I drew a simple diagram on a whiteboard
      showing exactly where delays were occurring at each step.
  5. 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.

  1. Draw blueprints (Widget Tree)
  2. Build the frame (Element Tree)
  3. Lay bricks and paint (Render Tree & Paint)
  4. 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.

Code Example

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          color: Colors.blue,
          child: Text('Hello Flutter!'),
        ),
      ),
    );
  }
}
  • This code goes through:
    1. Widget Tree: Scaffold > Center > Container > Text
    2. Element Tree: Actual instances of each widget
    3. Render Tree: Visual information like position, size, color of Container and Text
    4. 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.

Code Example

Here's a basic Flutter app structure:

import 'package:flutter/material.dart';
 
void main() => runApp(const MyApp());
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Home Page')),
        body: Center(
          child: Column(
            children: [
              const Text('Hello!'),
              ElevatedButton(
                onPressed: () {
                  print('Button clicked!');
                },
                child: const Text('Click'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
  • 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 example
void 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.

— Robert Fulghum


Other posts
The Team That Builds Test Cases 커버 이미지
 ・ 4 min

The Team That Builds Test Cases

How to Raise Startup Funding 커버 이미지
 ・ 4 min

How to Raise Startup Funding

Entrepreneurship and the Role of a Founder 커버 이미지
 ・ 4 min

Entrepreneurship and the Role of a Founder