Irbisa · Шпаргалка

Основы Flutter

Дерево виджетов, StatelessWidget, StatefulWidget, BuildContext

Junior-разработчик · 24 вопроса

  1. 01

    Что такое Flutter, чем он отличается от React Native и других кроссплатформенных фреймворков и какие у него ограничения?

    Flutter представляет собой UI-фреймворк от Google для мобильных, веб- и десктопных приложений из одной кодовой базы на Dart.

  2. 02

    Почему Flutter использует Dart, а не JavaScript или Kotlin?

    Dart умеет работать и через JIT (в debug, что даёт hot reload меньше чем за секунду), и через AOT (в release, что даёт быстрый старт и предсказуемую скорость без интерпретатора).

  3. 03

    Из каких слоёв состоит архитектура Flutter (framework, engine, embedder) и что делает каждый?

    Framework (Dart) содержит виджеты, раскладку, жесты и анимации; engine (C++) запускает рантайм Dart и рендерит кадры через Impeller; embedder представляет собой платформенную оболочку, которая даёт движку поверхность для отрисовки, потоки, ввод и события жизненного цикла.

  4. 04

    В чём разница между императивным и декларативным UI и почему Flutter называют декларативным?

    В императивном UI вы шаг за шагом меняете существующие view (label.setText(...)); в декларативном вы описываете, как экран должен выглядеть при текущем состоянии, а фреймворк сам вычисляет изменения.

  5. 05

    Что значит «всё есть виджет» и почему отступы и прозрачность сделаны отдельными виджетами, а не свойствами?

    Любая часть интерфейса (кнопка, но и отступ, выравнивание, тема или обработчик жестов) представляет собой виджет: неизменяемое описание куска экрана, а приложение образует дерево таких виджетов.

    // Composition: one job per wrapper, and the nesting *is* the layoutclass PriceTag extends StatelessWidget {  const PriceTag({super.key, required this.label});  final String label;   @override…
  6. 06

    В чём разница между StatelessWidget и StatefulWidget и почему не сделать все виджеты stateful?

    StatelessWidget не хранит собственных данных и перерисовывается, только когда родитель передал новые значения.

    // Stateless: a pure function of its constructor argumentsclass GreetingCard extends StatelessWidget {  const GreetingCard({super.key, required this.name});  final String name;   @override…
  7. 07

    Что такое BuildContext, что он представляет в дереве и зачем он нужен?

    BuildContext обозначает место виджета в дереве, а по сути является его Element.

    Widget build(BuildContext context) {  return Theme(    data: ThemeData.dark(),    // wrong: this context sits ABOVE the Theme, so it reads the outer theme    child: Text('Hi', style: Theme.of(context).textTheme.bodyLarge),  );…
  8. 08

    В каком порядке вызываются методы жизненного цикла State, от создания до уничтожения?

    createState → initState → didChangeDependencies → build, а в конце deactivate → dispose.

    class ProfileForm extends StatefulWidget {  const ProfileForm({super.key, required this.name});  final String name;   @override  State<ProfileForm> createState() => _ProfileFormState();…
  9. 09

    В чём разница между main() и runApp() и когда нужен WidgetsFlutterBinding.ensureInitialized()?

    main() служит точкой входа Dart, которую движок вызывает первой; runApp() берёт корневой виджет, прикрепляет его к экрану и начинает выдавать кадры.

    Future<void> main() async {  WidgetsFlutterBinding.ensureInitialized(); // plugins are called below  await Firebase.initializeApp(    options: DefaultFirebaseOptions.currentPlatform,  );  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);…
  10. 10

    Что на самом деле дают MaterialApp и Scaffold и что ломается без них?

    MaterialApp стоит один раз в корне и даёт то, что нужно всему приложению, а Scaffold собирает один экран.

    void main() => runApp(const MyApp()); class MyApp extends StatelessWidget {  const MyApp({super.key});   @override…
  11. 11

    В чём разница между hot reload, hot restart и полным перезапуском?

    Hot reload подставляет изменённый код и сохраняет состояние приложения; hot restart загружает код и заново запускает main(), а состояние теряется; полный перезапуск останавливает приложение, пересобирает и переустанавливает его.

  12. 12

    Для чего нужен pubspec.yaml и что означает ^ в версии зависимости?

    pubspec.yaml служит манифестом проекта: имя, версия, ограничение на SDK, зависимости, dev-зависимости, а также ассеты и шрифты, которые попадут в сборку.

    name: shop_appdescription: Online store client.publish_to: noneversion: 1.4.0+12          # version name + build number environment:…
  13. 13

    Приложение работает на мобильных, но в вебе падает на Platform.isIOS с «Unsupported operation: Platform._operatingSystem». Как во Flutter правильно ветвиться по платформе?

    Platform приходит из dart:io, у которого нет веб-реализации, поэтому чтение Platform.isIOS в браузере бросает UnsupportedError.

    // wrong: compiles for web but throws UnsupportedError when it runs// import 'dart:io' show Platform;// final isApple = Platform.isIOS || Platform.isMacOS; import 'package:flutter/cupertino.dart';import 'package:flutter/foundation.dart';…
  14. 14

    Как отлаживать Flutter-приложение и чем отличаются print, debugPrint и log?

    Отлаживают брейкпоинтами в IDE, через Flutter DevTools и логи.

    import 'dart:developer' as developer;import 'package:flutter/foundation.dart'; void onCheckoutFailed(Order order, Object error) {  if (kDebugMode) {    debugPrint('checkout ${order.id}: $error');   // throttled, safe for long text…
  15. 15

    Что такое режимы debug, profile и release во Flutter и как с ними связаны JIT- и AOT-компиляция?

    Debug запускает ваш Dart-код на VM с JIT-компилятором: hot reload работает, но приложение медленное и assert включены.

    flutter run                    # debug: JIT, hot reload, asserts onflutter run --profile          # AOT + tracing, real device onlyflutter run --release          # AOT, what users get flutter build apk              # build defaults to releaseflutter build appbundle --obfuscate --split-debug-info=build/symbols…
  16. 16

    Экран отправляет запрос к API прямо из build() и заваливает сервер. Почему build вызывается так часто и куда переносить вызов?

    build() обязан быть чистой функцией от входных данных виджета: Flutter вызывает его заново на каждое изменение, иногда много раз в секунду.

    // wrong: a new request on every rebuild@overrideWidget build(BuildContext context) {  return FutureBuilder(    future: api.loadUser(widget.id),   // rebuilt = refetched    builder: (context, snap) => UserView(snap.data),…
  17. 17

    Разбивать длинный build на приватные хелперы _buildHeader() или на отдельные классы-виджеты?

    Выделяйте отдельный класс-виджет.

    // wrong: helper method; one Element, rebuilt with the whole pageWidget _buildHeader(BuildContext context) =>    Text('Profile', style: Theme.of(context).textTheme.headlineSmall); // right: widget classes; own Elements, const-able, independently rebuiltclass ProfilePage2 extends StatelessWidget {…
  18. 18

    Счётчик хранится полем прямо в StatefulWidget, setState его меняет, но значение постоянно сбрасывается. Почему?

    Объект виджета, в котором лежит это поле, выбрасывается и создаётся заново при каждой перестройке родителя.

    // wrong: state on the widget; the field dies with the instanceclass Counter extends StatefulWidget {        // analyzer: must_be_immutable  Counter({super.key});  int count = 0;                              // recreated by every parent build   @override…
  19. 19

    Экран должен встречать пользователя диалогом при открытии, но вместо этого падает. Что не так и как это исправить?

    class _WelcomeState extends State<Welcome> {  @override  void initState() {    super.initState();    showDialog<void>(      context: context,      builder: (_) => const AlertDialog(title: Text('Welcome!')),    );  }   @override  Widget build(BuildContext context) => const Scaffold(body: Placeholder());}

    initState вызывается, когда виджет ещё не до конца встроен в дерево, поэтому его context пока не может читать унаследованные виджеты, а showDialog они нужны (локализации, тема, Navigator).

  20. 20

    Отметьте строку B в этом списке, затем нажмите «Remove first», и галочка переезжает на C. Почему и как это исправить?

    class _TodoListState extends State<TodoList> {  final items = ['A', 'B', 'C'];   @override  Widget build(BuildContext context) => Column(        children: [          // CheckTile is stateful: it keeps its own `checked` bool          for (final item in items) CheckTile(label: item),          TextButton(            onPressed: () => setState(() => items.removeAt(0)),            child: const Text('Remove first'),          ),        ],      );}

    Без ключей Flutter сопоставляет новые виджеты с существующими элементами по типу и позиции.

  21. 21

    Найдите баг: в планшетной раскладке master–detail выбор другого пользователя в списке по-прежнему показывает данные первого.

    class _UserDetailsState extends State<UserDetails> {  late Future<User> _user;   @override  void initState() {    super.initState();    _user = widget.repo.loadUser(widget.userId);  }   @override  Widget build(BuildContext context) => FutureBuilder<User>(        future: _user,        builder: (context, snap) => Text(snap.data?.name ?? 'Loading…'),      );}

    initState вызывается один раз на State, поэтому _user загружается только для первого userId.

  22. 22

    Найдите баг: после нажатия «Сохранить» в отчётах о сбоях появляется необработанное исключение из обработчика сохранения.

    class _EditNameState extends State<EditName> {  final _name = TextEditingController();   Future<void> _save() async {    await widget.api.saveName(_name.text);    ScaffoldMessenger.of(context)        .showSnackBar(const SnackBar(content: Text('Saved')));    Navigator.of(context).pop();  }   @override  void dispose() {    _name.dispose();    super.dispose();  }}

    После await экран может быть уже закрыт: его State отмонтирован, context больше нет, и ScaffoldMessenger.of(context) бросает исключение.

  23. 23

    Как приложение узнаёт, что ушло в фон, и что там нужно делать?

    Подмешайте в свой State миксин WidgetsBindingObserver, и Flutter будет вызывать didChangeAppLifecycleState каждый раз, когда приложение уходит в фон и возвращается.

    class _PlayerState extends State<Player> with WidgetsBindingObserver {  final _controller = VideoController();   @override  void initState() {    super.initState();…
  24. 24

    CTO спрашивает, на чём делать следующий продукт: на Flutter, на Kotlin Multiplatform с Compose Multiplatform или двумя нативными приложениями. Что вы взвешиваете?

    Взвешивают команду, существующий код, степень кастомности UI и долю продукта, которая живёт в платформенных функциях; чистая скорость решает редко.