Pull to refresh
15
0
Alexander Zhdanov @qwert2603

Android Developer

Send message

Спасибо за статью и подробное объяснение!

Добавлю небольшое уточнение по поводу crossinline.

    private inline fun crossInlineFun(crossinline body: () -> String) {
        val func = {
            "crossInline func code, " + body.invoke()
        }
        regularFun(func)
    }

В этом примере нужен crossinline, так как в передаваемой лямбде-параметре body на стороне вызывающей функции могут быть non-local returns, и поэтому body нельзя передавать в другой контекст исполнения (в лямбду func). Чтобы исправить это, можно либо отменить инлайнинг body (добавив noinline), либо запретив использовать non-local returns внутри body (добавив crossinline). В этом примере более предпочтителен вариант с crossinline, так как он позволяет всё-таки заинлайнить body.

noinline же полезен в том случае, когда, например, нужно работать с лямбдой-параметром в inline функции как с обычной переменной или передать в качестве параметра в "не inline" функцию.

    private inline fun crossInlineFun(noinline body: () -> String) {
        val someFunc = body
        regularFun(func)
    }

Здесь отменяется илнайнинг body, поэтому для него создаётся инстанс анонимного класса, и с ним можно работать как с обычной переменной.

В Android уже есть проверка на префикс у ресурсов внутри модуля. Можно добавить resourcePrefix в gradle файле.

Привет!

  1. Мне тоже ближе MVVM, и использование единого стейта из MVI позволяет получить лучшее из обоих подходов. С единым стейтом включение кнопки "войти" будет всего-лишь полем в классе стейта без необходимости создавать отдельный StateFlow или использовать derivedStateOf.

Кроме того, использование единого стейта для компонента очень удобно для написания проверок в unit-тестах. Достаточно будет проверить только 1 значение вместо нескольких.

Также в  другом комменте писали про складывание кол-ва состояний. И при использовании единого класса стейта гораздо проще будет ограничить невалидные состояния (кинуть exception в конструкторе), чем при разделении стейта на части.

Спасибо за статью! Тоже не раз убеждался, что подобные компоненты сильно упрощают код.

После прочтения осталось пара вопросов:

  1. почему в SignInComponent добавлено несколько StateFlow для полей-состояний вместо одного? Хранение состояния "по частям" может привести к проблемам с консистентностью, а единый объект-стейт будет проще логировать и упростит отладку.

  2. как лучше упростить создание дочерних компонентов с помощью DI? Сейчас в RealMainComponent дочерние компоненты создаются вручную, и при появлении зависимостей у дочерних компонентов (например, интеракторы и репозитории) их создание превратится в бойлерплейт.

Подскажите, не возникает ли проблем с подключением монитора по HDMI на Linux? У меня на монитор ничего не выводится HP OMEN 15-en0006ur с установленным Ubuntu 20.04, перепробовал все драйвера и советы с форумов...

Большое спасибо за статью! Скажите, где можно почитать про планируемую систему сборки под Flutter?
После перехода из мира Android не хватает возможности указывать зависимости для конкретного flavor, а также различия api / implementation (как в Gradle).

Ещё раз спасибо за подробное объяснение!


Сейчас разобрался — при изменении уровня вложенности (родителя виджета) на самом деле помочь может только GlobalKey. Но если виджеты просто меняются местами, оставаясь потомками одного родителя, то для сохранения Elements подойдут и ValueKey.


Как сказано в этом видео, "Flutter's element-to-widget algorithm looks at one level of tree at a time.", поэтому ValueKey не позволяет сохранить element при изменении уровня вложеннности.


Демо приложения с использованием ValueKey
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Keys & Elements',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool flag = false;

  @override
  Widget build(BuildContext context) {
    var children = flag ? [first(), second()] : [second(), first()];
    return Scaffold(
      appBar: AppBar(title: Text("Keys & Elements")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: children,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() => flag = !flag),
        child: Icon(Icons.swap_horiz),
      ),
    );
  }

  Widget first() => Container(
        key: ValueKey(1),
        height: 100,
        width: 100,
        color: Colors.green,
      );

  Widget second() => Transform(
        key: ValueKey(2),
        transform: Matrix4.rotationZ(1),
        child: Container(
          height: 100,
          width: 100,
          color: Colors.orange,
        ),
      );
}

Дальнейшие эксперименты привели меня к тому, что если меняется тип родительского виджета, то снова только GlobalKey не помогает сохранить element дочернего виджета, который не изменился. И это выглядит логично, так как если element родительского виджета сохранить не получилось, то не сохранятся и дочерние elements. В этом плане GlobalKey куда удобнее, но его главный минус в том, что его не получится использовать в StatelessWidget, а также "Global keys are relatively expensive."

Большое спасибо за статью!
Для переиспользования части дерева можно использовать ValueKey или ObjectKey вместо GlobalKey, чтобы не создавать для Key отдельное поле.

Еще стоит отметить, что при использовании String.fromEnvironment или bool.fromEnvironment надо присваивать полученное значение именно константам, иначе эти методы будут возвращать defaultValue.
Поведение это странное, и, надеюсь, в будущем его починят.
Issue в GitHub

Собирать под ios/android можно уже давно. Сборка под web уже в beta, сборка под macOS — в alpha. Так что это возможно уже сейчас.
Можно даже собирать под linux / windows, но только с master channel Flutter.

Спасибо за идею! Можно сделать TestAction полноценным классом, который будет иметь поле String name и добавить логирование в методе runTestActions — выводить какое действие и над каким элементом выполняется.
Это уже сделано в ветке master — коммит.
В будущем можно даже делать скрин упавшего теста для удобства.

Спасибо за отзыв! Такие тесты могут запускаться и на эмуляторах и на реальных Android/iOS девайсах. И даже на десктопе (правда, только с master ветки Flutter).
Такие тесты и правда удобно использовать для записи демо-видео или создания нужных скриншотов.

Посмотрите readme на github

А в примере со Slack что мешает злоумышленнику создать свое приложение с applicationId "com.Slack" и получать явный интент?

Сгенерированный метод ResultProfileBinding.inflate имеет перегрузку с параметрами (inflater, viewGroup, attach)

Не во всех проектах используется databinding, а View Binding решает очень распространенную задачу, и начать его применять достаточно легко.

тем, что View Binding обеспечивает Null Safety, например, в ситуации, когда view есть в одной конфигурации, но нет в другой.

Да, про использование в RecyclerView.ViewHolder написал я, так как это распространенный случай получения view в коде.

View Binding отличается от Butter Knife типобезопасностью и отсутствием бойлерплейта.

Есть еще один способ для передачи зависимостей в конструктор CustomView.
Надо создать свой CustomViewInflater, наследуясь от androidx.appcompat.app.AppCompatViewInflater, и указать его как viewInflaterClass в теме приложения.


CustomViewInflater


AppTheme

1

Information

Rating
Does not participate
Date of birth
Registered
Activity