Flutter Interview Questions and Answers

In this article, you’ll work through a series of Flutter and Dart job interview questions and answers. By Jonathan Sande.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Question 3

Given a Dart stream that produces an unending series of strings that can be either salmon or trout:

final fishStream = FishHatchery().stream; 
// salmon, trout, trout, salmon, ...

Transform the stream so it returns the string sushi only for the first five instances of salmon.

[spoiler title=”Solution”]
The transformed stream looks like this:

final fishStream = FishHatchery().stream;
final sushiStream = fishStream
    .where((fish) => fish == 'salmon')
    .map((fish) => 'sushi')
    .take(5);

If you’d like to play with the code more, here’s the FishHatchery class:

class FishHatchery {
  FishHatchery() {
    Timer.periodic(Duration(seconds: 1), (t) {
      final isSalmon = Random().nextBool();
      final fish = (isSalmon) ? 'salmon' : 'trout';
      _controller.sink.add(fish);
    });
  }

  final _controller = StreamController<String>();
  Stream<String> get stream => _controller.stream;
}

You can learn more about streams in the Flutter’s team video, Dart Streams — Flutter in Focus and in the Dart Creating Streams docs.
[/spoiler]

Question 4

Why would the following code block your Flutter app?

String playHideAndSeekTheLongVersion() {
  var counting = 0;
  for (var i = 1; i <= 1000000000; i++) {
    counting = i;
  }
  return '$counting! Ready or not, here I come!';
}

Would making it an async function help?

[spoiler title="Solution"]
It blocks your app because counting to ten billion is a computationally expensive task, even for a computer.

Dart code runs inside its own area of memory called an isolate — also known as memory thread. Each isolate has its own memory heap, which ensures that no isolate can access any other isolate's state.

Making it an async function wouldn't help, either, because it would still run on the same isolate.

Future<String> playHideAndSeekTheLongVersion() async {
  var counting = 0;
  await Future(() {
    for (var i = 1; i <= 10000000000; i++) {
      counting = i;
    }
  });
  return '$counting! Ready or not, here I come!';
}

The solution is to run it on a different isolate:

Future<String> makeSomeoneElseCountForMe() async {
  return await compute(playHideAndSeekTheLongVersion, 10000000000);
}

String playHideAndSeekTheLongVersion(int countTo) {
  var counting = 0;
  for (var i = 1; i <= countTo; i++) {
    counting = i;
  }
  return '$counting! Ready or not, here I come!';
}

This would not block your UI.

You can read more about asynchronous tasks and isolates in the Flutter team's video, Isolates and Event Loops — Flutter in Focus and also in didierboelens.com's article, Futures — Isolates — Event Loop.

You're also going to get another dose of isolates in the next question.
[/spoiler]

Intermediate Verbal Questions

Question 1

What is the event loop, and what is its relationship to isolates?

[spoiler title="Solution"]
Dart was an early adopter of social distancing. Dart code runs on a single thread called an isolate. Separate isolates don't hang out together — the most they do is text each other. In computer-speak, you'd say that isolates don't share any memory and they only communicate through messages sent over ports.

Every isolate has an event loop, which schedules asynchronous tasks to run. The tasks can be on one of two different queues: the microtask queue or the event queue.

Microtasks always run first, but they are mainly internal tasks that the developer doesn't need to worry about. Calling a future puts the task on the event queue when the future completes.

A lot of new Dart programmers think async methods run on a separate thread. Although that may be true for I/O operations that the system handles, it isn't the case for your own code. That's why if you have an expensive computation, you need to run it on a separate isolate.

Read more about isolates, event loops, and concurrency in the Medium article, Dart asynchronous programming: Isolates and event loops and Futures — Isolates — Event Loops.
[/spoiler]

Question 2

How do you reduce widget rebuild?

[spoiler title="Solution"]
You rebuild widgets when the state changes. This is normal and desirable, because it allows the user to see the state changes reflected in the UI. However, rebuilding parts of the UI that don't need to change is wasteful.

There are several things you can do to reduce unnecessary widget rebuilding.

  • The first is to refactor a large widget tree into smaller individual widgets, each with its own build method.
  • Whenever possible, use the const constructor, because this will tell Flutter that it doesn't need to rebuild the widget.
  • Keep the subtree of a stateful widget as small as possible. If a stateful widget needs to have a widget subtree under it, create a custom widget for the stateful widget and give it a child parameter.

Read more about performance considerations in the Flutter docs.
[/spoiler]

Question 3

What is BuildContext and how is it useful?

[spoiler title="Solution"]
BuildContext is actually the widget's element in the Element tree — so every widget has its own BuildContext.

You usually use BuildContext to get a reference to the theme or to another widget. For example, if you want to show a material dialog, you need a reference to the scaffold. You can get it with Scaffold.of(context), where context is the build context. of() searches up the tree until it finds the nearest scaffold.

Read didierboelens.com's article, Widget — State — Context — Inherited Widget, to not only learn about the build context, but also the stateful widget life cycle and inherited widgets.

Additionally, our article, Flutter Text Rendering, takes you on a low-level tour of the Flutter source code, where you'll meet build context, elements and even render objects.
[/spoiler]

Question 4

How do you talk to native code from within a Flutter app?

[spoiler title="Solution"]
Normally you don't need to talk to native code because the Flutter framework or third party plugins handle it. However, if you do find yourself needing to get special access to the underlying platform, you can use platform channels.

One type of platform channel is a method channel. Data is serialized on the Dart side and then sent to the native side. You can write native code to interact with the platform before sending a serialized message back. That message might be written in Java or Kotlin on Android or Objective-C or Swift on iOS.

You don't use platform channels on the web, however, because they're an unnecessary step.

The second type of platform channel is the event channel, which you use to send a stream of data from the native platform back to Flutter. This is useful for monitoring sensor data.

The Flutter docs have more details about platform channels.
[/spoiler]