Flutter Advanced Interview: Part 2
In the previous blog we covered a few of the several 1000 questions which can come up in a Flutter developer interview. In the second part we will answer a few more questions and learn a few more exciting stuff which will help you prepare for your next interview. This question can be considered as Advanced or basic level based upon your knowledge. These are some of the common questions which i have been asked in the past few interviews.
1. What are Factory Constructors?
Ans: factory constructors are a special type of constructor that are used to create objects. Unlike regular constructors that create a new instance of a class, factory constructors have the ability to return an instance of a different class or even a previously created instance.
class MyClass {
factory MyClass() {
/// use logic to determine which object to create or even reuse existing instances.
return MyClass();
}
}
Factory constructors are responsible for creating and returning instances of the class. They can use logic to determine which object to create or even reuse existing instances. They typically use the return
statement to specify the object to be returned.
Some of the use cases are:
i. Creating objects from a different class or subclass.
ii. Caching instances and returning them instead of creating new ones.
iii. Performing complex logic to determine which object to create based on conditions.
Here’s an example that demonstrates the usage of a factory constructor:
class Circle {
final double radius;
Circle(this.radius);
factory Circle.fromDiameter(double diameter) {
return Circle(diameter / 2);
}
}
void main() {
Circle circle1 = Circle(5); // Using regular constructor
Circle circle2 = Circle.fromDiameter(10); // Using factory constructor
print(circle1.radius); // Output: 5.0
print(circle2.radius); // Output: 5.0
}
In the example, the Circle
class has a regular constructor that accepts the radius directly. It also has a factory constructor named fromDiameter
, which calculates the radius based on the provided diameter and returns a new instance of the Circle
class.
Using the factory constructor Circle.fromDiameter(10)
creates a Circle
object with a radius of 5.0, as the factory constructor calculates the radius by dividing the diameter by 2.
2. How can we optimise of improve performance in a Flutter App
Ans: Here are some tips on how to optimize Flutter apps:
a. Use Stateless Widgets: If your widget doesn’t need to maintain state, consider using a StatelessWidget instead of a StatefulWidget. StatelessWidgets are less expensive to build and rebuild than StatefulWidgets.
b. Use const and final: Use the const
keyword for widgets and other objects that don’t change at runtime. Use the final
keyword for objects that don’t change after they’re initialized.
c. Avoid using setState()
unnecessarily: setState() triggers a rebuild of the widget tree.
d. Use ListView.builder()
instead of ListView(): Use ListView.builder() when you have a long list of items to display.
e. Use the const constructor for widgets: When creating widgets, use the const constructor whenever possible. This tells Flutter that the widget won’t change, so it can optimize its rendering.
f. Avoid using too many plugins: Plugins can add a lot of overhead to your app. Try to use only the plugins you need and avoid using too many plugins.
g. Minimize the use of Animations: Animations can be expensive in terms of performance.
h. Optimize your images: Large images can slow down your app. Use compressed images and reduce their size whenever possible.
i. Use Stateful widget wisely. Sometimes its better to use a stateful than a stateless widget. We have to identify the use cases properly so that we can make efficiecnt use of the lifecycle methods of statefull widgets.
j. using isolates and concurrent programming: Achieving concurrent execution can enhance the performance. we can move heavy cpu intensive tasks to seperate isolates so that we can acheive better performance in the main memory. You need to have good understanding of the isolates and should also be careful and use it wisely else it would easily become counter productive.
3. Difference between didChangeDependencies() and didUpdateWidget()?
Ans: Both didChangeDependencies
and didUpdateWidget
are lifecycle methods in Flutter that get called when a widget is updated, but they serve different purposes.
didChangeDependencies
is called when a widget’s dependencies have changed. Dependencies can include data that the widget depends on, such as the state of a Provider
or the arguments passed to a ModalRoute
. This method is typically used to update the widget’s state or any other logic that depends on the new dependencies.
On the other hand, didUpdateWidgets
is called when a widget is updated with a new instance of itself. This method is called after the framework determines that it needs to rebuild the widget.
4. What are RenderObjects in Flutter?
Ans: In Flutter, a RenderObject is a fundamental building block of the layout and rendering system. Every widget in Flutter is associated with a RenderObject, which is responsible for the layout and rendering of that widget. It is responsible for defining the geometry and appearance of the widget, including its size, position, and visual characteristics. It provides a low-level interface for the framework to interact with the graphics system, and it handles all the details of how to render the widget on the screen.
The parent RenderObject determines the size and position of its child RenderObjects, which in turn determine the size and position of their own children, and so on.
RenderObjects are highly optimized for performance and efficiency. They use a variety of techniques such as caching, invalidation, and incremental updates to minimize the amount of work required to update the layout and rendering of the widgets.
5. What are Generic Data type in Flutter?
Generics in Flutter allow you to create reusable code that can work with different data types. They provide a way to define classes, functions, or methods that can operate on different data types without sacrificing type safety.
They allow you to specify the type of elements that the collection will hold or the type of values that the function or method will work with, while still maintaining type safety.
class Box<T> {
T value;
Box(this.value);
}
void main() {
Box<int> intBox = Box<int>(42);
print(intBox.value); // Output: 42
Box<String> stringBox = Box<String>('Hello');
print(stringBox.value); // Output: Hello
}
Generics provide flexibility and type safety by allowing you to write code that can work with multiple types while maintaining compile-time type checks. They help in creating reusable and flexible code components in Flutter applications, especially when working with collections, data manipulation, and algorithms.
6. What are Extensions in Dart?
Ans: Extensions in Dart provide a way to add new methods or properties to existing classes or types without modifying their source code. They allow developers to extend the behavior of classes, making code more expressive and reusable. They can be used to add utility functions, provide convenience methods, or define custom operations on existing types. The following code snippet is how you write an extension on
extension StringExtension on String {
String capitalize() {
if (this.isEmpty) {
return this;
}
return '${this[0].toUpperCase()}${this.substring(1)}';
}
}
This Extension can be used as follows.
void main() {
String name = 'flutter';
String capitalized = name.capitalize();
print(capitalized); // Output: Flutter
}
7. What are Generators in Dart?
Ans: Generators in Dart provide a way to create iterable sequences of values “on-demand”, allowing for more efficient memory usage and performance. Dart provides two types of generators: Iterable and Stream generators.
Iterable generators are created using the sync*
syntax and the yield
keyword. Where as stream or asynchronous generators are created using the async*
syntax and the yield
keyword.
Iterable<int> countUpTo(int n) sync* {
for (int i = 1; i <= n; i++) {
yield i;
}
}
void main() {
final sequence = countUpTo(5);
for (var value in sequence) {
print(value);
}
}
import 'dart:async';
Stream<int> countUpToAsync(int n) async* {
for (int i = 1; i <= n; i++) {
yield i;
await Future.delayed(Duration(seconds: 1));
}
}
void main() {
final stream = countUpToAsync(5);
stream.listen((value) {
print(value);
});
}
8. Multi threading in Dart?
Ans: In Dart, multi-threading can be achieved using isolates. Isolates are independent workers that run in separate memory spaces, allowing concurrent execution of code. Each isolate has its own event loop and memory heap. Isolates can be created using Isolates.spawn()
We pass the function we need to execute concurrently to the isolate, we also pass the required ports. The communication between the isolates happen via message passing.
Checkout the following code snippet so that you can understand it better.
import 'dart:isolate';
void myIsolateFunction(SendPort sendPort) {
// Code to be executed in the isolate
sendPort.send('Hello from isolate!');
}
void main() async {
ReceivePort receivePort = ReceivePort();
Isolate.spawn(myIsolateFunction, receivePort.sendPort);
// Receiving messages from the isolate
receivePort.listen((message) {
print('Received message: $message');
});
}
All the best for your next Flutter Interview. I’ll be adding more parts to this Blog series as and when i face tough common interview questions. You can checkout Part1 here.