1

Lets take this dart class:

class Subject {
  String a;
  String b;
  String c;
}

Now I want to use it trough a Proxy, to manage lazy loading and synchronization.

I want as well to have default values to use as placeholders while I'm loading the real data from the net. To keep thighs neat and isolated I added another class:

class Fallback implements Subject {
  @override String a = 'a';
  @override String b = 'b';
  @override String c = 'c';
}

Now I have all the bricks I need to write down the "proxy with fallback" class:

class Proxy implements Subject {
  Subject model;
  Subject fallback = Fallback();

  Future<void> slowlyPopulateModel() async => if (model == null) ... // do some async stuff that valorize model

  @override
  String get a {
    slowlyPopulateModel();
    return model?.a ?? fallback.a;
  }
  @override
  set a(String _a) {
    model?.a = a;
    notifyListener();
  }

  // same getters and setters for b and c
}

By overriding get a I can call the slow I/O method if needed and return the placeholder value of my Fallback class. Once the new value is set my overridden set a(String _a) will call notifyListener() that will update my interface.
It is working fine, but I have manually override getter and setter for each field of my class (and they are many).

Does Dart have any trick to do this in a more DRY way?
E.g. some way to inject code to be executed before or after each getter or setter?

Pado
  • 1,497
  • 16
  • 28
  • So your interface has to be able to ask for this value and get notified whenever it changes? You are using Flutter for your interface? – dumazy Jan 08 '20 at 14:58

1 Answers1

2

I would suggest to have a look at Streams for this.

This code example will return an initial value, fetch a new value and notify the listener of it through a stream.

import 'dart:async';

class Subject {
  // Current value, initially at "a"
  String _a = "a";
  
  StreamController<String> _aController = StreamController<String>();
  
  String get a {
    _updateA();
    return _a;
  }
  
  Stream<String> get aStream => _aController.stream;
  
  void _updateA() async {
    String newValue = await _fetchA();
    _a = newValue; // update the current value
    _aController.add(newValue); // push the new value onto the stream for listeners
  }
  
  // return a String after two seconds
  Future<String> _fetchA() async {
    return Future.delayed(
      Duration(seconds: 2),
      () => "New value for _a",
    );
  }
  
  // This closes the stream
  void dispose() {
    _aController.close();
  }
  
}

main(args) {
  final subject = Subject();
  
  // listen to changes
  subject.aStream.listen((value) {
    print("new value of a: $value");
  });
  
  // get current value
  final currentValue = subject.a;
  print("current value of a: $currentValue");
  
}

Output of this example

current value of a: a

(after two seconds) new value of a: New value for _a

Use it in Flutter with a StreamBuilder

StreamBuilder<String>(
  stream: subject.aStream,
  initialData: subject.a,
  builder: (context, snapshot) {
    final valueOfA = snapshot.data;
    return Text("value is $valueOfA");
  }
)

Some of the boilerplate code could be replaced by the BehaviorSubject in RxDart. But this would require another dependency to be imported into the project.

Community
  • 1
  • 1
dumazy
  • 13,857
  • 12
  • 66
  • 113