Как сделать Sink <Locale>для форматирования результата Stream <String>? - PullRequest
0 голосов
/ 15 сентября 2018

В Google IO 18 докладчики Flutter показали функцию, но не показали, как это реализовать. Видео (в точное время): https://youtu.be/RS36gBEp8OI?t=1776

Как реализовать такую ​​вещь? Как я могу правильно сделать поток, который будет правильно отформатирован на основе Sink?

(извините, но я не слишком знаком с Rx)

Ответы [ 2 ]

0 голосов
/ 22 сентября 2018

Лучший кандидат для этого кроссплатформенного - NumberFormat из пакета intl. Однако вы все равно должны передать ей строку локали ("en_US") и код валюты ISO 4217 ("USD").

После небольшого копания я не смог найти эту информацию ни в одном пакете Дартс. Класс NumberFormat имеет частную карту для поиска символа валюты ("$") по коду валюты, но ключи карты, коды валюты недоступны. Поэтому я решил сделать пакет , в котором будут доступны строки локали и коды валют.

currency_bloc.dart

import 'dart:async';
import 'package:rxdart/rxdart.dart';
import 'package:intl/intl.dart';
import 'package:locales/locales.dart';
import 'package:locales/currency_codes.dart';

class LocalCurrency {
  const LocalCurrency(this.locale, this.code);
  final Locale locale;
  final CurrencyCode code;
  @override toString() => '$code ($locale)';
  @override operator==(o) => o is LocalCurrency && o.locale == locale && o.code == code;
  @override hashCode => toString().hashCode;
}

/// Emits currency strings according to a locale.
class CurrencyBloc {
  // Inputs.
  final _valueController = StreamController<double>();
  final _currencyController = StreamController<LocalCurrency>();
  // Outputs.
  final _currency = BehaviorSubject<String>();

  /// The last formatted currency value emitted from the output stream.
  String lastCurrency;

  // For synchronously receiving the latest inputs.
  double _value;
  NumberFormat _formatter;

  CurrencyBloc({LocalCurrency initialCurrency, double initialValue}) {
    _valueController.stream
        .distinct()
        .listen((value) => _updateCurrency(value: value));
    _currencyController.stream
        .distinct()
        .listen((currency) => _updateCurrency(currency: currency));

    // Initialize inputs.
    locale.add(initialCurrency ??
        LocalCurrency(Locale.en_US, CurrencyCode.usd));
    value.add(initialValue ?? 0.0);
  }

  void dispose() {
    _valueController.close();
    _currencyController.close();
    _currency.close();
  }

  _updateCurrency({double value, LocalCurrency currency}) {
    if (currency != null) {
      _formatter = NumberFormat.simpleCurrency(
          locale: '${currency.locale}',
          name: '${currency.code}',
          decimalDigits: 2);
    }
    if (value != null) {
      _value = value;
    }

    if (_value != null && _formatter != null) {
      lastCurrency = _formatter.format(_value);
      _currency.add(lastCurrency);
    }
  }

  /// Change the current [Locale] and/or [CurrencyCode].
  Sink<LocalCurrency> get locale => _currencyController.sink;

  /// Change the the value to be formatted.
  Sink<double> get value => _valueController.sink;

  /// Formatted currency.
  Stream<String> get currency => _currency.stream;
}

currency_provider.dart (обычный)

class CurrencyProvider extends InheritedWidget {
  CurrencyProvider({Key key, @required this.bloc, @required Widget child})
      : super(key: key, child: child);

  final CurrencyBloc bloc;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static CurrencyBloc of(BuildContext context) =>
      (context.inheritFromWidgetOfExactType(CurrencyProvider) as CurrencyProvider)
          .bloc;
}

Пример использования

...

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

class _MyHomePageState extends State<MyHomePage> {
  CurrencyBloc bloc;

  @override
  Widget build(BuildContext context) =>
      CurrencyProvider(bloc: bloc, child: CurrencyExample());

  @override
  void initState() {
    super.initState();
    bloc = CurrencyBloc();
  }

  @override
  void dispose() {
    bloc.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(StatefulWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    bloc.dispose();
    bloc = CurrencyBloc();
  }
}

class CurrencyExample extends StatelessWidget {
  final controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    final bloc = CurrencyProvider.of(context);
    return ListView(
      children: <Widget>[
        TextField(controller: controller),
        StreamBuilder(
            stream: bloc.currency,
            initialData: bloc.lastCurrency,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data);
              } else if (snapshot.hasError) {
                return new Text('${snapshot.error}');
              }
              return Center(child: CircularProgressIndicator());
            }),
        FlatButton(
          child: Text('Format Currency'),
          onPressed: () => bloc.value.add(double.tryParse(controller.text)),
        )
      ],
    );
  }
}
0 голосов
/ 22 сентября 2018

Используйте функцию combineLatest из пакета rxdart. Он принимает последние значения входных потоков, поэтому каждый раз, когда изменяется языковой стандарт или элементы корзины, он рассчитывает и форматирует общую стоимость.

import 'dart:async'; // Sink, Stream
import 'dart:ui'; // Locale
import 'package:rxdart/rxdart.dart'; // Observable, *Subject

class Bloc {
  var _locale = BehaviorSubject<Locale>(seedValue: Locale('en', 'US'));
  var _items = BehaviorSubject<List<CartItem>>(seedValue: []);
  Stream<String> _totalCost;

  Sink<Locale> get locale => _locale.sink;
  Stream<List<CartItem>> get items => _items.stream;
  Stream<String> get totalCost => _totalCost;

  Bloc() {
    _totalCost = Observable.combineLatest2<Locale, List<CartItem>, String>(
        _locale, _items, (locale, items) {
      // TODO calculate total price of items and format based on locale
      return 'USD 10.00';
    }).asBroadcastStream();
  }

  void dispose() {
    _locale.close();
    _items.close();
  }
}

Отказ от ответственности: я не пытался запустить этот код, поэтому могут быть ошибки, но основная идея должна быть четкой.

...