如何使Sink <locale>格式化Stream <string>的结果?

时间:2018-09-15 01:47:06

标签: dart flutter googleio

在google IO 18中,Flutter演示者展示了一项功能,但未展示如何实现此功能。 视频(确切时间)为:https://youtu.be/RS36gBEp8OI?t=1776

该如何实施?如何根据接收器正确设置Stream的正确格式?

(对不起,但我对Rx不太熟悉)

2 个答案:

答案 0 :(得分:2)

使用rxdart软件包中的combineLatest函数。它采用输入流的最新值,因此无论何时更改区域设置或购物车项目,它都会计算并格式化总费用。

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();
  }
}

免责声明:我并未尝试运行此代码,因此可能会出现错误,但基本思路应该牢固。

答案 1 :(得分:0)

执行此跨平台的最佳人选是intl软件包中的NumberFormat。但是,您仍然必须向其传递语言环境字符串(“ en_US”)和ISO 4217货币代码(“ USD”)。

经过一番挖掘,我在任何Dart程序包中都找不到此信息。 NumberFormat类具有用于从货币代码中查找货币符号(“ $”)的私有地图,但是无法访问该地图的键(即货币代码)。因此,我决定制作一个package,使其可以使用区域设置字符串和货币代码。

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)),
        )
      ],
    );
  }
}