Как стать автором
Обновить

RxDart для самых маленьких… проектов

Время на прочтение4 мин
Количество просмотров14K


Вчера мой хороший товарищ сказал что-то типа "я же пишу простяцкое оффлайн приложение, мне ни к чему все эти стримы и потоки". Я даже растерялся, а потом подумал, что это заблуждение могут разделять и другие кодеры.


Ниже буквально в 50 строк я на известном примере покажу, что реактивность


а) это не про оффлайн/онлайн
б) это очень просто
в) очень хороша для упрощения практически любого кода


Моим поспешным критикам,
которые без оглядки бросаются в бой, считая что BlocProvider — это provider, рекомендую для общего развития почитать сперва базовую статью, ссылка на которую есть на странице либы flutter_bloc, в первой же строке описания.


Всем известный пример "Счетчик", который генерится при создании Flutter проекта, является мальчиком для битья хорошей стартовой точкой для демонстрации множества практик.
Итак, он содержит MyHomePage extends StatefulWidget, метод _incrementCounter для команды инкремента и setState для перерисовки всей иерархии виджетов.


Внесем реактивность при помощи библиотеки rxdart и нескольких несложных шагов:


Добавим библиотеку в pubspec.yaml


dependencies:
...
  rxdart: 0.22.2

Изменим архитектуру счетчика и добавим event


class _Counter {
  int _count;

  int get count => _count;

  _Counter(this._count)
      : this.onCounterUpd = BehaviorSubject<int>.seeded(_count);

  /// Создадим евент.
  final BehaviorSubject<int> onCounterUpd;

  /// Вынесем инкремент за пределы виджета, добавим генерацию события.
  Future incrementCounter() async {
    onCounterUpd.add(++_count);
  }
}

final _counter = _Counter(5);

Сделаем класс StatelessWidget


/// Сделаем класс "без состояния"
class MyHomeRxPage extends StatelessWidget {
  final title;

  /// ! - Можно сделать константным классом
  const MyHomeRxPage({Key key, this.title}) : super(key: key);
...

Обернем виджет отображения в StreamBuilder и изменим вызов метода инкремента


            StreamBuilder<int>(
                stream: _counter.onCounterUpd,
                builder: (context, snapshot) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.display1,
                  );
                }),
...
      floatingActionButton: FloatingActionButton(
        onPressed: _counter.incrementCounter,
...

Вот и все. Полностью это выглядит так


import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:rxdart/rxdart.dart';

class _Counter {
  int _count;

  int get count => _count;

  _Counter(this._count)
      : this.onCounterUpd = BehaviorSubject<int>.seeded(_count);

  /// Создадим евент.
  final BehaviorSubject<int> onCounterUpd;

  /// Вынесем инкремент за пределы виджета, добавим генерацию события.
  Future incrementCounter() async {
    onCounterUpd.add(++_count);
  }
}

final _counter = _Counter(5);

///
class MyHomeRxPage extends StatelessWidget {
  final title;

  /// ! - Можно сделать константным классом
  const MyHomeRxPage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(title),
      ),
      body: Center(
        // Center is a layout widget. It takes a single child and positions it
        // in the middle of the parent.
        child: Column(
          // Column is also a layout widget. It takes a list of children and
          // arranges them vertically. By default, it sizes itself to fit its
          // children horizontally, and tries to be as tall as its parent.
          //
          // Invoke "debug painting" (press "p" in the console, choose the
          // "Toggle Debug Paint" action from the Flutter Inspector in Android
          // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
          // to see the wireframe for each widget.
          //
          // Column has various properties to control how it sizes itself and
          // how it positions its children. Here we use mainAxisAlignment to
          // center the children vertically; the main axis here is the vertical
          // axis because Columns are vertical (the cross axis would be
          // horizontal).
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            StreamBuilder<int>(
                stream: _counter.onCounterUpd,
                builder: (context, snapshot) {
                  return Text(
                    'You have pushed the button ${snapshot.data} times:',
                  );
                }),
//            Text(
//              'You have pushed the button this many times:',
//            ),
            /// 6.
            StreamBuilder<int>(
                stream: _counter.onCounterUpd,
                builder: (context, snapshot) {
                  return Text(
                    '${snapshot.data}',
                    style: Theme.of(context).textTheme.display1,
                  );
                }),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _counter.incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Теперь код реактивен, лаконичен, избавлен от лишних перерисовок, и легко расширяем.
Например, если в момент изменения счетчика понадобится менять текст другого виджета, достаточно сделать так:


            StreamBuilder<int>(
                stream: onCounterUpd,
                builder: (context, snapshot) {
                  return Text(
                    'You have pushed the button ${snapshot.data} times:',
                  );
                }),
//            Text(
//              'You have pushed the button this many times:',
//            ),

и вуаля!


Для сравнения попробуйте сделать это же с InheritedWidget, или другим паттерном.


Итак, надеюсь, я показал, что


  • Реактивность это очень несложно. Гораздо проще InheritedWidgets, BlocProvider, etc.
  • Реактивность не про оффлайн/онлайн. Она про архитектуру. Как я показал, в самых простых случаях даже не нужно вносить дополнительные классы, чтобы ее применять.
  • Реактивность это отзывчивые UI, быстрое расширение функционала, изящное разделение кода на слои любого типа: MVC, MVP, MVI, MVVM, все, что пожелается.

Код примера (ветка iter_0004_rxdart)


Отредактировано часом позже
Зря сделал слишком просто, получил щелбанов за "глобальные переменные" и неправильное понимание инициализации BehaviorSubject, исправился

Теги:
Хабы:
Всего голосов 15: ↑13 и ↓2+11
Комментарии30

Публикации

Истории

Работа

iOS разработчик
24 вакансии
Swift разработчик
32 вакансии

Ближайшие события