How to invoke a listener from another listener? - flutter

I'm using in_app_purchase package and I need to convert/map listener which is listening for List<PurchaseDetails> to another listener as shown below:
class Foo {
Foo() {
InAppPurchase.instance.purchaseStream.listen(_listener);
}
void _listener(List<PurchaseDetails> list) {
// How to pass these ids to `addListener()`
final List<String> ids = list.map((e) => e.productID).toList();
}
void addListener(void Function(List<String>) f) {}
}
This is how I want to use my listener
void main() {
Foo().addListener((List<String> ids) {});
}

Despite what your code comment says, I think what you're really asking for is for the internal _listener to invoke the callback that was previously passed as an argument to addListener (and not for _listener to call addListener directly, which it could just do directly).
Just have addListener save the callback to a member variable and let your internal listener invoke that:
class Foo {
void Function(List<String>)? _listener;
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
var listener = _listener;
if (listener == null) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
listener(ids);
}
void addListener(void Function(List<String>) f) => _listener = f;
}
If you want callers to be able to call addListener multiple times to register multiple callbacks, you would need to store them in a collection (and provide a mechanism to unregister callbacks):
class Foo {
final _listenerMap = <Object, void Function(List<String>)>{};
Foo() {
InAppPurchase.instance.purchaseStream.listen(_internalListener);
}
void _internalListener(List<PurchaseDetails> list) {
if (_listenerMap.isEmpty) {
return;
}
final List<String> ids = list.map((e) => e.productID).toList();
for (var listener in _listenerMap.values) {
listener(ids);
}
}
Object addListener(void Function(List<String>) f) {
var token = Object();
_listenerMap[token] = f;
return token;
}
void removeListener(Object token) {
_listenerMap.remove(token);
}
}

Related

ReactiveX and Dart

I have a class Too
class Too{
bool isLogged = false;
BehaviorSubject suject = BehaviorSubject<bool>();
Too({required this.isLogged}){
suject = new BehaviorSubject<bool>.seeded(isLogged);
}
void login(){
isLogged = true;
suject.sink.add(isLogged);
}
void logOut(){
isLogged = false;
suject.sink.add(isLogged);
}
void dispose(){
suject.close();
}
and I also have the Foo class:
class Foo{
Too _too = new Too(isLogged: false);
_too.stream.listen((event) { print('${event}');});
}
My issue is When the user is calling the login() method of the Too class nothing happens at the level of the Foo class.
What I want to do is that if the user calls the login() method of the Too class and his isLogged attribute is set to true, then this change is done at the level of all the classes that have an attribute of the Too type.
Note: It's much easier to do it with Angular or Ionic using RxJS, but with dart, I don't know how to implement this mechanism.
Foo is not reacting because its listening to a different instance of Too.
The way you have it is that each new instance of Foo creates a new instance of Too. If I understand you correctly, you want all instances of Foo to react to any change to a single instance of Too.
You can use a singleton for this.
class Too {
// one of a few ways to make a singleton in Dart
Too._();
static final _instance = Too._();
factory Too() {
return _instance;
}
final subject = BehaviorSubject<bool>.seeded(isLogged);
static bool isLogged = false;
void login() {
isLogged = true;
subject.sink.add(isLogged);
}
void logOut() {
isLogged = false;
subject.sink.add(isLogged);
}
void dispose() {
subject.close();
}
}
Now you can have any Foo object listen to the same Too instance.
class Foo {
Foo() {
Too().subject.stream.listen((event) {
print('foo $event'); // this will now print whenever a subject from your Too class is updated.
});
}
}
Now for example you could test this by creating a button with this as the onPressed.
onPressed: () {
final foo = Foo(); // just created an example of a Foo object that will
// print the updated value of the Too singleton
Too().login();
},
RxDart is great. However when it comes to reactive programming in Flutter, I suggest checking out Get X as it simplifies a lot of stream based stuff.

RxDart. Listener of BehaviorSubject does not get value after subject.add(value)

I have two classes: CurrencyRepo and CurrencyFetcher. From CurrencyFetcher I try to listen to the behaviorSubject that is in the repo. But when I add values in the behaviorSubject inside the repo, CurrencyFetcher does not get these values. What am I doing wrong?
class CurrencyFetcher implements CurrencyFetcherService {
final CurrencyRepo _currencyRepo;
StreamSubscription currencySubscription;
CurrencyFetcher(this._currencyRepo, this._preferencesService) {
_subscribeToCurrencies();
}
void _subscribeToCurrencies() {
currencySubscription = _currencyRepo
.getCurrenciesStream()
.listen((currencies) => _handleApiCurrencies);
}
Future<void> _handleApiCurrencies(List<ApiCurrency> apiCurrencies) async {
// implemetation
}
}
class CurrencyRepo {
final CurrencyApi _currencyApi;
final BehaviorSubject<List<ApiCurrency>> _currencySubject = BehaviorSubject.seeded([]);
Stream<List<ApiCurrency>> getCurrenciesStream() {
_updateCurrencies();
return _currencySubject.stream;
}
CurrencyRepo(this._currencyApi);
void _updateCurrencies() {
_currencyApi.getCurrencies().then((currencies) {
_currencySubject.add(currencies);
});
}
}
I have checked that the values are added to the stream after the CurrencyFetcher starts to listen. And I have checked that in the moment, when I add new value to the stream, it has a listener. The first time using RxDart, may someone help? :)
Inside the function _subscribeToCurrencies() try to write
_currencyRepo.currenciesStream.listen((currencies) {
_handleApiCurrencies(currencies);
});
instead of
_currencyRepo.currenciesStream.listen(_handleApiCurrencies);

Why can't I set a dynamic property inside a nested function?

I'm trying to create a function that can dynamically set the properties on an object like so:
void main() {
final obj = Item();
obj.update(5);
print(obj.xVal);
}
class ObjectBase {
void _setData(current, newValue) {
current = newValue;
print(current);
}
}
class Item extends ObjectBase {
int _x;
int get xVal => _x;
update(x) {
_setData(_x, x);
}
}
The print statement in _setData works fine, but it doesn't actually appear to change _x, even if it has been passed through. I expected that changing the reference here would update it everywhere.
So why isn't this working and is there a fix?
You can assume that I do have good reason to be calling _setData inside update rather than just implementing the functionality in update.
Update:
A real life example of what i'm trying to achieve
class ViewModel extends ChangeNotifier {
void _setDataFromDependency(current, newValue) {
if (!deepDynamicEquality(current, newValue)) {
current = newValue;
notifyListeners();
}
}
}
class ListScreenViewModel extends ViewModel {
int _localCount = 0;
List<int> _globalList;
ListScreenViewModel();
List<int> get globalList => _globalList;
int get localCount => _localCount;
incrementLocal() {
_localCount++;
notifyListeners();
}
void update(ListStore listStore) {
_setDataFromDependency(_globalList, listStore.globalList);
// if (!deepDynamicEquality(_globalList, listStore.globalList)) {
// _globalList = listStore.globalList;
// notifyListeners();
// }
}
}
An oversimplified workaround is to return the value from _setData . #julemand101 has already answered limitations.
class ObjectBase {
int _setData(current, newValue) {
current = newValue;
print('current: $current');
return current;
}
}
class Item extends ObjectBase {
int _x;
int get xVal => _x;
update(x) {
_x = _setData(_x, x);
}
}

Elegant error handling in Dart like Scala's `Try`

A data class in Dart:
import 'package:validate/validate.dart';
class AuthUser {
final String email, token, username, bio, image;
AuthUser(this.email, this.token, this.username, this.bio, this.image) {
Validate.isEmail(this.email);
}
#override
String toString() {
return 'AuthUser{email: $email, token: $token, username: $username, bio: $bio, image: $image}';
}
}
where Validate.isEmail will throws an Error when failed to match:
static void matchesPattern(String input, RegExp pattern,[String message = DEFAULT_MATCHES_PATTERN_EX]) {
if (pattern.hasMatch(input) == false) {
throw new ArgumentError(message);
}
}
static void isEmail(String input,[String message = DEFAULT_MATCHES_PATTERN_EX]) {
matchesPattern(input,new RegExp(PATTERN_EMAIL),message);
}
Now I want to use an elegant way to new this class.
When using Scala, I can use Try(new AuthUser(...)) and patten-matching it.
And in Dart, first I tried RxDart,
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
Observable.just(AuthUser("email", "token", "username", "bio", "img"))
.doOnError((e, s) => print("oh no"))
.listen((e) => print(e.toString()));
});
}
Not work, the test failed for the error(which means RxDart doesn't catch errors at all!!!)
And I want to try Future, failed also.
And I want to use dartz, but I am worried because there is just one maintainer...
Any advice?
If you are OK with using Future what's wrong with this advice: Using Future.sync() to wrap your code? The code will look like this:
void main() {
var f = Future.sync(() {AuthUser("email", "token", "username", "bio", "img"); });
f.then((v) => print("Value: " + v.toString())).catchError((e) => print("Failure: " +e.toString()));
}
The main trick is that Future.sync effectively enables lazy evaluation of the parameter but you have to pass your parameter wrapped in a function. This is actually the same trick Scala compiler does for Try (i.e. for call-by-name parameters) but takes adding a few brackets around.
If you only want the basic functionality of returning either type based on whether an exception occurred or not then you can easily create a utility class such as below.
Otherwise I recommend #SergGr's answer about using Future.sync since it gives you more monadic like pipeline.
void main() {
Try<Error, void> result = Try.it(() => Validate.isEmail("test-example.com"));
if (result is Success) {
print("Good");
} else if (result is Failure) {
print("Error: " + result.exception().toString());
}
}
typedef TryExec<V> = V Function();
abstract class Try<E extends Error, V> {
static Try<E, V> it<E extends Error, V>(TryExec<V> fn) {
try {
return Try.success(fn());
} catch (e) {
return Try.failure(e);
}
}
static Try<E, V> failure<E extends Error, V>(Error e) {
return new Failure(e);
}
static Try<E, V> success<E extends Error, V>(V v) {
return new Success(v);
}
}
class Failure<E extends Error, V> extends Try<E, V> {
final E _e;
Failure(this._e);
E exception() => _e;
}
class Success<E extends Error, V> extends Try<E, V> {
final V _v;
Success(this._v);
V value() => _v;
}

How to convert callbacks to Rx.Observable?

If an external library offers only to register a callback instead of an event, what is the best way to create an Observable from it?
If it where an event I could use Observable.FromEventPattern but in this case the only idea I have is to use a Subjectand queue events in it on each callback.
Is there any better way to do this?
Use Observable.Create. Here's an example:
void Main()
{
var target = new SampleCallbacker();
var actionB = new Action<int>(i => Console.WriteLine($"{i} * {i} = {i * i}."));
target.Register(actionB);
var observable = Observable.Create<int>(observer =>
{
var action = new Action<int>(i => observer.OnNext(i));
target.Register(action);
return () => target.Unregister(action);
});
var subscription = observable.Subscribe(i => Console.WriteLine($"From observable: {i} was fired."));
target.Fire(1);
target.Fire(2);
target.Fire(3);
Console.WriteLine("Unsusbscribing observable...");
subscription.Dispose();
target.Fire(4);
target.Fire(5);
}
class SampleCallbacker
{
private List<Action<int>> _actions = new List<System.Action<int>>();
public void Register(Action<int> action)
{
_actions.Add(action);
}
public void Unregister(Action<int> action)
{
while (_actions.Remove(action))
{} //loop remove
}
public void Fire(int i)
{
foreach (var action in _actions)
{
action(i);
}
}
}