Flutter: Best practice for nested async HTTP calls - flutter

I'm trying to load the result of two Http requests into a list-item (the second request is a get detail about the first request result item).
The first request gets a list of cryptocurrencies.
The second request collects more information about that currency.
The reason why I want to accomplish this in two separate requests is,
the data is on different sources.
after I got the list of currencies I want to show a process circle, which means "stay calm, details are loading"
What I already tried is ;
One stateful-widget with two methods ( both are calling setState) it was catastrophic. Endless loop.
One base stateful-widget and a second child stateful-widget in the build method. Worked but whenever I scroll up or down, the list re-renders its items, and the child stateful-widget makes the same getPrice HTTP request again and again.
One inherited-widget and one stateless-widget. Had to use the context for both of the HTTP request methods. It caused endless loops again.
Currently, a part of my Brain yells "Redux is the only way to handle this in a proper way"
Do you have any suggestions how to handle this?
Thank you in advance.

void getOwnedCoins() async {
CoinsList cList = await ResourceCoins().list();
setState(() {
list = cList;
items = cList.result.length;
});
getCoinSummary(cList);
}
void getCoinSummary(CoinsList coins) {
coins.result.forEach((Coin coin) async {
CoinSummary cs = await ResourceCoinSummary().summary(market: {'market': 'btc-' + coin.currency});
coin.summary = cs;
setState(() {
list = coins;
});
});
}
That's how I solved it. My mistake was to call the getCoinSummary in the ListView.builder. However, I feel not comfortable with that setState in getCoinSummary. Every time I get details from server I re-render the whole list. Is it how it should be?

Related

in Flutter, make a list of api call inside one api call

In one of my flutter app, at first I want to call an api, which will return a list of item, and the item will be shown in a ListView. I also need to call another api for each item of the ListView to fetch description of that item and show the description to each item according to their id. How can I resolve this scenario. In RxJava, there is an operator called flatmap which did the same things without any hassle. But in flutter, How can I implement this. Here is my 2 function
class HomeRepositoryImpl extends HomeRepository {
HomeGraphQLService homeGraphQLService;
HomeMapper homeMapper;
HomeRepositoryImpl(HomeGraphQLService homeGraphQLService, HomeMapper homeMapper) {
this.homeGraphQLService = homeGraphQLService;
this.homeMapper = homeMapper;
}
#override
Future<List<Course>> getAllCourseOf(String className, String groupName) async {
final response = await homeGraphQLService.getAllCourseOf(className, groupName);
return homeMapper.toCourses(response).where((course) => course.isAvailable);
}
#override
Future<CourseProgressAndPerformance> getProgressAndPerformanceAnalysisOf(String subjectCode) async {
final response = await homeGraphQLService.getProgressAndPerformanceAnalysisOf(subjectCode);
return homeMapper.toProgressAndPerformance(response);
}
}
In the above class, first I call getAllCourseOf() function to get a list of course and show them in list view. I need to call getProgressAndPerformanceAnalysisOf(courseId) to fetch description of each item and show the description in each item of that list.
So what is recommended way to do so.
thanks in advance
I'm not sure on how the listing would be presented, my guess is you're looking for Stream and asyncMap()
Here's an example implementation that would give you a list of CourseProgressAndPerformance, this is the direction I'd investigate.
var perfList = Stream
.fromIterable(listOfCourses)
.asyncMap((course) => getProgressAndPerformanceAnalysisOf(courseId))
.toList();

My useQuery hook is refetching everytime its called. I thought it is suppose to hand back the cache?

I'm a little confused here. I thought react-query, when using useQuery will hand back 'cache' n subsequent calls to the same "useQuery". But everytime I call it it, it refetches and makes the network call.
Is this the "proper way" to do this? I figured it would just auto hand me the "cache" versions. I tried extending staleTime and cacheTime, neither worked. Always made a network call. I also tried initialData with the cache there.. didn't work.
SO, I am doing the following, but seems dirty.
Here is the what I have for the hook:
export default function useProducts ({
queryKey="someDefaultKey", id
}){
const queryClient = useQueryClient();
return useQuery(
[queryKey, id],
async () => {
const cachedData = await queryClient.getQueryData([queryKey, id]);
if (cachedData) return cachedData;
return await products.getOne({ id })
}, {
enabled: !!id
}
);
}
This is initiated like so:
const { refetch, data } = useProducts(
{
id
}
}
);
I call "refetch" with an onclick in two diff locations.. I'd assume after I retrieve the data.. then subsequent clicks will hand back cache?
I’m afraid there are multiple misconceptions here:
react query operates on stale-while-revalidate, so it will give you data from the cache and then refetch in the background. You can customize this behavior by setting staleTime, which will tell the library how long the data can be considered fresh. No background updates will happen.
when you call refetch, it will refetch. It’s an imperative action. If you don’t want it, don’t call refetch.
you don’t need to manually read from the cache in the queryFn - the library will do that for you.

How can I asynchronously stream loaded objects from a list of futures in Dart

I have a list of objects that can be loaded by calling the function object.load().
This function returns a loaded version of the same object asynchronously.
I want call the load() funcion of all objects in a list at the same time and stream the loaded versions as soon as they finish loading.
The code below works but the program is loading one object at a time.
Sender:
Stream<ImageIconModel> streamLoadedIcons() async* {
for (var i = 0; i < imageIconModels.length; i++) {
yield await imageIconModels[i].load().then((loadedIconModel) {
return loadedIconModel;
});
}
}
Receiver:
await for (var loadedIcon in streamLoadedIcons()) {
final var result = doSomething(loadedIcon);
yield result;
}
The main problem is:
In the sender, if I await each load() call, it will do every step in the loop awaiting the load() to finish.
But if I remove the "await", I would be returning a future, not the loaded icon.
You need Stream.fromFutures.
final loadedIconModelStream = Stream.fromFutures([
for (final iconModel in imageIconModels) iconModel.load(),
]);
Both #hacker1024 an #pskink answers successfully answered my question!
But neither one worked as it supposed to and I think I discovered why.
I substituted the load() method for a Future.delayed(duration: random), and then the program worked as it intended to.
So what I think happened is that probably the lib I'm using to load the images (multi_image_picker: ^4.7.14) is accessing the phone files synchronously.
So even if I try to load every image at same time, it will do the task synchronously and return every image at the order I called them to load.
Thank you both for the answer!

While using ag-grid dataSurce, how can I know if the getRows function being called due to scrolling or filtering

As we know, in infinite rowModelType, we have to set dataSource for the ag-grid.
const dataSource = {
rowCount: count
getRows: (params: IGetRowsParams) => this.getRows(params, [])
};
this.gridApi.setDatasource(dataSource);
Now, dataSource.getRows method is called whenever there is need to fetch the rows in the grid (due to scrolling) OR the filter is changed.
I need to decide how many ajax calls need to be made depending on this reason. Below code blocks explains this.
private getRows(params: IGetRowsParams, data: any) {
// two ajax calls can be made from here
// 1. getCount
// 2. getData
// if this getRows function is called due to scrolling in the grid,
// I just want to call getData - no need to call getCount as I already know it
// if this is called due to change in filter,
// I need to call getCount as well as the no of rows will be different
// How can I know here due to which above mentioned reasons, getRows is getting called?
}
Is there anyway to know it within getRows function?

Vala force refresh progressbar

I've made an aplication with vala where at some point I have to process a lot of files. I've created a window to choose a folder and then I get the paths of files and make some proces on them.
I've added a progress bar to this window to show how many files have been processed but for some reason it remains always empty.
Code about window:
this.files_window = new Gtk.Window();
this.files_window.window_position = Gtk.WindowPosition.CENTER;
this.files_window.destroy.connect (Gtk.main_quit);
// VBox:
Gtk.Box vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
this.files_window.add (vbox);
// Buttons to open and close
Gtk.Button cancel = new Gtk.Button.with_label ("Cancel");
Gtk.Button select = new Gtk.Button.with_label ("Select");
vbox.add (select);
vbox.add (cancel);
// proogress bar
this.progress_bar = new Gtk.ProgressBar();
vbox.add(this.progress_bar);
// conect select to method do_stuff
select.clicked.connect (do_stuff);
this.files_window.show_all ();
As you can see, I connect the button "select" to the method "do_stuff" where I get the paths of selected files and make some process.
I update correctlly the fraction of the progres bar because I've added some prints to know if the value is correct and it is. It's just that the windows is not refreshing, possibly because all the work it is doing with the process of the files. Here is the code about do_stuff() method:
// some proces to get paths of files in the list sfiles
double fraction = 0.0;
this.progress_bar.set_fraction (fraction);
int processed_files = 0;
foreach (string sfile in sfiles) {
do_some_proces_to_file(sfile);
processed_files += 1;
fraction = (double)processed_files/(double)sfiles.length;
this.progress_bar.set_fraction (fraction);
stdout.printf("Real fraction: %f\n", this.progress_bar.get_fraction());
}
The printf shows that the value of the progres bar is being updated but in the window the bar is always empty.
Am I missing something? Is it the correct way to do the progres bar? Should I made another thread to do the stuff?
As #nemequ says, your code is blocking the main loop thread (which handles both user input and scheduling/drawing widget updates), hence it the progress bar is not updated until the method completes.
Using a thread is one way solve the problem, however using threads can lead to a lot of bugs however since it can be difficult to make even simple interactions between threads safe.
An async method avoids this by interleaving the code with the other work being done by the main loop. An async version of your do_stuff() would be pretty straight-forward to write, simply declare it async and put a yield in the for loop somewhere:
public async void do_stuff() {
...
foreach (string sfile in sfiles) {
// all of this is as before
do_some_proces_to_file(sfile);
processed_files += 1;
fraction = (double)processed_files/(double)sfiles.length;
this.progress_bar.set_fraction (fraction);
// Schedule the method to resume when idle, then
// yield control back to the caller
Idle.add(do_stuff.callback);
yield;
}
}
You can then kick it off from your click handler by calling: do_stuff.begin().
Unless there is some relevant code you're not showing, you're blocking the main loop. One option would be to do everything in a thread, and use an idle callback to update the UI. The basic idea is something like:
new GLib.Thread<void*>("file-processor", () => {
foreach (string sfile in sfiles) {
/* do stuff */
GLib.Idle.add(() => {
/* Update progress */
return false;
});
}
return null;
});
Depending on your application you may need to add a mutex to avoid race conditions. You may also need to add some logic for canceling the operation.
A better option might be to use a GLib.ThreadPool. You'd still want to update the UI from an idle callback, but this would allow each task to execute in parallel, which could provide a significant speed-up.
If I were you I'd probably wrap it all up in an async function to keep the API tidy, but you don't really have to.