Exposing BLOC streams via fields, methods, or getter - oop

I am using the BLOC pattern for my latest Flutter app and I started out using something like this for my output streams:
class MyBloc {
// Outputs
final Stream<List<Todo>> todos;
factory MyBloc(TodosInteractor interactor) {
final todosController = BehaviorSubject<List<Todo>>()
..addStream(interactor.todos);
return MyBloc._(todosController);
}
MyBloc._(this.todos);
}
but slowly I found myself doing something more like this, using a method (or getter) after awhile:
class MyBloc {
final TodosInteractor _interactor;
// Outputs
Stream<List<Todo>> todos(){
return _interactor.todos;
}
MyBloc(this._interactor) { }
}
For people who want to see... getter for todos in TodosInteractor:
Stream<List<Todo>> get todos {
return repository
.todos()
.map((entities) => entities.map(Todo.fromEntity).toList());
}
When I look at the differing code, I see that the first example uses a field versus a method to expose the stream but I couldn't figure out why I would choose one over the other. It seems to me that creating another controller just to push through the stream is a little much... Is there a benefit to this other than being immutable in my todos stream definition? Or am I just splitting hairs?

Well maybe this will not be a best answer but it is a good practice expose your output stream using get methods. Below a example of a bloc class that i have written to a project using RxDart.
class CityListWidgetBloc {
final _cityInput = PublishSubject<List<Cidade>>();
final _searchInput = new PublishSubject<String>();
final _selectedItemsInput = new PublishSubject<List<Cidade>>();
// exposing stream using get methods
Observable<List<Cidade>> get allCities => _cityInput.stream;
Observable<List<Cidade>> get selectedItems => _selectedItemsInput.stream;
List<Cidade> _searchList = new List();
List<Cidade> _selectedItems = new List();
List<Cidade> _mainDataList;
CityListWidgetBloc() {
//init search stream
_searchInput.stream.listen((searchPattern) {
if (searchPattern.isEmpty) {
_onData(_mainDataList); // resend local data list
} else {
_searchList.clear();
_mainDataList.forEach((city) {
if (city.nome.toLowerCase().contains(searchPattern.toLowerCase())) {
_searchList.add(city);
}
});
_cityInput.sink.add(_searchList);
}
});
}
//getting data from firebase
getCity( {#required String key}) {
FirebaseStateCityHelper.getCitiesFrom(key, _onData);
//_lastKey = key;
}
searchFor(String pattern) {
_searchInput.sink.add(pattern);
}
void _onData(List<Cidade> list) {
_mainDataList = list;
list.sort((a, b) => (a.nome.compareTo(b.nome)));
_cityInput.sink.add(list);
}
bool isSelected(Cidade item) {
return _selectedItems.contains(item);
}
void selectItem(Cidade item) {
_selectedItems.add(item);
_selectedItemsInput.sink.add(_selectedItems);
}
void selectItems(List<Cidade> items){
_selectedItems.addAll( items);
_selectedItemsInput.sink.add( _selectedItems );
}
void removeItem(Cidade item) {
_selectedItems.remove(item);
_selectedItemsInput.sink.add(_selectedItems);
}
dispose() {
_cityInput.close();
_searchInput.close();
_selectedItemsInput.close();
}
}

Related

Exclude specific products from Product Indexer in Shopware 6

We have four specific products with a massive amount of variants. When running the Product Indexer we run out of memory because of these products.
So we want to exclude these specific products from the Product Indexer Job.
My first approach was to use the ProductIndexerEvent, but the event is dispatched at the end of the handle() method :
(vendor/shopware/core/Content/Product/DataAbstractionLayer/ProductIndexer.php:187),
which is probably too late.
What is the best approach to implement that behaviour?
I would advise against excluding products from being indexed. There's business logic relying on the data being indexed.
If you're confident in what you're doing and know about the consequences, you could decorate the ProductIndexer service.
<service id="Foo\MyPlugin\ProductIndexerDecorator" decorates="Shopware\Core\Content\Product\DataAbstractionLayer\ProductIndexer">
<argument type="service" id="Foo\MyPlugin\ProductIndexerDecorator.inner"/>
</service>
In the decorator you would have to deconstruct the original event, filter the WriteResult instances by excluded IDs and then pass the reconstructed event to the decorated service.
class ProductIndexerDecorator extends EntityIndexer
{
const FILTERED_IDS = ['9b180c61ddef4dad89e9f3b9fa13f3be'];
private EntityIndexer $decorated;
public function __construct(EntityIndexer $decorated)
{
$this->decorated = $decorated;
}
public function getDecorated(): EntityIndexer
{
return $this->decorated;
}
public function getName(): string
{
return $this->getDecorated()->getName();
}
public function iterate($offset): ?EntityIndexingMessage
{
return $this->getDecorated()->iterate($offset);
}
public function update(EntityWrittenContainerEvent $event): ?EntityIndexingMessage
{
$originalEvents = clone $event->getEvents();
if (!$originalEvents) {
return $this->getDecorated()->update($event);
}
$event->getEvents()->clear();
/** #var EntityWrittenEvent $writtenEvent */
foreach ($originalEvents as $writtenEvent) {
if ($writtenEvent->getEntityName() !== 'product') {
$event->getEvents()->add($writtenEvent);
continue;
}
$results = [];
foreach ($writtenEvent->getWriteResults() as $result) {
if (\in_array($result->getPrimaryKey(), self::FILTERED_IDS, true)) {
continue;
}
$results[] = $result;
}
$event->getEvents()->add(new EntityWrittenEvent('product', $results, $event->getContext()));
}
return $this->getDecorated()->update($event);
}
public function handle(EntityIndexingMessage $message): void
{
$data = array_diff($message->getData(), self::FILTERED_IDS);
$newMessage = new ProductIndexingMessage($data, $message->getOffset(), $message->getContext(), $message->forceQueue());
$this->getDecorated()->handle($newMessage);
}
public function getTotal(): int
{
return $this->getDecorated()->getTotal();
}
public function getOptions(): array
{
return $this->getDecorated()->getOptions();
}
}

Modelina Csharp Generator Add Inheritance

I am playing around with asyncapi/modelina CSharpGenerator. I would like to add inheritance to the generated class something like this
public class UserCreated: IEvent
{
}
Is that possible? Can we add additional dependencies other than the generated ones?
Inheritance is, unfortunately, one of those features that have gotten put on the backburner, and still is.
Fortunately, it is possible to accomplish it, but it does require you to overwrite the entire rendering behavior, which might not be maintainable in the long run. You can find the full example in this PR: https://github.com/asyncapi/modelina/pull/772
const generator = new CSharpGenerator({
presets: [
{
class: {
// Self is used to overwrite the entire rendering behavior of the class
self: async ({renderer, options, model}) => {
//Render all the class content
const content = [
await renderer.renderProperties(),
await renderer.runCtorPreset(),
await renderer.renderAccessors(),
await renderer.runAdditionalContentPreset(),
];
if (options?.collectionType === 'List' ||
model.additionalProperties !== undefined ||
model.patternProperties !== undefined) {
renderer.addDependency('using System.Collections.Generic;');
}
const formattedName = renderer.nameType(model.$id);
return `public class ${formattedName} : IEvent
{
${renderer.indent(renderer.renderBlock(content, 2))}
}`;
}
}
}
]
});
What is happening here is that we create a custom preset for the class renderer and overwrite the entire rendering process of itself.
This will generate based on this input:
public class Root : IEvent
{
private string[] email;
public string[] Email
{
get { return email; }
set { email = value; }
}
}
Regarding dependencies, please see https://github.com/asyncapi/modelina/blob/master/docs/presets.md#adding-new-dependencies. You can do this in the self preset hook.
You can read more about the presets here: https://github.com/asyncapi/modelina/blob/master/docs/presets.md

Pass API Data to GetX Controller from class

How can I pass the decoded Data from my Api to my GetX Controller?
Here is my Class "Germany" and my fetchGermany() Function.
Future<Germany> fetchGermany() async {
final response =
await get(Uri.parse('https://api.corona-zahlen.org/germany'));
if (response.statusCode == 200) {
return Germany.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to get data');
}
}
class Germany {
int cases;
int deaths;
int recovered;
double weekIncidence;
double casesPer100k;
int casesPerWeek;
Germany(
{required this.cases,
required this.deaths,
required this.recovered,
required this.weekIncidence,
required this.casesPer100k,
required this.casesPerWeek});
factory Germany.fromJson(Map<String, dynamic> json) {
return Germany(
cases: json["cases"],
deaths: json["deaths"],
recovered: json["recovered"],
weekIncidence: json["weekIncidence"],
casesPer100k: json["casesPer100k"],
casesPerWeek: json["casesPerWeek"]);
}
}
Here is my GetX controller which is empty at the moment:
class DetailController extends GetxController {
}
So basically I just want to be able to acceess this data:
cases: json["cases"],
deaths: json["deaths"],
recovered: json["recovered"],
weekIncidence: json["weekIncidence"],
casesPer100k: json["casesPer100k"],
casesPerWeek: json["casesPerWeek"]
While I agree with #DarShan that you don't necessarily need a GetXController here, I still would just for the simple sake of using a stateless widget over a stateful widget. If for no other reason than less cluttered UI code and separating business logic.
Also not sure if your Api call function is global or if that's just how you have it in your example, but if it is global I'd create a helper class.
class ApiHelper {
Future<Germany> fetchGermany() async {
final response =
await get(Uri.parse('https://api.corona-zahlen.org/germany'));
if (response.statusCode == 200) {
return Germany.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to get data');
}
}
}
Then your GetX class can look like this.
class DetailController extends GetxController {
Germany germany;
#override
void onInit() async {
super.onInit();
final apiHelper = ApiHelper();
germany = await apiHelper.fetchGermany();
}
}
And here's an example using GetView widget which is just a stateless widget with a built in controller of the type you provided without having to find it.
class GermanyExample extends GetView<DetailController> {
#override
Widget build(BuildContext context) {
// access the initialized Germany object with controller.germany
return // the rest of your UI
}
}
Why not directly use the returned Germany object?
I don't see a need to use GetxController here.
Can be simply used as:
Germany _germany;
#override
void initState() {
super.initState();
fetchGermanyData();
}
fetchGermanyData() async {
final fetchedData = await fetchGermany();
setState(() => _germany = fetchedData);
}
/// use ? : operator to show relevant UI in the build method.

How to expand the Start task with new scenarios

I've just learned about serenity-js and am giving it a go. I'm following the tutorial and noticed the follow example:
james.attemptsTo(
Start.withAnEmptyTodoList(),
AddATodoItem.called('Buy some milk')
)
The task for Start:
export class Start implements Task {
static withATodoListContaining(items: string[]) { // static method to improve the readability
return new Start(items);
}
performAs(actor: PerformsTasks): PromiseLike<void> { // required by the Task interface
return actor.attemptsTo( // delegates the work to lower-level tasks
// todo: add each item to the Todo List
);
}
constructor(private items: string[]) { // constructor assigning the list of items
} // to a private field
}
I really like this syntax and would like to continue this setup with more starting scenario's.
What would be the proper approach to accomplish this?
For anyone having the same question this is how I resolved it (found a similar setup going through the serenity-js repo):
// Start.ts
export class Start {
public static withATodoListContaining = (items: string[]): StartWithATodoListContaining => new StartWithATodoListContaining(items);
}
// StartWithATodoListContaining.ts
export class StartWithATodoListContaining implements Task {
static withATodoListContaining(items: string[]) {
return new StartWithATodoListContaining(items);
}
performAs(actor: PerformsTasks): PromiseLike<void> {
return actor.attemptsTo(
// todo: add each item to the Todo List
);
}
constructor(private items: string[]) {
}
}

How to transform a blocking code into a reactive code

I looking for a good way to code this:
MyCloudDTO saveInCloud(MyCloudDTO dto) {
//sync http call :-(
return cloudService.save(dto);
}
MyData saveInDb(MyData data) {
//database call
return repository.save(data);
}
MyDTO save(MyDTO dto) {
MyCloudDTO cloudDTO = mapper.map(dto);
dto.setCloudId(saveInCloud(dto).getId());
MyData data = mapper2.map(dto);
dto.setDbId(saveInDb(data).getId());
return dto;
}
With Spring WebFlux and Monos.
The main problem here is that I want to keep the base dto during all the process because the mappers map only the properies they need and leave the the rest.
I found a way to do that but I'm not sure that's the correct/best way to do:
Mono<MyCloudDTO> saveInCloud(MyCloudDTO dto) {
return reactiveCloudService.save(dto);
}
Mono<MyData> saveInDb(MyData data) {
return reactiveRepository.save(data);
}
Mono<MyDTO> save(MyDTO dto) {
Mono<MyDTO> baseMono = Mono.just(dto);
Mono<MyCloudDTO> saveCloudMono = Mono.just(dto)
.map(mapper::map)
.flatMap(this::saveInCloud);
Mono<MyDTO> mergeAfterCloudSave = Mono.zip(
baseMono,
saveCloudMono,
(base, cloudDto) -> base.idCloud(cloudDto.getId())); //the setter return this
Mono<MyData> saveDbMono = mergeAfterCloudSave
.map(mapper2::map)
.flatMap(this::saveInCloud);
Mono<MyDTO> mergeAfterDbSave = Mono.zip(
mergeAfterCloudSave,
saveDbMono,
(base, dbData) -> base.idDb(data.getId())); //the setter return this
return mergeAfterDbSave;
}
Thank you very much!
Saveriu