Flutter - Riverpod Future Provider: How to keep old data when error occurs - flutter

This is my usecase:
data is downloaded
final dataProvider = FutureProvider<MyModel>((ref) async {
return fetchData();
});
and it's used like this within widget's build method:
ref.watch(dataProvider).when(
data: DataWidget(data),
error: ErrorWidget(),
loading: LoadingWidget())
user has option to refresh the data:
ref.refresh(dataProvider.future);
But when there is an error (for example phone is in airplane mode) error is provided so DataWidget is lost and replaced with ErrorWidget... Is there a way using Riverpod to provide/keep existing data instead of error ? I believe it's common scenario, but I didn't find any elegant solution for this problem. I've also read documentation too, but didn't find anything helpful related to this. Am I missing something ? (I'm new to Riverpod) Thank you

Preserving the previous data on refresh is behavior as part of 2.0.0.
Although there were some bugs that you may have encountered. Make sure youre using 2.1.3 or above, which should fix all the issues related to this.
As for using when to show the previous data/error, you can use various flags:
asyncValue.when(
// show previous data/error on loading
skipLoadingOnReload: true,
// show previous data if there's an error
skipError: true,
loading:...,
data:...,
error: ...,
)

In the new version of Riverpod (as of v.2.1.0), you can do this:
asyncValue.when(
skipLoadingOnReload: false,
skipLoadingOnRefresh: true,
skipError: false,
data: DataWidget(data),
error: ErrorWidget(),
loading: LoadingWidget(),
)
You can see more details here.

Related

How to make "compute()" function insert data to sqlite while in isolated process?

I'm working on flutter app that uses php apis for server and sqlite for local data.
The problem is with "compute()".
Here is the explanation :
I have three functions that receives data from api on the server, then add the data to my local database (sqlite) table.
First function to get data from server.
Future<List<Map<String, dynamic>>> getServerData(int vers)async {
//my code
}
Second function to insert data into local database:
Future<int> addNewData(List<Map<String, dynamic>>)async {
//my code
}
Third function to call the first and second function:
Future<bool> checkServerData(int vers)async {
List<Map<String, dynamic>> sdt= await getServerData(vers);
int res=await addNewData(sdt);
if(res>0) return true;
else return false;
}
I want to call the third function in a compute function:
compute(checkServerData, 2);
When did that I found this error:
null check operator used on null value.
Note*:
If I used it without calling local database it works good.
The error appears if I called the database to insert data into.
When I searched about this issue I found that it's not allowed to access any resources which generated in one thread from another thread. But I didn't understand exactly how to resolve it or how to use another way that do the same idea.
After searching about the issue specified, I found those workaround solutions:
1: if the process is very important to work in background, you can use the Isolate package classes and functions which allow all isolated processes or contexts to share data between them as messages sending and receiving. But it's something complex for beginners in flutter and dart to understand these things, except those who know about threading in another environments.
To now more about that I will list here some links:
Those for flutter and pub documentation:
https://api.flutter.dev/flutter/dart-isolate/dart-isolate-library.html
https://api.flutter.dev/flutter/dart-isolate/Isolate-class.html
https://pub.dev/packages/flutter_isolate
This is an example in medium.com website:
https://medium.com/flutter-community/thread-and-isolate-with-flutter-30b9631137f3
2: the second solution if the process isn't important to work on background:
using the traditional approaches such as Future.builder or async/await.
You can know more about them here:
https://www.woolha.com/tutorials/flutter-using-futurebuilder-widget-examples
https://dart.dev/codelabs/async-await
and you can review this question and answers in When should I use a FutureBuilder?

How can I reduce the copied code using Riverpod

I feel myself copying a lot of the same code using Riverpod. An example, if I have something like this:
...modelList.when(
data: (data) { display page when data returns },
loading: () { display disabled version of page until data returns} ,
error: (error, st) { display page with error message },
)
I find myself displaying the a version of the page in 3 different places. This feels very repetitive and I feel I must be doing something wrong. Suggestions?
How do you propose you could solve this in less code?
You need to handle different states in your app; I'm unsure how you could do that without specifying the desired behavior for each state.
You aren't doing anything wrong. Try using bloc (for example) and you will see that Riverpod is really lightweight for what it provides.

Stop huge error output from testing-library

I love testing-library, have used it a lot in a React project, and I'm trying to use it in an Angular project now - but I've always struggled with the enormous error output, including the HTML text of the render. Not only is this not usually helpful (I couldn't find an element, here's the HTML where it isn't); but it gets truncated, often before the interesting line if you're running in debug mode.
I simply added it as a library alongside the standard Angular Karma+Jasmine setup.
I'm sure you could say the components I'm testing are too large if the HTML output causes my console window to spool for ages, but I have a lot of integration tests in Protractor, and they are SO SLOW :(.
I would say the best solution would be to use the configure method and pass a custom function for getElementError which does what you want.
You can read about configuration here: https://testing-library.com/docs/dom-testing-library/api-configuration
An example of this might look like:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
You can then put this in any single test file or use Jest's setupFiles or setupFilesAfterEnv config options to have it run globally.
I am assuming you running jest with rtl in your project.
I personally wouldn't turn it off as it's there to help us, but everyone has a way so if you have your reasons, then fair enough.
1. If you want to disable errors for a specific test, you can mock the console.error.
it('disable error example', () => {
const errorObject = console.error; //store the state of the object
console.error = jest.fn(); // mock the object
// code
//assertion (expect)
console.error = errorObject; // assign it back so you can use it in the next test
});
2. If you want to silence it for all the test, you could use the jest --silent CLI option. Check the docs
The above might even disable the DOM printing that is done by rtl, I am not sure as I haven't tried this, but if you look at the docs I linked, it says
"Prevent tests from printing messages through the console."
Now you almost certainly have everything disabled except the DOM recommendations if the above doesn't work. On that case you might look into react-testing-library's source code and find out what is used for those print statements. Is it a console.log? is it a console.warn? When you got that, just mock it out like option 1 above.
UPDATE
After some digging, I found out that all testing-library DOM printing is built on prettyDOM();
While prettyDOM() can't be disabled you can limit the number of lines to 0, and that would just give you the error message and three dots ... below the message.
Here is an example printout, I messed around with:
TestingLibraryElementError: Unable to find an element with the text: Hello ther. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
...
All you need to do is to pass in an environment variable before executing your test suite, so for example with an npm script it would look like:
DEBUG_PRINT_LIMIT=0 npm run test
Here is the doc
UPDATE 2:
As per the OP's FR on github this can also be achieved without injecting in a global variable to limit the PrettyDOM line output (in case if it's used elsewhere). The getElementError config option need to be changed:
dom-testing-library/src/config.js
// called when getBy* queries fail. (message, container) => Error
getElementError(message, container) {
const error = new Error(
[message, prettyDOM(container)].filter(Boolean).join('\n\n'),
)
error.name = 'TestingLibraryElementError'
return error
},
The callstack can also be removed
You can change how the message is built by setting the DOM testing library message building function with config. In my Angular project I added this to test.js:
configure({
getElementError: (message: string, container) => {
const error = new Error(message);
error.name = 'TestingLibraryElementError';
error.stack = null;
return error;
},
});
This was answered here: https://github.com/testing-library/dom-testing-library/issues/773 by https://github.com/wyze.

Flutter - Parse Server SDK - IncludeObject not working with liveQuery

I was trying out includeObject method with my livequery in one of my project. I am using Parse_Server_Sdk for this back4App database. Also, I am using flutter parse_server_sdk latest version 1.0.26.
What i want is i have a list of events. So whenever a new event is added in my database then it should trigger the subscription method. I have used following code and it works as well. But, only thing i have issue with is i don't get the includedobject i have added in QueryBuilder.
QueryBuilder<ParseObject> parseQuery =
QueryBuilder<ParseObject>(ParseObject(tblEvents))
..whereEqualTo(tblEventUserId, currentUser);
parseQuery.includeObject([tblOrganizerId]);
Subscription subscription = await liveQuery.client.subscribe(parseQuery);
subscription.on(LiveQueryEvent.create, (value) {
print("val : ${value}");
print("val : ${value.runtimeType}");
});
Output :
val : {"className":"Group_Events","objectId":"uotQ6BpT4C","createdAt":"2020-08-27T07:18:26.581Z","updatedAt":"2020-08-27T07:18:26.581Z","organizer_id":{"__type":"Pointer","className":"Organizer","objectId":"t9M1oyZHh4"},"user_id":{"__type":"Pointer","className":"_User","objectId":"Hx5xJ5ABxG"},"weight":1}
I/flutter (30335): val : ParseObject
I have also evaluated the expression. But, there is only limited pointer data to "tblOrganizerId". I am not getting whole data of that table.
Can anyone suggest me a workaround or a solution?
Thanks.
I would use the "ParseLiveListWidget". It should be perfect for this scenario.
You can find an example here.
Additional information is in this README.
(Use version 1.0.27 for some important bug fixes.)

Office JavaScript API: selecting a range in Word for Mac

I'm working on a side project using the Microsoft Office JavaScript APIs. I have some functionality working to select a range in order to scroll to a particular position within a document. This works as expected in Office for the web, but in Office for Mac I get the following error when calling context.sync().then():
Unhandled Promise Rejection: RichApi.Error: ItemNotFound
I can't find any documentation on that particular error, and I'm not sure how to troubleshoot what I might be doing wrong. What am I missing? Like I said, this works in the web interface.
Here is minimal sample of code that demonstrates the problem:
function UI(context) {
this.context = context;
}
UI.prototype.initialize = function() {
var paragraphs = this.context.document.body.paragraphs;
this.context.load(paragraphs);
document.querySelector('button').addEventListener('click', () => {
this.context.sync().then(() => {
this.goToRange(paragraphs.items[0]);
});
});
};
UI.prototype.goToRange = function(range) {
range.select();
this.context.sync();
};
document.addEventListener('DOMContentLoaded', () => {
Office.onReady(() => {
Word.run(context => {
return context.sync().then(() => {
new UI(context).initialize();
});
});
});
});
The only thing I can think of is that maybe the reference to the paragraph client object becomes "stale" in some sense, perhaps based on some resource limits that are lower in the Mac application than in the online interface? (That would be counterintuitive to me, but it's the only thing that comes to mind.)
I think I figured out the problem. I stumbled upon a hint while putting together the minimum code sample in the question; I removed a little too much code at first and encountered the following error:
Unhandled Promise Rejection: RichApi.Error: The batch function passed
to the ".run" method didn't return a promise. The function must return
a promise, so that any automatically-tracked objects can be released
at the completion of the batch operation.
I believe the issue is that, at least in Word for Mac, you can't use the context object provided by Word.run in an asynchronous event listener. I'm guessing this is because, as the above error states, some state has been released after resolving the promise returned. I can get the code to work by adding a dedicated call to Word.run (and using the fresh context provided) inside the event listener.
It is still a little odd that it works just fine in the browser. Presumably, the same state is not released as aggressively in the browser-based version.