Skip to content

FAQ


Is it a must to always use MaterialApp for as the base for every app?

While it is not strictly required to use MaterialApp in every Flutter application, it is highly recommended and serves as the root widget for most applications, especially those that implement Material Design.

The MaterialApp widget serves several key roles, including:

  1. It acts as a wrapper around your application and provides many features that follow the Material Design guidelines.
  2. It includes a Navigator, which manages routing and navigation for the application. This includes displaying other widgets based on routes, handling back navigation, and transition animations.
  3. It provides themes for the application, allowing you to control the visual appearance including colors, text styles, etc.
  4. It offers localization, accessibility support, and many more built-in services that would be cumbersome to implement on your own.

In apps that don't need these services, or prefer a different design language, you could use the more bare-bones WidgetsApp as your root widget or even build your own custom root widget. However, for most typical Flutter apps, MaterialApp provides a foundation that's both easy to use and feature-rich.


Why need the const to be added to widgets?

In Dart and Flutter, the const keyword is used to represent a compile-time constant. Values which are assigned as const are determined at compile-time and cannot be changed once assigned.

Adding const to your Flutter widgets tells the framework that the widget is immutable and can be efficiently redrawn by directly reusing the previous widget when performed in a rebuild operation.

For example, instead of creating and rendering Text widget every time screen rebuilds:

Text('Hello World'),

You can use:

const Text('Hello World'),

This const keyword will tell Flutter that this widget will never change, and Flutter can optimize by reusing it from cache instead of creating a new Widget.

Here are the general rules when to use const in Flutter:

  1. If the widget and its entire subtree doesn't depend on any external data or state, you should add const to the widget.
  2. Do not use const if any value within that widget can change dynamically through any sort of state management (like setState, Provider, Bloc, etc), or data received from server, or user input, and it requires the widget to rebuild.
  3. const can only work with data that is immediately available at compile-time. It cannot work with data that would only be available at runtime.

When to use stateless and when to use stateful?

In Flutter, widgets form the fundamental building blocks of the UI and can be divided into two types:

  1. Stateless Widgets are static widgets that remain unmodified once they have been built. They describe a part of the user interface which can depend on configuration information (constructor arguments) and can change only if their parent changes. They do not store mutable data. Examples are Text, Icon, and RaisedButton.

When to use: If your widget only needs to display some data and doesn't need to manage any state itself, it can be a StatelessWidget.

  1. Stateful Widgets are dynamic and store mutable data. They are used when the part of the UI can change dynamically. A StatefulWidget can be used any time the widget might need to change: for example, a Widget that animates, a Widget that changes shape or color when a user interacts with it. The widget’s state is created, updated, and destroyed by the framework using the StatefulWidget’s createState, State.build, and State.dispose.

When to use: If your widget needs to maintain some kind of state, like user input, configuration, or read from a database, it can be a StatefulWidget.

Remember: While making the decision between Stateless and Stateful, it is important to consider the lifecycle of your widget and whether or not it will need to maintain and change data over time.


What lifecycle process of a stateful widget?

A Stateful widget in Flutter has a lifecycle that governs its behavior. Here are the main stages of the lifecycle:

  1. createState(): This is the first method called when you instantiate a StatefulWidget. It returns a new state object.

  2. initState(): This is the first method called after the State object is created. This method is only called once in the lifecycle of a state object. It is typically overridden to perform one-time setup that requires context, such as reading shared preferences or database, listening to streams, etc.

  3. didChangeDependencies(): Called when a dependency changes. If you refer to an InheritedWidget in your build method, this method is called when that widget changes.

  4. build(): This method is called whenever the UI needs to be rendered. It's called often to redraw the UI, so keep it efficient. It's required and needs to return a Widget.

  5. didUpdateWidget(Widget oldWidget): Flutter calls this method whenever it rebuilds your StatefulWidget with new configuration information. You might need to update your state if you depend on your widget's configuration.

  6. dispose(): Just before destroying the state object, Flutter calls this method. You can override it to unsubscribe from any streams, cancel any network requests or clean up resources used by your state object.

Remember that StatefulWidgets are immutable, but the separate State object persists over the lifetime of the widget.


What exactly happen for setState()?

In Flutter, the setState method is a crucial function when working with StatefulWidget. It signals to the Flutter framework that the internal state of the widget has changed in a way that requires the widget to be rebuilt.

Here's a detailed explanation of the purpose of setState in StatefulWidget:

Purpose of setState

  1. Trigger a Rebuild:
  2. When setState is called, it schedules a rebuild of the widget, ensuring that any changes to the state are reflected in the UI.
  3. This is necessary because Flutter's rendering process is based on the concept of immutability; the UI doesn't automatically update when the state changes. Instead, the framework needs to be explicitly informed that it needs to repaint.

  4. Update the State:

  5. The setState method takes a callback function where you can update the internal state variables of your widget.
  6. The changes made inside the setState callback are then applied, and the framework knows to render the widget again with these new state values.

How setState Works

  1. Mutate the State:
  2. Inside the setState callback, you modify the state variables.

  3. Schedule a Rebuild:

  4. Calling setState notifies the Flutter framework that it should trigger a rebuild of the widget.

  5. Rebuild the Widget:

  6. During the next build phase, the framework calls the build method of the widget, creating a new widget tree that reflects the updated state.

Syntax

setState(() {
  // Code to modify the state
  _myStateVariable = newValue;
});

Any time you change a variable or data inside your StatefulWidget and you want to reflect the changes in your UI, you should wrap that change inside the setState method.

Here is a simple example:

class MyStatefulWidget extends StatefulWidget {
  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter app')),
      body: Center(
        child: Text('Count: $_count'),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
      ),
    );
  }
}

In the above code, every time the floating action button is pressed, _count is incremented. However, just incrementing _count would not cause the Text widget to visually update with the new count.

Wrapping _count++ with setState tells the Flutter framework that it needs to rebuild the widget. Flutter will then run this widget's build method and draw the updated count on the screen.


What is the different between pushNamed() vs pushReplacementNamed()

Imagine that each time, only one screen is able to be shown and you can either have a screen fully replace with another screen or you have a screen "stack on top" of the previous screen

In Flutter, the Navigator class provides various methods to manage the stack of routes (screens or pages) in your application. Two such methods are pushNamed and pushReplacementNamed. They have different behaviors in terms of how they manipulate the navigation stack.

pushNamed

pushNamed is used to push a new route onto the navigator stack, which means it opens a new screen and keeps the current screen in the stack. This allows the user to navigate back to the previous screen using the back button.

Use Case: When you want to navigate to a new screen but still allow the user to go back to the previous screen

Navigator.of(context).pushReplacementNamed('/newScreen');

pushReplacementNamed

pushReplacementNamed is used to push a new route onto the navigator stack and simultaneously remove the current route. This means it replaces the current screen, so the user cannot navigate back to the previous screen.

Use Case: When you want to navigate to a new screen and do not want the user to navigate back to the previous screen. This is useful for workflows where the previous screen is no longer relevant, like after a successful login.

Navigator.of(context).pushReplacementNamed('/newScreen');

Comparison

  1. Stack Behavior:

    • pushNamed: Adds the new screen to the navigation stack
    • pushReplacementNamed: Replaces the current screen in the navigation stack with the new screen
  2. Back Navigation:

    • pushNamed: User can navigate back to the previous screen
    • pushReplacementNamed: User cannot navigate back to the previous screen and normally you will add a drawer
  3. Use Cases:

    • pushNamed: When you want to provide back navigation (e.g., navigating from a list of items to a detail screen)
    • pushReplacementNamed: When the previous screen is no longer relevant and you do not want to provide back navigation (e.g., after a login or splash screen)

What is the diff between onChanged and onSaved in DropdownButtonFormField?

tl;dr

  • onChanged:

    • Called whenever the user changes the field.
    • Ideal for immediate responses to user input.
  • onSaved:

    • Called when the form is saved.
    • Ideal for collecting final input values during form submission.

Example code:

DropdownButtonFormField<String>(
  value: _selectedValue,
  items: _dropdownItems,
  onChanged: (String? newValue) {
    setState(() {
      _selectedValue = newValue!;
    });
  },
  onSaved: (String? newValue) {
    _savedValue = newValue!;
  },
),

In Flutter, both onChanged and onSaved are callbacks used with form fields like DropdownButtonFormField. They serve different purposes:

onChanged

  • Purpose: onChanged is called whenever the user selects a new value from the dropdown list.
  • Execution Time: It is triggered immediately when the user changes the value.
  • Use Case: Use onChanged when you need to respond to user input immediately. For example, if you want to update other parts of the UI or validate input as soon as the user makes a selection.

onSaved

  • Purpose: onSaved is called when the form is saved (usually when the form's save method is called).
  • Execution Time: It is triggered only once, typically when the form is submitted using methods like formKey.currentState!.save().
  • Use Case: Use onSaved when you want to collect the final value of the form field and do something with it, such as sending it to a server or saving it in a database.

What does parsing means?

Parsing is the process of converting data from one format to another, making it usable or understandable for further processing. This often involves reading a string or a stream of data and transforming it into a structured format that a program can work with.

Key Points About Parsing

  • Conversion: Converts data from one format (often text) into another (such as integers, doubles, objects, etc.).
  • Validation: Ensures the data meets the required format or structure.
  • Extraction: Involves extracting specific pieces of information from a larger dataset.

Parsing in Python

In Python, parsing a string to an integer is straightforward:

Example: String to Integer in Python

str_value = "123"
int_value = int(str_value)
print(int_value)  # Output: 123

Parsing in Dart

In Dart, parsing is done using similar methods, but the syntax is different.

Example 1: String to Integer

void main() {
  String str = "123";
  int number = int.parse(str);
  print(number); // Output: 123
}

Example 2: String to Double

void main() {
  String str = "123.45";
  double number = double.parse(str);
  print(number); // Output: 123.45
}

Wat does exceptions means?

What is an Exception?

An exception is an event that occurs during the execution of a program that disrupts the normal flow of instructions. It's a way to handle errors or other exceptional events that occur during runtime.

Why Use Exceptions?

Exceptions allow a program to continue running or to gracefully handle errors without crashing. Instead of stopping the entire program, exceptions provide a way to catch and respond to error conditions.

Basic Explanation

Imagine you're following a recipe to bake a cake. An exception is like discovering you're out of flour halfway through. Instead of giving up, you pause, handle the situation (maybe by borrowing flour from a neighbor), and then continue with the recipe.

Exceptions in Dart

In Dart, exceptions are handled using try, catch, and finally blocks. Here's a simple breakdown:

  1. try: The code that might throw an exception is placed inside a try block
  2. catch: If an exception occurs, the catch block is executed
  3. finally: Code inside the finally block is executed regardless of whether an exception occurred

Example 1: Basic Exception Handling

Example of trying to parse a string to an integer:

void main() {
  String input = "abc"; // This is not a valid integer

  try {
    int value = int.parse(input);
    print("The value is $value");
  } catch (e) {
    print("An error occurred: $e");
  } finally {
    print("Parsing attempt finished.");
  }
}
In the example above:

  • The try block attempts to parse the string
  • If the parsing fails, the catch block handles the error
  • The finally block runs regardless of whether an error occurred or not

Example 2: Using tryParse for Safe Parsing

A safer way to handle parsing is using the tryParse method, which doesn't throw an exception but returns null if parsing fails:

void main() {
  String input = "abc";
  int? value = int.tryParse(input);

  if (value == null) {
    print("Failed to parse the input.");
  } else {
    print("The value is $value");
  }
}
In the example above:

  • tryParse returns null instead of throwing an exception.
  • You can check if the result is null and handle it accordingly.

What is Dependency Injection?

Dependency Injection (DI) is a design pattern used in software development to achieve Inversion of Control (IoC). In simpler terms, it's a way to provide a class with the objects it needs (its dependencies) from the outside rather than creating them itself. This promotes modularity and makes the code easier to maintain and test.

Key Concepts

  • Dependency: An object that a class needs to function.
  • Injection: The process of passing these dependencies to the class from the outside.

Layman Term Explanation

Imagine you own a coffee shop, and you need a coffee machine that requires water, coffee beans, and electricity to make coffee.

Without Dependency Injection

  1. You (the coffee machine) have to buy your own water, coffee beans, and get connected to electricity:

    CoffeeMachine:
       - Water: Buy your own water
       - CoffeeBeans: Buy your own coffee beans
       - Electricity: Connect to power by yourself
    

    This means the coffee machine has to handle everything on its own, which makes it difficult to change any of its components without modifying the coffee machine itself.

With Dependency Injection

  1. Someone else provides the water, coffee beans, and connects you to electricity:

    CoffeeMachine:
       - Water: Provided by someone else
       - CoffeeBeans: Provided by someone else
       - Electricity: Connected by someone else
    

    This way, the coffee machine only focuses on making coffee, while someone else takes care of providing what it needs. If you want to change the brand of coffee beans, you just provide a different coffee bean supplier.


What is the diff between ListView.builder and ListView.separated?

Overview

Flutter provides several ways to create scrollable lists, among them are ListView.builder and ListView.separated. Both are used to create dynamic, lazily loaded lists, but they serve slightly different purposes with key functional differences.

ListView.builder

ListView.builder is used to create a scrollable, linear array of widgets that are created on demand. This is particularly useful when dealing with large collections of data as it ensures that only visible items are built and maintained in memory.

Syntax

ListView.builder(
  itemCount: int?,
  itemBuilder: BuildContext context, int index,
)

Key Parameters

  • itemCount: The total number of items in the list.
  • itemBuilder: A function that returns a widget to build for each item in the list, given its index.

Example

ListView.builder(
  itemCount: 100,
  itemBuilder: (BuildContext context, int index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
)

Use Case

Use ListView.builder when you need a simple, efficient way to display a large number of widgets, and you don't need any custom separators between the items.

ListView.separated

ListView.separated is similar to ListView.builder but adds the ability to include a separator widget between the items. This is useful for adding visual separation between list items such as dividers or spaces.

Syntax

ListView.separated(
  itemCount: int,
  itemBuilder: BuildContext context, int index,
  separatorBuilder: BuildContext context, int index,
)

Key Parameters

  • itemCount: The total number of items in the list.
  • itemBuilder: A function that returns a widget to build for each item in the list, given its index.
  • separatorBuilder: A function that returns a widget to build for separating each pair of items, given its index.

Example

ListView.separated(
  itemCount: 100,
  itemBuilder: (BuildContext context, int index) {
    return ListTile(
      title: Text('Item $index'),
    );
  },
  separatorBuilder: (BuildContext context, int index) {
    return Divider();
  },
)

Use Case

Use ListView.separated when you need to insert a separator widget (such as a Divider or a custom widget) between your list items.

Key Differences

  1. Separator:
  2. ListView.builder: Does not include separators between items.
  3. ListView.separated: Allows for a custom separator widget between items.

  4. Flexibility:

  5. ListView.builder: Simplified interface when separators are not needed.
  6. ListView.separated: Additional flexibility for adding custom separators.

  7. Use Case:

  8. ListView.builder: Ideal for lists without separators or with more complex item rendering logic.
  9. ListView.separated: Ideal for lists requiring visual separation between items, such as dividers or custom widgets.

Summary

  • ListView.builder:
  • Purpose: Efficiently builds a scrollable list of widgets as they are scrolled into view.
  • Syntax:
    ListView.builder(
      itemCount: int?,
      itemBuilder: BuildContext context, int index,
    )
    
  • Key Features: No separators between items.
  • Use Case: When you need a simple list without separators.

  • ListView.separated:

  • Purpose: Builds a scrollable list of widgets with separators between each item.
  • Syntax:
    ListView.separated(
      itemCount: int,
      itemBuilder: BuildContext context, int index,
      separatorBuilder: BuildContext context, int index,
    )
    
  • Key Features: Allows for custom separators between items.
  • Use Case: When you need visual separation between list items, such as dividers.

Both ListView.builder and ListView.separated are powerful tools in Flutter for creating efficient, scrollable lists, with ListView.separated providing additional functionality for inserting separators between list items.