Last active
June 30, 2021 21:14
-
-
Save jifalops/3909e55a526c05f7e851355ccc956f9e to your computer and use it in GitHub Desktop.
Creating a BLoC for formatted currency, and using it in Flutter.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:flutter/material.dart'; | |
class StreamHandler<T> extends StreamBuilder<T> { | |
StreamHandler( | |
{@required Stream<T> stream, | |
@required Widget Function(BuildContext context, T data) handler, | |
T initialData, | |
Widget waiting}) | |
: super( | |
stream: stream, | |
initialData: initialData, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return handler(context, snapshot.data); | |
} else if (snapshot.hasError) { | |
return new Text('${snapshot.error}'); | |
} | |
return waiting ?? Center(child: CircularProgressIndicator()); | |
}); | |
} | |
class FutureHandler<T> extends FutureBuilder<T> { | |
FutureHandler( | |
{@required Future<T> future, | |
@required Widget Function(BuildContext context, T data) handler, | |
T initialData, | |
Widget waiting}) | |
: super( | |
future: future, | |
initialData: initialData, | |
builder: (context, snapshot) { | |
if (snapshot.hasData) { | |
return handler(context, snapshot.data); | |
} else if (snapshot.hasError) { | |
return new Text('${snapshot.error}'); | |
} | |
return waiting ?? Center(child: CircularProgressIndicator()); | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/widgets.dart'; | |
abstract class BlocState<T extends StatefulWidget> extends State<T> { | |
/// Create *BLoC*s and setup [StreamSubscription]s. | |
void initBloc(); | |
/// Cancel [StreamSubscription]s and dispose of *BLoC*s. | |
void disposeBloc(); | |
@override | |
@mustCallSuper | |
@protected | |
void initState() { | |
super.initState(); | |
initBloc(); | |
} | |
@override | |
@mustCallSuper | |
@protected | |
void dispose() { | |
disposeBloc(); | |
super.dispose(); | |
} | |
@override | |
@mustCallSuper | |
@protected | |
void didUpdateWidget(StatefulWidget oldWidget) { | |
super.didUpdateWidget(oldWidget); | |
disposeBloc(); | |
initBloc(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
/// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/widgets.dart'; | |
import '...util/bloc_state.dart'; | |
import '...util/async_handlers.dart'; | |
import '...bloc/currency_bloc.dart'; | |
export '...bloc/currency_bloc.dart'; | |
/// Implements the Flutter provider pattern. | |
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; | |
} | |
/// Introduces the [CurrencyProvider] widget and manages its corresponding | |
/// [BlocState]. | |
class CurrencyProviderRoot extends StatefulWidget { | |
CurrencyProviderRoot({Key key, @required this.child}) : super(key: key); | |
final Widget child; | |
_CurrencyProviderRootState createState() => _CurrencyProviderRootState(); | |
} | |
class _CurrencyProviderRootState extends BlocState<CurrencyProviderRoot> { | |
CurrencyBloc bloc; | |
@override | |
Widget build(BuildContext context) => | |
CurrencyProvider(bloc: bloc, child: widget.child); | |
@override | |
void initBloc() => bloc = CurrencyBloc(); | |
@override | |
void disposeBloc() => bloc.dispose(); | |
} | |
/// Wraps the [CurrencyBloc]'s output stream and actually shows the text. | |
class CurrencyWidget extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) { | |
final bloc = CurrencyProvider.of(context); | |
return StreamHandler( | |
stream: bloc.currency, | |
initialData: bloc.lastCurrency, | |
handler: (context, currency) => Text(currency), | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
... | |
class MyApp extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) => MaterialApp( | |
title: 'My App', | |
home: CurrencyProviderRoot( | |
child: MyHomePage(), | |
)); | |
} | |
class MyHomePage extends StatelessWidget { | |
final controller = TextEditingController(); | |
@override | |
Widget build(BuildContext context) { | |
return ListView( | |
children: <Widget>[ | |
TextField(controller: controller), | |
CurrencyWidget(), | |
FlatButton( | |
child: Text('Format Currency'), | |
onPressed: () => CurrencyProvider.of(context) | |
.value | |
.add(double.tryParse(controller.text)), | |
) | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment