Open btrautmann opened 1 month ago
This is a tricky one, Ill have to brainstorm a bit to see if there's an ergonomic solution.
For a quick workaround, I would create a .lazyInView
where that has a .setTransaction()
method.
The workaround I've gone with is something like the following:
// Mapping here because we cannot await, otherwise the call to
// txnStore.inView won't be registered as a dependency.
final categoryView = categories.value.mapOr(
(c) => CategoriesWithIds(c.ids), // Invoked if categories.value is AsyncData
const AllCategories(), // Invoked in all other cases
);
final txnsFuture = $txnStore()
.inView(
TxnsView(
dateRange: range,
accounts: const AllAccounts(),
categories: categoryView,
filter: const ExpenseFilter(),
),
)
.toFuture();
The downside to this is that there's a potential for an intermediate "fake/incorrect" state if the setting the user has configured is still loading. In my case, this currently only has the potential to occur in 2 small sections of the app, so the scope of the downside is limited, but still exists. In a lot of other cases I was able to work around the issue entirely by passing an object as the CategoryView
that did not depend on the ids
produced by user action, something like:
// Note that `ExpensesInSelectedView` is passed both here, and...
final $categories = $categoriesStore().inView(const ExpensesInSelectedView()).toFuture();
final $txns = $txnStore()
.inView(
TxnsView(
dateRange: const SelectedDateRange(),
accounts: accountsView,
categories: const ExpensesInSelectedView(), // ...here!
filter: const ExpenseFilter(),
),
)
.toFuture();
In this case, both Future's understand the meaning of ExpensesInSelectedView
and do the same work, producing results influenced by the same inputs. But in the first case above, the ids
are required for the result to be correct, and therefore I need a "fake" intermediate result (AllCategories
) while the categories load.
Commenting to add that the async gap problem becomes particularly challenging when you need code to run once and produce a deterministic result.
This is just a restating of the above, but I have a case where my app offers Home Screen Widgets and to keep these up-to-date, I run the update logic both on process start and then periodically in the background. For process starts, a Beacon.effect
that uses the strategy above (mapOr
in the case of a FutureBeacon
whose resolved data needs to be an input to another FutureBeacon
) works great as the effect
will run again once the initial FutureBeacon
is fully loaded and produce the correct result. That API might look something like:
final (dispose, settled) = Beacon.effect(() => ...);
await settled; // The effect's emission queue is empty
Disclaimer: The above is entirely made up and I have no idea whether such a queue even exists!
However on a background process that wants to re-use this code, it's not ideal to have to "wait" for this effect
to run enough times to produce the correct result (also there's AFAIK no way to say "tell me when this effect has emitted everything and is "up-to-date"... If there was, this use case could probably be solved).
The simplest path forward would be to abstract all the non-beacon-y logic out of the update-widget logic and have one codepath that uses an effect and another that doesn't. The latter would await
the FutureBeacon#toFuture()
wherever needed, ensuring the correct result is produced the first time. But this would still have non-trivial duplication.
Right now what I'm currently doing is running the effect and then adding a few Future.delayed()
calls to essentially purge the effect, but it's hacky and not exactly a perfect science.
IIUC, effects are always alive until disposed so I'm not sure how I would define "settled". I would like to see a sample/abstract of the hacky code.
Description
Within the
README
, the Pitfalls section clearly calls out the potential footgun of registering dependencies following anasync
gap. One thing that's not touched on, though, is the case where you don't really have a choice. Consider the following:To get the above to compile, I need to
await categories
before the second line. But doing that will cause a failure to register the call toinView
which is itself aBeaconFamily
.I am not listing requirements here because I understand this may not be possible/feasible, but I would love to know if there's a workaround I can employ.