maxim-saplin / data_table_2

In-place substitute for Flutter's DataTable and PaginatedDataTable with fixed/sticky header and extra features
https://pub.dev/packages/data_table_2
BSD 3-Clause "New" or "Revised" License
204 stars 136 forks source link

Async-table version? and ... another problems #31

Closed ghost closed 2 years ago

ghost commented 3 years ago

Maxim, When do you think the async-table version will be available? I have noticed that I have problems with the implementation simulation that I had done to take the rows by pages from server, sometimes it does not work correctly, in addition, I would like to be able to initially place the PaginatedDataTable2 on the last page and it has not been possible. Thank you very much for all the help you are giving me. Regards, Jose

maxim-saplin commented 3 years ago

Hi, new ETA is sometime this week)

ghost commented 3 years ago

Wonderful. Keep me informed. Thank you very much Maxim. 馃挴 馃

maxim-saplin commented 3 years ago

Hi @Josua2012 , eventually I've completed my view on asyn data source and data table. Preview is available on 'async-table' branch: https://github.com/maxim-saplin/data_table_2/tree/async-table

There're a number of examples in async_paginated_data_table2.dart

ghost commented 3 years ago

Hi @maxim-saplin Tomorrow I will do tests and tell you my conclusions. Thank you very much for your work and help. :) Regards, Jose

ghost commented 3 years ago

Hi @maxim-saplin , I see some things that could be improved. Check out the following video: https://recordit.co/UC5jOuc0Bj When the height of the AsyncPaginatedDataTable2 decreases, the number of visible rows decreases, so there should be no rows reloading. I suppose that when the height of the AsyncPaginatedDataTable2 is increased, there is a debounce of the load from the server, however, as you will see in the video, there are several reloads of the rows, there should only be one reload. What do you think of these comments? Thanks.

ghost commented 3 years ago

Selecting "custom pager" with asynpaginatedDataTable2 causes console errors (Demo):

鈺愨晲鈺愨晲鈺愨晲鈺愨晲 Exception caught by foundation library 鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲
setState() called after dispose(): _PageNumberState#236f5(lifecycle state: defunct, not mounted)
鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲

Also, the custom pager does not disable the buttons if the first or last page is reached, for the purposes of the demo it would be very good to include this functionality.

ghost commented 3 years ago

If "custom pager" is selected with asynpaginatedDataTable2, the "Go to row 5" button does not work correctly. You can see the following video: https://recordit.co/6zUBwKih7D First click on the "Go to row 25" button and then click on the "Go to row 5" button, as you will see the rows are not loaded.

ghost commented 3 years ago

I have modified the async_paginated_data_table2 file in the example to make it go directly to the last page at startup, however it doesn't work. I include the modified file for you to investigate if I have done something incorrectly. async_paginated_data_table2.zip Thanks for your help.

ghost commented 3 years ago

Theloading property of theAsyncPaginatedDataTable2 produces a slightly strange effect when the data comes from the server very quickly. When switching pages the canvas of the AsyncPaginatedDataTable2 is displayed white very quickly before drawing the new rows. This even affects the title and action buttons of the AsyncPaginatedDataTable2. If a system could be made through which, if the data from the server comes very quickly, do not try to show the loading (CircularProgressIndicator or the implementation that has been made). Also, loading should only affect the rows area, never the title and the buttons in the title area. Check out the following video (loading active): https://recordit.co/77izAboUlx Watch the following video (deactivating the loading parameter): https://recordit.co/b0QvJGnY1N

Even without using theloading parameter, changing the height of the AsyncPaginatedDataTable2 produces very unwanted refreshments. In the following video you can even see the CircularProgressIndicator problem. Check out the following video: https://recordit.co/dIj6bxrR92

Thanks for your help.

ghost commented 3 years ago

For some reason that I do not understand, on a few occasions, the last row of the AsyncPaginatedDataTable2 is not drawn and only the CircularProgressIndicator are shown and that line never ends. As many CircularProgressIndicator are displayed as columns the AsyncPaginatedDataTable2 has. Look at the following image (it's a drill). If it happens to me again I will take a real screenshot. 2021-08-11_17-14-19 Finally I have been able to reproduce the problem and I have recorded it in the following video: https://recordit.co/MTFstObVy2 It generally occurs if I remove the loading parameter, hot reload, show the AsyncPaginatedDataTable2, re-include the loading parameter in the widget, hot reload, show the AsyncPaginatedDataTable2 and then the problem occurs.

ghost commented 3 years ago

The refreshDataSource() method is very cool and can be used for many things, but it should readjust the displayed page in case it now has no rows to display anymore. For example, some buttons can be used to create a filter, after pressing the button, when doing refreshDataSource() the page that was being displayed may not contain rows, so it should show the closest page with rows. If there are no rows, it should be put on the first page. Another very normal circumstance that can occur is that, from another computer the rows that are being displayed from this computer have been deleted, when doing a refreshDataSource() the AsyncPaginatedDataTable2 must be placed on the closest page that has any row and if there are no rows to display placed on the first page. You can see the problem in the following video: https://recordit.co/rb4kkaciK9 Thanks for your help.

ghost commented 3 years ago

If I was placed on a certain page (for example the second page), by means of onPageChanged I save the initialFirstRowIndex, and when I return to the same window that shows that AsyncPaginatedDataTable2 I assign its initialFirstRowIndex parameter to the value that I have saved so that it is placed in the same place in the one that was, only the CircularProgressIndicators are shown in the columns, it can only be refreshed, and yet it is not placed in the initialFirstRowIndex indicated initially. Check out the following video: https://recordit.co/VuG7mOWTwQ Thanks for your help.

ghost commented 3 years ago

The onPageChanged event does not initially run on the AsyncPaginatedDataTable2.

maxim-saplin commented 3 years ago

@Josua2012 thanks for extensive feedback. Will look onto the issues you've highlighted later this week

ghost commented 3 years ago

Ok. Thanks Maxim.

ghost commented 3 years ago

If I go to the AsyncPaginatedDataTable2 on the second page, for example, and I make the widget grow in height, it starts to show the first page, in my opinion, I think that the first row should always be the same, it should be immutable, and only add rows to the bottom of the page, only in the case that all rows can be displayed on a single page, the widget should change the first row of the page. Even if it could be optimized, when growing the widget in height, only the new rows should be requested from the server. And when decreasing in height, the lines should not be requested from the server again. Being even more optimal, the AsyncPaginatedDataTable2 could keep a small cache of rows so that, when changing the height of the page, it would not have to ask the server for the rows again. Check out the following video: https://recordit.co/FGsyWlYWOv

Thanks for your help.

maxim-saplin commented 3 years ago

@Josua2012 there's an update in the branch, fixed quite a few bugs and created an example with opening last page upon displaying AsyncPaginatedDataTable2. You can give it a try. Will comment on the messages above in separate posts.

maxim-saplin commented 3 years ago

Hi @maxim-saplin , I see some things that could be improved. Check out the following video: https://recordit.co/UC5jOuc0Bj When the height of the AsyncPaginatedDataTable2 decreases, the number of visible rows decreases, so there should be no rows reloading. I suppose that when the height of the AsyncPaginatedDataTable2 is increased, there is a debounce of the load from the server, however, as you will see in the video, there are several reloads of the rows, there should only be one reload. What do you think of these comments? Thanks.

It is a by-design behaviour, I don't see the combination of auto-rows, resizable screen and async as the most frequent/important scenariou and thus implemented the easieset wirkaround in place to allow auto-rows working with async tab;e.

maxim-saplin commented 3 years ago

Selecting "custom pager" with asynpaginatedDataTable2 causes console errors (Demo):

鈺愨晲鈺愨晲鈺愨晲鈺愨晲 Exception caught by foundation library 鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲
setState() called after dispose(): _PageNumberState#236f5(lifecycle state: defunct, not mounted)
鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲鈺愨晲

Also, the custom pager does not disable the buttons if the first or last page is reached, for the purposes of the demo it would be very good to include this functionality.

Should be fixed now.

maxim-saplin commented 3 years ago

If "custom pager" is selected with asynpaginatedDataTable2, the "Go to row 5" button does not work correctly. You can see the following video: https://recordit.co/6zUBwKih7D First click on the "Go to row 25" button and then click on the "Go to row 5" button, as you will see the rows are not loaded.

Now async table will always align the top row to the beginning of the page, i.e. if page size is 10 and you request row 5, async table will set first row to 5 - unlike sync table which allows jumping to arbitrary rows this one is more strict and can't break pages.

maxim-saplin commented 3 years ago

I have modified the async_paginated_data_table2 file in the example to make it go directly to the last page at startup, however it doesn't work. I include the modified file for you to investigate if I have done something incorrectly. async_paginated_data_table2.zip Thanks for your help.

See my example which is working

maxim-saplin commented 3 years ago

Theloading property of theAsyncPaginatedDataTable2 produces a slightly strange effect when the data comes from the server very quickly. When switching pages the canvas of the AsyncPaginatedDataTable2 is displayed white very quickly before drawing the new rows. This even affects the title and action buttons of the AsyncPaginatedDataTable2. If a system could be made through which, if the data from the server comes very quickly, do not try to show the loading (CircularProgressIndicator or the implementation that has been made). Also, loading should only affect the rows area, never the title and the buttons in the title area. Check out the following video (loading active): https://recordit.co/77izAboUlx Watch the following video (deactivating the loading parameter): https://recordit.co/b0QvJGnY1N

Even without using theloading parameter, changing the height of the AsyncPaginatedDataTable2 produces very unwanted refreshments. In the following video you can even see the CircularProgressIndicator problem. Check out the following video: https://recordit.co/dIj6bxrR92

Thanks for your help.

That is by design behaviour of the example and has nothing to do with the async table. If you check what gets passed to loading property, the widget first shows a white translucent shade and after 1 second and shows the spinner. So the idea is that when async data fetch is happening I tell the async table to display _Loading widget which covers the currently visible widget and blocks any actions and if it takes to long to load data the loading indication becomes explicit with spinner displayed. That is the UX approach I've chosen from the example. You're free to not set the loading property or pass there smth else as you deem relevant.

maxim-saplin commented 3 years ago

For some reason that I do not understand, on a few occasions, the last row of the AsyncPaginatedDataTable2 is not drawn and only the CircularProgressIndicator are shown and that line never ends. As many CircularProgressIndicator are displayed as columns the AsyncPaginatedDataTable2 has. Look at the following image (it's a drill). If it happens to me again I will take a real screenshot. 2021-08-11_17-14-19 Finally I have been able to reproduce the problem and I have recorded it in the following video: https://recordit.co/MTFstObVy2 It generally occurs if I remove the loading parameter, hot reload, show the AsyncPaginatedDataTable2, re-include the loading parameter in the widget, hot reload, show the AsyncPaginatedDataTable2 and then the problem occurs.

Row with circular indicator is displayed whenever there's not data for the given row index in the data soruce. Could be a bug in the async table widget (tried to fix as many as possible) or could be a bug int the data source implementation, it simple doesn't load all the required rows. Please try the new version and see if you catch such situations. If you do - try to diagnose the steps and demo them with an example for me to help you with the fix.

maxim-saplin commented 3 years ago

The refreshDataSource() method is very cool and can be used for many things, but it should readjust the displayed page in case it now has no rows to display anymore. For example, some buttons can be used to create a filter, after pressing the button, when doing refreshDataSource() the page that was being displayed may not contain rows, so it should show the closest page with rows. If there are no rows, it should be put on the first page. Another very normal circumstance that can occur is that, from another computer the rows that are being displayed from this computer have been deleted, when doing a refreshDataSource() the AsyncPaginatedDataTable2 must be placed on the closest page that has any row and if there are no rows to display placed on the first page. You can see the problem in the following video: https://recordit.co/rb4kkaciK9 Thanks for your help.

To my mind it is a good practice to reset page number to the very first if you happen to change filter and a new data set is suppoed to be displayed - isn't that the case for most of the e-commerce web sites. But still I agree that the widget can be more friendly to devs if it could automatically switch top the last available page upon refresh. Will look into that.

maxim-saplin commented 3 years ago

If I was placed on a certain page (for example the second page), by means of onPageChanged I save the initialFirstRowIndex, and when I return to the same window that shows that AsyncPaginatedDataTable2 I assign its initialFirstRowIndex parameter to the value that I have saved so that it is placed in the same place in the one that was, only the CircularProgressIndicators are shown in the columns, it can only be refreshed, and yet it is not placed in the initialFirstRowIndex indicated initially. Check out the following video: https://recordit.co/VuG7mOWTwQ Thanks for your help.

Try out this scenariou with the new version and let me know if it is fixed now

maxim-saplin commented 3 years ago

The onPageChanged event does not initially run on the AsyncPaginatedDataTable2.

Will check

maxim-saplin commented 3 years ago

If I go to the AsyncPaginatedDataTable2 on the second page, for example, and I make the widget grow in height, it starts to show the first page, in my opinion, I think that the first row should always be the same, it should be immutable, and only add rows to the bottom of the page, only in the case that all rows can be displayed on a single page, the widget should change the first row of the page. Even if it could be optimized, when growing the widget in height, only the new rows should be requested from the server. And when decreasing in height, the lines should not be requested from the server again. Being even more optimal, the AsyncPaginatedDataTable2 could keep a small cache of rows so that, when changing the height of the page, it would not have to ask the server for the rows again. Check out the following video: https://recordit.co/FGsyWlYWOv

Thanks for your help.

This one is tricky as it contradicts the rule I've introduced about having pages aligned... Any resizing with auto rows provides new page size and thus new alignment. Need to think about that.

maxim-saplin commented 3 years ago

Here's the summary of what requires attention:

You contribution/commits to the branch are welcome and can speed up the finilization of the feature. After bugs and defficiences are fixed I've also need to add more test before publishing to pub.dev

ghost commented 3 years ago

I'm going to do tests and tell you my results. Thanks for your hard work. 馃挴 Apart from this, I wanted to ask you for advice on an issue that has nothing to do with data_table_2 and it is driving me a little crazy, in case it has been presented to you in any of your developments. Speaking of Flutter web. Suppose I have a window with a form, that form has been modified, so the modifications are pending to be saved. Now I open another window changing the url and therefore the current one (which has the modifications) closes, how can I put an alert message to prevent or accept navigating to the new url and accept or cancel the pending modifications in the current form? Is there a standardized method to control this type of situation? Thanks for your help.

ghost commented 3 years ago

Hi Maxim, Ignore what he had told you earlier. I have been able to solve most of the problems.

However, I have been able to find one that I believe is a bug. 1) If you make only one row show in the display area and you navigate to the last row, one more row is shown, in my case, instead of 39, 40-40 of 39 is shown. At that moment it gets hooked and you cannot go to the previous row, you need to click on the button to go to the first page, in which case it shows 2-2 of 39, when it really should have been 1-1 of 39. Watch the following video where you can see the problem: https://recordit.co/a1pahvTi0Y As you will see in the following video, your example has the same problem as my program: https://recordit.co/DT3lifCSiO

2) Regarding what I told you, I still think that as long as possible, when the AsyncPaginatedDataTable2 increases or decreases in height, the first visible row should remain unchanged. I think that visually the end user would find it easier and more coherent, it is a bit strange that the visible rows vary, since as the height increases and subsequently decreases the position of the entire page remains in another place. If the first row were immutable, when growing or decreasing, the same rows would always be seen. If the user wants to scroll and see other rows, he can do it only with the buttons.

3) It would be really great if refreshDataSource() were placed on the closest page that contains rows.

4) I have encountered another problem. For some reason if I go to the penultimate page, I open another page on the web and go back to the page where the AsyncPaginatedDataTable2 is in the last line, the CircularProgressIndicators remain perennial. Check out the following video: https://recordit.co/tQGveHht3j

For some strange reason there are 2 calls to getRows, one with 0 startIndex and 10 count, and another with 0 startIndex and 11 count, but the calculation of the result occurs first with the 11 count and then with the 10 count, for that reason remain the perennial CircularProgressIndicators, because it lacks one row to render the 11 rows. As you will see in the code, I have an await inlogProvider.getLogs that calls the server API to fetch the rows, so I don't know why first 0/11 lines are calculated and then 0/10. Do you understand what is happening? Look at the following images: 2021-08-14_0-14-49 2021-08-14_0-12-59 For your reference, there are only 2 simultaneous calls to getRows the first time, then when navigating the pages only one call to getRows occurs:

===========================
0 // 10
===========================
0 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":0,"totalrows":39,"logs":[{"_id":"60f01f9f68259843a8b0ee89","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:31.615Z","__v":0},{"_id":"60f01fa868259843a8b0ee8c","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:40.724Z","__v":0},{"_id":"60f038a868259843a8b0ef2e","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:31:20.931Z","__v":0},{"_id":"60f0390368259843a8b0ef31","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:32:51.341Z","__v":0},{"_id":"60f03c2f68259843a8b0efc4","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:23.328Z","__v":0},{"_id":"60f03c3368259843a8b0efc7","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:27.978Z","__v":0},{"_id":"60f03c4468259843a8b0efca","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:44.337Z","__v":0},{"_id":"60f03d2d68259843a8b0f01f","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:50:37.641Z","__v":0},{"_id":"60f08b9513c40177e8977c48","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:09.673Z","__v":0},{"_id":"60f08b9d13c40177e8977c4b","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:17.556Z","__v":0},{"_id":"60f08ba213c40177e8977c4e","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:22.918Z","__v":0}]}
getLogs logs.lenght: 11
===========================
DataTable2 built: 0ms
getLogs resp: {"initrow":0,"totalrows":39,"logs":[{"_id":"60f01f9f68259843a8b0ee89","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:31.615Z","__v":0},{"_id":"60f01fa868259843a8b0ee8c","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:40.724Z","__v":0},{"_id":"60f038a868259843a8b0ef2e","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:31:20.931Z","__v":0},{"_id":"60f0390368259843a8b0ef31","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:32:51.341Z","__v":0},{"_id":"60f03c2f68259843a8b0efc4","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:23.328Z","__v":0},{"_id":"60f03c3368259843a8b0efc7","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:27.978Z","__v":0},{"_id":"60f03c4468259843a8b0efca","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:44.337Z","__v":0},{"_id":"60f03d2d68259843a8b0f01f","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:50:37.641Z","__v":0},{"_id":"60f08b9513c40177e8977c48","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:09.673Z","__v":0},{"_id":"60f08b9d13c40177e8977c4b","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:17.556Z","__v":0}]}
getLogs logs.lenght: 10
===========================
DataTable2 built: 0ms
===========================
11 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":11,"totalrows":39,"logs":[{"_id":"60f08ba713c40177e8977c51","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:27.312Z","__v":0},{"_id":"60f08baa13c40177e8977c57","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:30.605Z","__v":0},{"_id":"60f08bab13c40177e8977c5a","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:31.487Z","__v":0},{"_id":"610813c8ff64156160b53087","descripcion":"Error en obtenerLogs: ReferenceError: init_row is not defined","date":"2021-08-02T15:48:24.067Z","__v":0},{"_id":"610c19313ee97a911856165b","descripcion":"Error en obtenerConfiguraciones: TypeError: Cannot read property 'auth_pass' of null","date":"2021-08-05T17:00:33.922Z","__v":0},{"_id":"610c19643ee97a9118561664","descripcion":"Error en obtenerConfiguraciones: TypeError: Cannot read property 'auth_pass' of null","date":"2021-08-05T17:01:24.515Z","__v":0},{"_id":"610c19ce46d3ea23f836138d","descripcion":"Error en obtenerConfiguraciones: TypeError: Cannot read property 'auth_pass' of null","date":"2021-08-05T17:03:10.970Z","__v":0},{"_id":"610c19ce46d3ea23f8361390","descripcion":"Error en obtenerConfiguraciones: TypeError: Cannot read property 'auth_pass' of null","date":"2021-08-05T17:03:10.994Z","__v":0},{"_id":"610c19ef46d3ea23f8361393","descripcion":"Error en obtenerConfiguraciones: TypeError: Cannot read property 'auth_pass' of null","date":"2021-08-05T17:03:43.208Z","__v":0},{"_id":"610c1bffe2e77f0290e8e82c","descripcion":"Error en obtenerConfiguraciones: ReferenceError: Cannot access 'configuracion' before initialization","date":"2021-08-05T17:12:31.432Z","__v":0},{"_id":"610c1d43fea16a7eecf43513","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:17:55.691Z","__v":0}]}
getLogs logs.lenght: 11
===========================
DataTable2 built: 0ms
===========================
22 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":22,"totalrows":39,"logs":[{"_id":"610c1e23fea16a7eecf43532","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:21:39.158Z","__v":0},{"_id":"610c1e3cfea16a7eecf4353e","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:22:04.619Z","__v":0},{"_id":"610c1eecfea16a7eecf43592","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:00.178Z","__v":0},{"_id":"610c1f0afea16a7eecf43597","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:30.369Z","__v":0},{"_id":"610c21bafea16a7eecf4359d","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:36:58.579Z","__v":0},{"_id":"610c229bfea16a7eecf435b3","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:40:43.961Z","__v":0},{"_id":"610c2366fea16a7eecf435c9","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:44:06.211Z","__v":0},{"_id":"610c2409434fa44af8adb814","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.496Z","__v":0},{"_id":"610c2409434fa44af8adb816","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.517Z","__v":0},{"_id":"610c2409434fa44af8adb818","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.518Z","__v":0},{"_id":"610c2409434fa44af8adb81a","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.519Z","__v":0}]}
getLogs logs.lenght: 11
===========================
DataTable2 built: 0ms
===========================
33 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":33,"totalrows":39,"logs":[{"_id":"610c2409434fa44af8adb81c","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.520Z","__v":0},{"_id":"610c39598236e894ccf9284b","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:17:45.633Z","__v":0},{"_id":"610c3a184a27607f44eec9ee","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:20:56.083Z","__v":0},{"_id":"610c3a3201c8977938be24ea","descripcion":"Error en sendEmailsTest: ReferenceError: pOn is not defined","date":"2021-08-05T19:21:22.427Z","__v":0},{"_id":"610c3ae60048393a9c21831f","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:24:22.412Z","__v":0},{"_id":"610c3be3d36b877b74c763f0","descripcion":"Error en sendEmailsTest: ReferenceError: get_Email is not defined","date":"2021-08-05T19:28:35.631Z","__v":0}]}
getLogs logs.lenght: 6
===========================
DataTable2 built: 0ms
===========================
22 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":22,"totalrows":39,"logs":[{"_id":"610c1e23fea16a7eecf43532","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:21:39.158Z","__v":0},{"_id":"610c1e3cfea16a7eecf4353e","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:22:04.619Z","__v":0},{"_id":"610c1eecfea16a7eecf43592","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:00.178Z","__v":0},{"_id":"610c1f0afea16a7eecf43597","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:30.369Z","__v":0},{"_id":"610c21bafea16a7eecf4359d","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:36:58.579Z","__v":0},{"_id":"610c229bfea16a7eecf435b3","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:40:43.961Z","__v":0},{"_id":"610c2366fea16a7eecf435c9","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:44:06.211Z","__v":0},{"_id":"610c2409434fa44af8adb814","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.496Z","__v":0},{"_id":"610c2409434fa44af8adb816","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.517Z","__v":0},{"_id":"610c2409434fa44af8adb818","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.518Z","__v":0},{"_id":"610c2409434fa44af8adb81a","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.519Z","__v":0}]}
getLogs logs.lenght: 11
===========================
DataTable2 built: 0ms
===========================
33 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":33,"totalrows":39,"logs":[{"_id":"610c2409434fa44af8adb81c","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.520Z","__v":0},{"_id":"610c39598236e894ccf9284b","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:17:45.633Z","__v":0},{"_id":"610c3a184a27607f44eec9ee","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:20:56.083Z","__v":0},{"_id":"610c3a3201c8977938be24ea","descripcion":"Error en sendEmailsTest: ReferenceError: pOn is not defined","date":"2021-08-05T19:21:22.427Z","__v":0},{"_id":"610c3ae60048393a9c21831f","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:24:22.412Z","__v":0},{"_id":"610c3be3d36b877b74c763f0","descripcion":"Error en sendEmailsTest: ReferenceError: get_Email is not defined","date":"2021-08-05T19:28:35.631Z","__v":0}]}
getLogs logs.lenght: 6
===========================
DataTable2 built: 0ms
===========================
22 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":22,"totalrows":39,"logs":[{"_id":"610c1e23fea16a7eecf43532","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:21:39.158Z","__v":0},{"_id":"610c1e3cfea16a7eecf4353e","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:22:04.619Z","__v":0},{"_id":"610c1eecfea16a7eecf43592","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:00.178Z","__v":0},{"_id":"610c1f0afea16a7eecf43597","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:30.369Z","__v":0},{"_id":"610c21bafea16a7eecf4359d","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:36:58.579Z","__v":0},{"_id":"610c229bfea16a7eecf435b3","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:40:43.961Z","__v":0},{"_id":"610c2366fea16a7eecf435c9","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:44:06.211Z","__v":0},{"_id":"610c2409434fa44af8adb814","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.496Z","__v":0},{"_id":"610c2409434fa44af8adb816","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.517Z","__v":0},{"_id":"610c2409434fa44af8adb818","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.518Z","__v":0},{"_id":"610c2409434fa44af8adb81a","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.519Z","__v":0}]}
getLogs logs.lenght: 11
===========================
DataTable2 built: 0ms
===========================
33 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":33,"totalrows":39,"logs":[{"_id":"610c2409434fa44af8adb81c","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.520Z","__v":0},{"_id":"610c39598236e894ccf9284b","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:17:45.633Z","__v":0},{"_id":"610c3a184a27607f44eec9ee","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:20:56.083Z","__v":0},{"_id":"610c3a3201c8977938be24ea","descripcion":"Error en sendEmailsTest: ReferenceError: pOn is not defined","date":"2021-08-05T19:21:22.427Z","__v":0},{"_id":"610c3ae60048393a9c21831f","descripcion":"Error en sendEmailsTest: ReferenceError: checkSendData is not defined","date":"2021-08-05T19:24:22.412Z","__v":0},{"_id":"610c3be3d36b877b74c763f0","descripcion":"Error en sendEmailsTest: ReferenceError: get_Email is not defined","date":"2021-08-05T19:28:35.631Z","__v":0}]}
getLogs logs.lenght: 6
===========================
DataTable2 built: 0ms
===========================
22 // 11
DataTable2 built: 0ms
getLogs resp: {"initrow":22,"totalrows":39,"logs":[{"_id":"610c1e23fea16a7eecf43532","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:21:39.158Z","__v":0},{"_id":"610c1e3cfea16a7eecf4353e","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:22:04.619Z","__v":0},{"_id":"610c1eecfea16a7eecf43592","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:00.178Z","__v":0},{"_id":"610c1f0afea16a7eecf43597","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:25:30.369Z","__v":0},{"_id":"610c21bafea16a7eecf4359d","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:36:58.579Z","__v":0},{"_id":"610c229bfea16a7eecf435b3","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:40:43.961Z","__v":0},{"_id":"610c2366fea16a7eecf435c9","descripcion":"Error en obtenerConfiguraciones: Configuraci贸n no encontrada","date":"2021-08-05T17:44:06.211Z","__v":0},{"_id":"610c2409434fa44af8adb814","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.496Z","__v":0},{"_id":"610c2409434fa44af8adb816","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.517Z","__v":0},{"_id":"610c2409434fa44af8adb818","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.518Z","__v":0},{"_id":"610c2409434fa44af8adb81a","descripcion":"Error en obtenerConfiguraciones: TypeError: res.statusCode is not a function","date":"2021-08-05T17:46:49.519Z","__v":0}]}
getLogs logs.lenght: 11

I have tried to reproduce the problem in your example. In your example, 2 simultaneous calls are also made to getRows the first time the window containing the AsyncPaginatedDataTable2 is created, however, as you will see in the following results, the resolution of the lines occurs in order, first a call is made to getRows with count of 10 lines, and after 13 lines. In my program, although the calls of 10 and 11 lines are made, the 11 lines are resolved first and then the 10 lines, so line 11 is orphaned and the CircularProgressIndicators are displayed indefinitely. The debug output in your example is as follows:

===================================
DataTable2 built: 0ms
===========================
0 // 10
===========================
0 // 13
DataTable2 built: 0ms
getData resp: 10 == [Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert']
===========================
getData resp: 13 == [Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert', Instance of 'Dessert']
===========================
DataTable2 built: 0ms

I have found the solution to this last problem. All the operations of composition of the rows must be done within a function that is called from AsyncRowsResponse, if any of the operations are carried out outside, many problems can come, me, to get the totalRows it was good for me to do some operations outside and that was my error. :(

If I'm not mistaken, only points 1, 2 and 3 would have to be solved.

It is a wonderful product. I love it, it gives a lot of development possibilities. You are a programming crack. 馃挴 馃

maxim-saplin commented 3 years ago

I'm going to do tests and tell you my results. Thanks for your hard work. 馃挴 Apart from this, I wanted to ask you for advice on an issue that has nothing to do with data_table_2 and it is driving me a little crazy, in case it has been presented to you in any of your developments. Speaking of Flutter web. Suppose I have a window with a form, that form has been modified, so the modifications are pending to be saved. Now I open another window changing the url and therefore the current one (which has the modifications) closes, how can I put an alert message to prevent or accept navigating to the new url and accept or cancel the pending modifications in the current form? Is there a standardized method to control this type of situation? Thanks for your help.

In dart:html there's a binding to window's onBeforeUnload event, that's the place to start IMHO, https://api.dart.dev/stable/2.13.4/dart-html/Window/onBeforeUnload.html

You can read about that browser API in Web/JS related sources.

ghost commented 3 years ago

Hi Maxim, Thaks so much for the orientation. I will study it. 馃憤

ghost commented 3 years ago

Hi @maxim-saplin As I told you, I thought I had solved the problem, but it is not solved. The first time an AsyncPaginatedDataTable2 is displayed there are 2 calls to the function

   @override
   Future <AsyncRowsResponse> **getRows** (int startIndex, int count) async { }

Although the code that is placed inside has all the awaits, when you make the call with source._fetchData (_firstRowIndex, _effectiveRowsPerPage); on

@override
   Widget build (BuildContext context)

inside the file async_paginated_data_table_2.dart In the call you do not put await, since it is not possible, since this call is in the build function. By not putting await, in some occasions of the 2 calls to the getRows function, the second executes the code before the first, and that is when the problem occurs. Check out the following video: https://recordit.co/AVmKmCEP27 In many cases the problem does not occur, but it is a matter of chance, the 2 calls are executed in their correct order, but sometimes this is not the case, and the second call is executed first. In my code I have put print's in the getRows function, and in the following output you can see the problem. First, there is a request of 10 rows and immediately the request of 11 rows occurs, since at that moment the AsyncPaginatedDataTable2 has 11 rows. Instead of executing all the calculations of the request of 10 rows first, all the calculations of the request of 11 rows occurs, and after these calculations the calculations of the request of 10 rows are executed, with the consequent problem, since in the rendering 11 rows are expected, instead of 10 rows.

0 // 10
CafeApi.httpGet url: /logs?initRow=0&rowsperpage=10
httpGet path: /logs?initRow=0&rowsperpage=10
===========================
0 // 11
CafeApi.httpGet url: /logs?initRow=0&rowsperpage=11
httpGet path: /logs?initRow=0&rowsperpage=11
DataTable2 built: 0ms
getLogs resp: {"initrow":0,"totalrows":39,"logs":[{"_id":"60f01f9f68259843a8b0ee89","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:31.615Z","__v":0},{"_id":"60f01fa868259843a8b0ee8c","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:40.724Z","__v":0},{"_id":"60f038a868259843a8b0ef2e","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:31:20.931Z","__v":0},{"_id":"60f0390368259843a8b0ef31","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:32:51.341Z","__v":0},{"_id":"60f03c2f68259843a8b0efc4","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:23.328Z","__v":0},{"_id":"60f03c3368259843a8b0efc7","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:27.978Z","__v":0},{"_id":"60f03c4468259843a8b0efca","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:44.337Z","__v":0},{"_id":"60f03d2d68259843a8b0f01f","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:50:37.641Z","__v":0},{"_id":"60f08b9513c40177e8977c48","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:09.673Z","__v":0},{"_id":"60f08b9d13c40177e8977c4b","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:17.556Z","__v":0},{"_id":"60f08ba213c40177e8977c4e","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:22.918Z","__v":0}]}
logs count: 11
getLogs logs.lenght: 11
39 [] 11
===========================
DataTable2 built: 0ms
getLogs resp: {"initrow":0,"totalrows":39,"logs":[{"_id":"60f01f9f68259843a8b0ee89","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:31.615Z","__v":0},{"_id":"60f01fa868259843a8b0ee8c","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T11:44:40.724Z","__v":0},{"_id":"60f038a868259843a8b0ef2e","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:31:20.931Z","__v":0},{"_id":"60f0390368259843a8b0ef31","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:32:51.341Z","__v":0},{"_id":"60f03c2f68259843a8b0efc4","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:23.328Z","__v":0},{"_id":"60f03c3368259843a8b0efc7","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:27.978Z","__v":0},{"_id":"60f03c4468259843a8b0efca","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:46:44.337Z","__v":0},{"_id":"60f03d2d68259843a8b0f01f","descripcion":"Haciendo login - El password del usuario \"pepe2\" es incorrecto","date":"2021-07-15T13:50:37.641Z","__v":0},{"_id":"60f08b9513c40177e8977c48","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:09.673Z","__v":0},{"_id":"60f08b9d13c40177e8977c4b","descripcion":"Haciendo login - El password del usuario \"admin\" es incorrecto","date":"2021-07-15T19:25:17.556Z","__v":0}]}
logs count: 10
getLogs logs.lenght: 10
39 [] 10

By the way, I have tried to put some prints inside your code, but they are not displayed in the DEBUG CONSOLE, is this normal?

  @override
  Widget build(BuildContext context) {
    var source = widget.source as AsyncDataTableSource;
    var w = widget as AsyncPaginatedDataTable2;

    print('********************************');

    if (source.state == _SourceState.none) {
      _showNothing = true;
      var x = super.build(context);

      **print('source._fetchData pre: $_firstRowIndex //// $_effectiveRowsPerPage');**

      source._fetchData(_firstRowIndex, _effectiveRowsPerPage);

      **print('source._fetchData post: $_firstRowIndex //// $_effectiveRowsPerPage');**

      // Future.delayed(Duration(milliseconds: 0),
      //     () => source._fetchData(_firstRowIndex, _effectiveRowsPerPage));
      return x;

It is important to note that in your example you put predefined fixed times (Future.delayed) to simulate a call to a REST API, however, the actual calls to a REST API can take more or less time depending on the case, so the processing order can cause a subsequent call to end earlier than the initial call.

I have used the latest version of the source code that is currently on github.

Note1: Finally I have been able to reproduce the problem directly in your example, as I have told you if the times of the API calls are varied to make the first call slower than the second the problem occurs. These are the files of your example modified to reproduce the problem: lib.zip You can also watch this video: https://recordit.co/EmEUJNKlNn

Note2: I also wanted to tell you that the solution you have given in the example so that the AsyncPaginatedDataTable2 is placed on the last page is not very correct or elegant:

   if (getCurrentRouteOption(context) == goToLast) {
      _dataSourceLoading = true;
      _dessertsDataSource!.getTotalRecors().then((count) => setState(() {
            _initialRow = count - _rowsPerPage;
            _dataSourceLoading = false;
          }));
    }

since it is based on rowsPerPage. Initially the value of rowsPerPage is always 10 and it can be very different from what the AsyncPaginatedDataTable2 later takes when autoRowsToHeight is true, it may not even be placed on the last page. For example:

_initialRow = count - _rowsPerPage;
_initialRow = 100 - 10; // **90**

If, because of autoRowsToHeight the page finally contains only 5 rows, then: 100-5 = 95; so line 90 is placed on the page before the last.

Note 3: After doing a refreshDatasource() the value of _controller.rowCount is not correct, so it is impossible to adjust the display page in the case that we are located on a page that now no longer contains any rows. I have done many tests and in conclusion I can tell you that readjusting the position of the page after having done a refresh or a filter of the datasource is a real mess. :(

Regards,

maxim-saplin commented 3 years ago

Progressing slowly on Async version, uploading increment to the branch, it makes sense to recheck after the updates are ready

ghost commented 3 years ago

Hi maxim-saplin, From what I have been seeing, it is very important to make a system by which the response order to the server API is completely respected, that is, if requests have been made to bring the page 1, 2, 3, 4, 5 ... . that there is no danger that the execution of the code of a subsequent page before the previous one, that is to say, end the treatment of page 4 before that of 3, motivated perhaps because the server has taken longer to respond on page 3. Some estimate for the new updates. Thank you very much for all your work. 馃挴

maxim-saplin commented 3 years ago

Hi Josua,

Do you say that the current design of async table allows for cases when out of order fetches/updates can happen which can break the widget? If yes, can you describe the steps?

In terms of the current state, thr asycn branch has to following updates:

The following changes are pending:

Unfortunately can't give you ETA's, will try to put some time this week

ghost commented 3 years ago

Hi Maxim,

I mentioned it to you exhaustively in a previous ticket (https://github.com/maxim-saplin/data_table_2/issues/31#issuecomment-906632464). I also sent you the modified example units and a video so you can see the problem in a fully reproducible way. lib.zip https://recordit.co/EmEUJNKlNn

As it is, it is impossible for me to use it in the application in production, as it causes those problems, and the problem is that I have already modified my code in all places to be able to use async mode. :(

If you have any problem reproducing the problems, tell me.

There should also be a different solution to be placed on the last page, since, as I mentioned in that ticket, that solution that you have given in the example will not always work, but this problem is not so urgent.

You also say the following:

In terms of the current state, thr asycn branch has to following updates: Refreshing data source and getting less rows than before while being at the last page doesn't fix the number (need to show the last page with rows rather than empty rows)

However, as you can see in the following videos, if I apply a filter and do a refreshDatasource()the page does not change to one with rows. In this video, when the data source is filtered, it remains empty, however it remains on the same page: https://recordit.co/hKbpcRkIUB In this other video the datasource when doing the filter stays with 25 rows, however when doing refreshDatasource() it remains on the same page it was on when it had 39 rows: https://recordit.co/fTmMrSN6os

Any questions you ask me.

Thanks for your help.

jamolina2021 commented 3 years ago

Hi Maxim,

How are you? Have you been on vacation? If so, I hope it was a rewarding and relaxing few days. :) You currently have an estimate of when you will be able to make the corrections so that the paging system works correctly.

Thank you.

Regards, Jose

maxim-saplin commented 3 years ago

Hi Jason, thanks for cheking with me, though unfirtunately I was not in the vacation.

There was little progress. I made an attempt to go through the bulks of text you provided but had hard time making sense of them and outlining any actionable items - it requires to much effort which I don'nt have capacity for. Currently on my list there's only the issue with autoRowSize and making requests.

To be in a position to help you I would need a more specific, concise and organized inputs from you. Starting with checking the current branch, trying out your scenarious, making reproduction cases based on the exisiting examples will be the natural step. The next step would be creating an itemized list of issues with clear specification of steps and modifications made to the examples.

Regarding reproduction examples, to make the effort smaller I would like to recieve a link to a branch (one branch per issues) cloned from async-table and modified to showcase certain issue.

jamolina2021 commented 3 years ago

Hi Maxim,

In the following video you can see the problem reproduced with the files of your example: https://recordit.co/EmEUJNKlNn Below I put all the files in the example/lib directory, if you replace them in your example and run the project you will be able to reproduce the problem: lib.zip

You simply have to follow the steps seen in the video.

Steps to follow so that you can easily see the problem in the source code. I have only modified your example files data_sources.dart and async_paginated_data_table2.dart

1) In getRows (data_sources.dart) I have conditionally put the milliseconds of Future.delayed

  bool flag = true;

  @override
  Future<AsyncRowsResponse> getRows(int startIndex, int count) async {
    if (_errorCounter != null) {
      _errorCounter = _errorCounter! + 1;

      if (_errorCounter! % 2 == 1) {
        await Future.delayed(Duration(milliseconds: 1000));
        throw 'Error #${((_errorCounter! - 1) / 2).round() + 1} has occured';
      }
    }

    var index = startIndex;
    final format = NumberFormat.decimalPercentPattern(
      locale: 'en',
      decimalDigits: 0,
    );
    assert(index >= 0);

    print('===========================');
    print('$startIndex // $count');

    int ms = 2000;
    if (flag) {
      ms = 2000;
    } else {
      ms = 200;
    }

    print('$startIndex // $count  // $ms');

    flag = !flag;

    var x = _empty
        ? await Future.delayed(Duration(milliseconds: ms),
            () => DesertsFakeWebServiceResponse(0, []))
        : await _repo.getData(
            startIndex, count, _sortColumn, _sortAscending, ms);

    int c = 0;

    var r = AsyncRowsResponse(
        x.totalRecords,
        x.data.map((dessert) {
          c++;
          return DataRow(
            key: ValueKey<int>(dessert.id),
            selected: dessert.selected,
            onSelectChanged: (value) {
              if (value != null)
                setRowSelection(ValueKey<int>(dessert.id), value);
            },
            cells: [
              DataCell(Text(dessert.name)),
              DataCell(Text('${dessert.calories}')),
              DataCell(Text(dessert.fat.toStringAsFixed(1))),
              DataCell(Text('${dessert.carbs}')),
              DataCell(Text(dessert.protein.toStringAsFixed(1))),
              DataCell(Text('${dessert.sodium}')),
              DataCell(Text('${format.format(dessert.calcium / 100)}')),
              DataCell(Text('${format.format(dessert.iron / 100)}')),
            ],
          );
        }).toList());

    print('getData resp: ${c} == ${x.data}');

    print('===========================');

    return r;
  }
}

2) The function getData (data_sources.dart) I have modified it to take the milliseconds from getRows

  Future<DesertsFakeWebServiceResponse> getData(int startingAt, int count,
      String sortedBy, bool sortedAsc, int ms) async {
    return Future.delayed(Duration(milliseconds: ms), () {
      _dessertsX3.sort(_getComparisonFunction(sortedBy, sortedAsc));
      return DesertsFakeWebServiceResponse(_dessertsX3.length,
          _dessertsX3.skip(startingAt).take(count).toList());
    });
  }
}

These first 2 points are the important ones and they are the ones that serve to reproduce the problem. As you will see, I have exaggerated the times (milliseconds) to make it more evident, the first call to the api is delayed 2000ms, while the second call is made at 200ms, as your plugin does not internally control that the first call is always executed in the first, the problem occurs. In a real API this circumstance can occur, the first call can take longer to resolve than the second call, which causes the problem to occur when this circumstance occurs. Initially when the AsyncPaginatedDataTable2 is displayed, an API call is made to collect 10 rows, and a second call to collect the amount of rows that is actually seen in the PaginatedDataTable2 (autoRowsToHeight). The problem occurs when the second call occurs before the first call, if the page supports 20 rows, the first call has only collected 10 rows so it cannot render the 20 rows that the PaginatedDataTable2 is expecting. I hope I have explained myself correctly. I am not fluent in English.

3) I have also modified the async_paginated_data_table2.dart file, but only in order to make it unnecessary to modify the parameters manually when entering the AsyncPaginatedDataTable2 window.

In class _AsyncPaginatedDataTable2DemoState I have removed the lines:

  //bool _sortAscending = true;
  //int? _sortColumnIndex;

The didChangeDependencies procedure looks like this:

  @override
  void didChangeDependencies() {
    // initState is to early to access route options, context is invalid at that stage
    if (_dessertsDataSource == null) {
      /*
      _dessertsDataSource = getCurrentRouteOption(context) == noData
          ? DessertDataSourceAsync.empty()
          : getCurrentRouteOption(context) == asyncErrors
              ? DessertDataSourceAsync.error()
              : DessertDataSourceAsync();
              */
      _dessertsDataSource = DessertDataSourceAsync();
    }

    /*
    if (getCurrentRouteOption(context) == goToLast) {
      _dataSourceLoading = true;
      _dessertsDataSource!.getTotalRecors().then((count) => setState(() {
            _initialRow = count - _rowsPerPage;
            _dataSourceLoading = false;
          }));
    }
    */
    super.didChangeDependencies();
  }

I have removed the entire Sort procedure

/*
  void sort(
    int columnIndex,
    bool ascending,
  ) {
    var columnName = "name";
    switch (columnIndex) {
      case 1:
        columnName = "calories";
        break;
      case 2:
        columnName = "fat";
        break;
      case 3:
        columnName = "carbs";
        break;
      case 4:
        columnName = "protein";
        break;
      case 5:
        columnName = "sodium";
        break;
      case 6:
        columnName = "calcium";
        break;
      case 7:
        columnName = "iron";
        break;
    }
    _dessertsDataSource!.sort(columnName, ascending);
    setState(() {
      _sortColumnIndex = columnIndex;
      _sortAscending = ascending;
    });
  }
  */

and therefore I have modified the _columns procedure

  List<DataColumn> get _columns {
    return [
      DataColumn(
        label: Text('Desert'),
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Calories'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Fat (gm)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Carbs (gm)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Protein (gm)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Sodium (mg)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Calcium (%)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
      DataColumn(
        label: Text('Iron (%)'),
        numeric: true,
        //onSort: (columnIndex, ascending) => sort(columnIndex, ascending),
      ),
    ];
  }

I have modified the build(BuildContext context) procedure as follows:

  @override
  Widget build(BuildContext context) {
    // Last ppage example uses extra API call to get the number of items in datasource
    if (_dataSourceLoading) return SizedBox();

    print('===================================');
    print('asyncPaginated_data_table_2 build');
    print('===================================');

    return Stack(alignment: Alignment.bottomCenter, children: [
      AsyncPaginatedDataTable2(
          horizontalMargin: 20,
          checkboxHorizontalMargin: 12,
          columnSpacing: 0,
          wrapInCard: false,
          /*
          header:
              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
            Text('AsyncPaginatedDataTable2'),
            if (kDebugMode && getCurrentRouteOption(context) == custPager)
              Row(children: [
                OutlinedButton(
                    onPressed: () => _controller.goToPageWithRow(25),
                    child: Text('Go to row 25')),
                OutlinedButton(
                    onPressed: () => _controller.goToRow(5),
                    child: Text('Go to row 5'))
              ]),
            if (getCurrentRouteOption(context) == custPager)
              PageNumber(controller: _controller)
          ]),
          */
          //rowsPerPage: _rowsPerPage,
          autoRowsToHeight: true, //getCurrentRouteOption(context) == autoRows,
          minWidth: 800,
          fit: FlexFit.tight,
          /*
          border: TableBorder(
              top: BorderSide(color: Colors.black),
              bottom: BorderSide(color: Colors.grey[300]!),
              left: BorderSide(color: Colors.grey[300]!),
              right: BorderSide(color: Colors.grey[300]!),
              verticalInside: BorderSide(color: Colors.grey[300]!),
              horizontalInside: BorderSide(color: Colors.grey, width: 1)),
              */
          onRowsPerPageChanged: (value) {
            // No need to wrap into setState, it will be called inside the widget
            // and trigger rebuild
            //setState(() {
            //_rowsPerPage = value!;
            //});
          },
          initialFirstRowIndex: 0, //_initialRow,
          onPageChanged: (rowIndex) {
            //print(rowIndex / _rowsPerPage);
          },
          /*
          sortColumnIndex: _sortColumnIndex,
          sortAscending: _sortAscending,
          onSelectAll: (select) => select != null && select
              ? (getCurrentRouteOption(context) != selectAllPage
                  ? _dessertsDataSource!.selectAll()
                  : _dessertsDataSource!.selectAllOnThePage())
              : (getCurrentRouteOption(context) != selectAllPage
                  ? _dessertsDataSource!.deselectAll()
                  : _dessertsDataSource!.deselectAllOnThePage()),
                  */
          controller: _controller,
          //getCurrentRouteOption(context) == custPager ? _controller : null,
          hidePaginator: getCurrentRouteOption(context) == custPager,
          columns: _columns,
          empty: Center(
              child: Container(
                  padding: EdgeInsets.all(20),
                  color: Colors.grey[200],
                  child: Text('No data'))),
          /*loading: _Loading(),
          errorBuilder: (e) => Center(
                child: Container(
                    padding: EdgeInsets.all(10),
                    height: 70,
                    color: Colors.red,
                    child: Column(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Text('Oops! ${e != null ? e.toString() : ""}',
                              style: TextStyle(color: Colors.white)),
                          TextButton(
                              onPressed: () =>
                                  _dessertsDataSource!.refreshDatasource(),
                              child: Row(
                                  mainAxisSize: MainAxisSize.min,
                                  children: [
                                    Icon(
                                      Icons.refresh,
                                      color: Colors.white,
                                    ),
                                    Text('Retry',
                                        style: TextStyle(color: Colors.white))
                                  ]))
                        ])),
              ),
              */
          source: _dessertsDataSource!),
      /*
      if (getCurrentRouteOption(context) == custPager)
        Positioned(bottom: 16, child: CustomPager(_controller))
        */
    ]);
  }
}

Please tell me if these explanations help you reproduce the problem. Thanks.

maxim-saplin commented 3 years ago

Nope..

ghost commented 3 years ago

Are these explanations and the files that I have provided not useful to you? Don't you understand the problem? Have you tried the modified files of your example that I have sent you? Well, I'm sorry, I don't think I can explain it to you any other way, I have made all the effort I could to explain it to you. I do not know more. It took many hours of work to be able to reproduce the problem with your example, but if I have not been able to explain it to you, I am very sorry. Thanks.

maxim-saplin commented 3 years ago

The inputs you provide are hard to follow and digest. Loads of poorly structured and formatted text don't help...

In my previous post I tried to be specific and clear on the kind of inputs that can be efficient.

ghost commented 3 years ago

Hi Maxim, The code that I have indicated is only your example code with very few variations. It is strange that a person with the high programming knowledge that I have seen that you have does not understand these few variations, so I suppose that something I said must have angered you, if so, I'm sorry, it was not my intention at all, on the contrary, my intention is to help make your widget library the best. As I mentioned, my English is really poor and I may have used a bad expression. On the contrary, if not, I'm also sorry, but I don't know how to explain the problem to you more clearly. So, for one reason or another, I say goodbye, you can close this ticket. Bye Maxim.

maxim-saplin commented 3 years ago

Simply put, asycn table and with auto rows enabled make 2 requests to data source (_fetchRows) upon opening the table for the first time. This can be seen in the current example (Async Table -> Auto rows) when AsyncDataTableSource._fetchDatais called 2 times when opening the screen.

Check the recent update of async-table branch, this should be fixed now

jamolina2021 commented 3 years ago

Yes. Now it works perfectly. Thank you very much Maxim.

maxim-saplin commented 3 years ago

Could you please reiterate on outstaning items you think still require attention and share the list?

There's still one on my radar:

jamolina2021 commented 3 years ago

Hi Maxim,

Note 1: You commented the following:

In terms of the current state, the asycn branch has to following updates: Auto-rows, resizing and page aligning - top row disapperrs Refreshing data source and getting less rows than before while being at the last page doesn't fix the number (need to show the last page with rows rather than empty rows)

However, as you can see in the following videos, if I apply a filter and do a refreshDatasource() the page does not change to one with rows. In this video, when the data source is filtered, it remains empty, however it remains on the same page: https://recordit.co/hKbpcRkIUB In this other video the datasource when doing the filter stays with 25 rows, however when doing refreshDatasource() it remains on the same page it was on when it had 39 rows: https://recordit.co/fTmMrSN6os

Note 2: I also wanted to tell you that the solution that you have given in the example so that AsyncPaginatedDataTable2 is placed on the last page I think is not very correct.

   if (getCurrentRouteOption(context) == goToLast) {
      _dataSourceLoading = true;
      _dessertsDataSource!.getTotalRecors().then((count) => setState(() {
            _initialRow = count - _rowsPerPage;
            _dataSourceLoading = false;
          }));
    }

since it is based on rowsPerPage. Initially the value of rowsPerPage is always 10 and it can be very different from what the AsyncPaginatedDataTable2 later takes when autoRowsToHeight is true, it may not even be placed on the last page. For example:

_initialRow = count - _rowsPerPage;
_initialRow = 100 - 10; // **90**

If, because of autoRowsToHeight the page finally contains only 5 rows, then: 100-5 = 95; so line 90 is placed on the page before the last.

There should be a different solution to get to the last page. What do you think?

Note 3: After doing a refreshDatasource() the value of _controller.rowCount is not correct, so it is impossible to adjust the display page in the case that we are located on a page that now no longer contains any rows.

I've done testing and I think I can tell you that readjusting the page position after having done an update or datasource filter is a problem.

Note 4: onPageChanged not firing after first load of async table

Note 5: Unless I'm wrong, I think that "Auto rows, resizing and re-fetching" works correctly.

I believe that solving these problems the library would be perfect for any application that a developer wants to address.

I have seen other developments based on the PaginatedDataTable but undoubtedly your improvements are far superior to those that have been included in them.

There could be other interesting aesthetic or practical issues, such as having page numbers in the footer to be able to go directly to a certain page. I think I have seen another fork of PaginatedDataTable that allows to do that. It could also be visually interesting that the user could go forward or backward the first row of the page. This can be very useful when Auto-Rows is true, as sometimes the user wants to simultaneously see the last row of the previous page and the first row of the next page. Does this make sense to you?

As I mentioned, my English is terrible, if you don't understand something, tell me.

If you see that I overwhelm you by saying many things simultaneously, you also tell me.

And again, thank you very much for all your work. Your selfless work helps many developers.

maxim-saplin commented 3 years ago

Thanks. There're an improvement to ''Auto rows, resizing and re-fetching'', now there shouldn't be extra requests while a window is being resized (async branch has the commit).

Notes 1, 3, 4 - any chance to see those cases reproducible within existing samples? Note 5 - I've specifically added onPageChanged handler and print() to async examples. Seems to work as expected - I.e. it doesn't fire upon first load if autoRoes is false and fires when it is true.

Note 2 - indeed, the sample works only when auto Rows is false. I believe there's no general short solution that covers both cases. AutoRows might require using controllers. I'd ask you to investigate the solution for your specific case.

In the meantime my plan is to focus on adding tests and pushing the async feature to pub.dev. Repro cases for Notes 1, 3 and 4 would be useful as they can uncover nasty bugs.

ghost commented 3 years ago

These issues can be easily reproduced in my app, but I can't send it to you as it's for the company I work for. But I will study some way to reproduce these problems with some example. Regarding case 2 I have not been able to solve the problem, however, I will continue investigating. I believe that the async feature is now ready for publication in pub.dev. Thanks.