Dart/Flutter problems with null-safety - firebase

I got the same problem with null statements (?) in Dart multiple times in different cases. I really hope somebody can help me.
Just added some lines of code & the error:
Error:
The property 'isEmpty' can't be unconditionally accessed because the receiver can be 'null'. Try making the access conditional (using '?.') or adding a null check to the target ('!'). here
Here is one of my examples:
child: MaterialButton(
onPressed: () {
var currentState = this._formKey.currentState;
if (currentState == null) {
return;
}
if (_formKey.currentState.validate()) {
AuthService.instance.signIn(
email: emailTextEditingController.text,
password:
passwordTextEditingController.text);
if (AuthService.instance
.checkIfUserExists() ==
true) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => MainMenu()));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
VerifyScreen()));
}
}
},
Got this error-message again:
The method 'validate' can't be unconditionally invoked because the receiver can be 'null'.Try making the call conditional (using '?.') or adding a null check to the target ('!').
After I edited the code with a ! to avoid the Null-Statement like:
singUpUser() {
if (formKey.currentState!.validate()) {
setState(() {
isLoading = true;
});
} else {
return null;
};
But now i just avoid the error in the code itself, after starting a emulator and testing it, next error appears:
Null check operator used on a null value
So thats not the right solution...
If you need more code, just message me.
Thank you!
Tom

In a nutshell: if Dart is certain that a variable at compile time can be null at runtime, it doesn't compile.. unless you explicitly check for null values, and/or promote the variable to be non-nullable with the ! operator (Dart is not always able to infer the non-nullability in certain situations, so it's our responsibility to promote them to non-nullable).
There's much more to know if you're curious ("why?", for starters), so I'd suggest to check the null safety documentation (quick read).
This being said, your code now changes:
(1) We must check if val is nullable, before accessing it. We can either use ! or .? to safely access it; note: the null check operator ! is the least safe null operator, it's likely that it will result in run time exceptions.
validator: (val) {
val==null || val?.isEmpty || val?.length<3
? "Enter Username 3+ characters"
: null
}
(2) I can't infer which method / variable can be null by myself
(3) It depends on what you're trying to do, but I guess that you're trying to implement the Firebase authentication process, in which your user can and should be null before authenticating. Therefore, your function should accept a nullable user value (User?). In there, we do the usual null check and we add a ! operator to promote its value in case user is not null. As aforementioned, Dart isn't always able to infer nullability of variables.
MyUser _userFromFirebaseUser(User? user) {
return user==null ? null : MyUser(userId: user!.uid);
}
Note how using a null-check ! here is perfectly safe, only because you just checked its nullability in the same line (nonetheless, keep a wise-eye when you refactor this).
EDIT. (4)
I can't infer where exactly your exception is fired, but since you want to validate your form, then here's my code from a project of mine:
// inside "saveForm()"...
var currentState = this._formKey.currentState;
if (currentState == null)
return; // this just means something went wrong
if (!currentState.validate()) return; // todo if invalid, handle this, maybe show a snackbar and stuff...
Note how the variable currentState is now promoted to be non-nullable WITHOUT using the null check operator !, which is just good practice (avoid using ! whenever possible, PREFER using null-aware operators, such as ?. or ??, or ?=)

Being empty is not the same as being null. So before you can check an object is empty, you need to check against null first.
if (obj != null && !obj.isEmpty) {}

Related

Flow type refine mixed to function

I'm using koa which has middleware props typed as mixed, so I'm trying to do something along the lines of the following below but I'm getting an error that Cannot call `ctx.render` because mixed [1] is not a function.Flow(not-a-function)
app.use(async (ctx, next) => {
// some other code above it
await ctx.render('index');
});
My question is, what's the correct way to do a type refinement that this is a function and then allow me to call it?
You can refine this to a function, but calling it is another matter.
app.use(async (ctx, next) => {
if (typeof ctx.render === 'function') {
// Now we know that `ctx.render` is a function.
}
});
Flow actually has a special case for this, this is called an "unknown function." We know that ctx.render is a function, but we don't know anything about its arguments or return type so we can't safely do anything with it except pass it around. How can we safely call ctx.render(1) if we don't know that ctx.render takes a number?
What's more, we can't know anything about it. There is no reflection mechanism provided by JavaScript that we could interrogate for enough information about this function to be able to safely call it. The only thing we can find is the static arity (ctx.render.length) but this by itself is not reliable or sufficient.
If we had more information, like say if this were a union type instead of mixed, then we could use type refinement to do what we want:
(arg: boolean | (number => void)) => {
if (typeof arg === 'function') {
arg(1); // safe because if this is a function, we know it takes a number
}
};
In this case the most reasonable solution is to type through any. Assuming that we know that we should only ever receive one type of render function, then we just forcibly cast it to that type with all the blaring caveats one would expect:
// I don't know what the type of your render function is, but you would put it
// here:
type RenderFunction = string => void;
app.use(async (ctx, next) => {
// some other code above it
if (typeof ctx.render === 'function') {
// DANGER: We make a hard assumption here that the render function is
// always of this specific type. If it is ever of any other type then
// behavior is undefined!
await ((ctx.render: any): RenderFunction)('index');
}
});
Also it sounds to me like the koa libdef could probably be improved upon.

How to distinguish between the reasons Firebase Transactions return null?

From my understanding of Transactions, it can return null for two reasons:
There is actually no value at the node where the transaction is being performed.
The local cache is empty as Firebase Cloud Functions is stateless. Therefore, it may return null the very first time and it will re-run the function.
My question is, how do we distinguish between these two cases? Or does firebase do the distinction by itself?
Myref.transaction(function(currentData) {
if(currentData != null) {
return currentData + 1;
} else {
console.log("Got null")
return;
}
}, function(error, committed, snapshot) {
if(!committed) {
// The transaction returned null.
// But don't know if it's because the node is null or
// because the transaction failed during the first iteration.
}
});
In the above example, the transaction callback will be passed null both when the value at Myref is non-existent and when it attempts to get the data in the very first try when executing the transaction.
If the value of Myref is actually empty, I want the number 1238484 to be filled in there. If it is not, and the null is actually being thrown because of a wrong read by the transaction, how do I make this distinction?
PS: Please don't suggest a listener at the node. Is there any other more effective way of doing this?
On initial run of the transaction and value returned from the update function is undefined, onComplete is invoked with error to be null
For subsequent runs when there is no data, a reason for aborting the transaction is provided.
You can check the value of error to differentiate whether there is no data at the reference or local cache is empty.
error === null && !committed // local cache is empty
error !== null && !committed // there is no data
This is internal implementation detail and you shouldn't rely on it for your queries.

Why Flow still complains about null values for document.getElementById

Why even with an IF check, Flow still complains about a possibly null value
if(document && document.getElementById("myID") && document.getElementById("myID").offsetWidth){
console.log(document.getElementById("myID").offsetWidth);
}
Gives this error
^ property `offsetWidth`. Property cannot be accessed on possibly null value
Flow has no way to know that the success of first call to getElementById means that the later ones will also succeed. For all it knows, reading the offsetWidth property could cause getElementById to start returning null the next time it is called.
You'll need to store the value, e.g.
const myIdEl = document && document.getElementById("myID");
if(myIdEl && myIdEl.offsetWidth) {
console.log(myIdEl.offsetWidth);
}
this way there is no way for myIdEl to become null after it has been referenced.
For HTMLElement (and extensions of HTMLElement like VideoHTMLElement) in FlowType, I'd recommend using instanceof to validate the Type and to validate that it's not null.
Also, I don't believe you need to check if document exists, that is defined globally in flow (1)*
<HTMLElement> Example
const myIdEl: ?HTMLElement = document.getElementById('myID');
if (myIdEl instanceof HTMLElement) {
// continue
console.log(myIdEl.offsetWidth);
}
<HTMLSelectElement> Example
const selectEl: ?HTMLElement = document.getElementById('someSelectElement');
// Checks correct type (!null !undefined come for free)
if (selectEl instanceof HTMLSelectElement) {
const selectedVal = selectEl.options[selectEl.selectedIndex].value;
}
<HTMLVideoElement> Example using invariant
import invariant from 'invariant';
const videoContent = document.getElementById('video-player');
invariant(videoContent instanceof HTMLVideoElement, 'No video element');
// do stuff with video api
videoContent.volume = 0;
videoContent.plause();
https://github.com/facebook/flow/blob/f3f29f7fd8c5aa73ac5a8a546ccfbc29cd7505fe/lib/dom.js#L1288

Flowtype constantly requiring null checks

I'm wondering how to avoid these numerous null checks or at least understand what the point is because it seems counter-productive.
Flowtype is giving me an error for this if I omit the null check:
var myEl = new MyElement()
if (document.body != null) { // error on next line if omitted
document.body.appendChild(myEl)
}
I have to do that null check for the document body in every single callback too, because who knows, maybe the body is null here right?!
I think this is total overkill. Not only that, but what's the point of such a simple nullcheck? It will just silently skip over a vital part of the program and exhibit undefined behavior somewhere else and make debugging the app that much harder.
I'd really prefer just having a null exception at this point if an error ever happens here, because to be really sure this tiny 2-line code segment that I'd write in javascript would have to be like this in flowtype:
var myEl = new MyElement()
if (document.body != null) {
document.body.appendChild(myEl)
} else {
console.error("null error")
}
So 4 additional code lines and some nesting just to trace something I'd get for free if I just let the app run into an error. And I need those 4 lines on every single querySelector. On every single document.body. On every single getElementByTagName. This alone probably increases my entire codebase by 10%.
What's the point of enforcing this so strictly?
In other languages I'd also be able to try-catch around these hotspots gradually as needed, flow doesn't let me do that either. It shows errors whether I add a try-catch or not.
By using a type checker, you are opting into the rules that it enforces. Accessing a property on a nullable type is one of those restrictions. So if you want to have exceptions for null values, you need to explicitly throw to prove to Flow that it is what you want. You could for instance make a module like
if (!document.body) throw new Error("Unexpectedly missing <body>.");
export const body: HTMLElement = document.body;
export function querySelector(el: HTMLElement, selector: string): HTMLElement {
const result = el.querySelector(selector);
if (!result) throw new Error(`Failed to match: ${selector}`);
return result;
}
By throwing, these functions explicitly say "I will return an element" in all cases, and in null cases, they will throw exceptions.
Then in your normal code, you are guaranteed you can use those
import {body, querySelector} from "./utils";
body.appendChild(document.createElement('div'));
querySelector(body, 'div').setAttribute('thing', 'value');
and it will typecheck property.
When I know for sure that my variable won't be null and Flow doesn't, I use an unwrap() function:
export default function unwrap<T>(value: T): $NonMaybeType<T> {
if (value !== null && value !== undefined) return value
throw new Error('Unwrapping not possible because the variable is null or undefined!')
}

What is the main purpose of audit-argument-checks?

For example,
findByType: function(type) {
check(type, String);
var role;
if(type === 'user') {
role = 'user'
} else if(type === 'admin') {
role = 'admin'
}
Accounts.find({role: role})
}
in this case, check(type, String); is not necessary?
so if I have my own check codes, there is no need to use audit-argument-checks?
audit-argument-checks requires all your Meteor methods to actually check each of their arguments, if they don't, an error message will be logged on the server and presented to the client as a "500 Internal server error".
This package helps you making sure all your methods implement basic security checking, but it does not check your arguments for you (it can't guess the intended types of your arguments), so you still need to call check unless you want this package to throw errors.
https://docs.meteor.com/#/full/auditargumentchecks
You could also improve your check by doing the following (unless you want to be searching for Accounts.find({role: null}) when the type is neither 'admin' nor 'user'):
findByType: function(type) {
check(type, Match.Where((type)=>{
check(type, String);
return type=="user" || type=="admin";
}));
Accounts.find({role: type});
}
I know it's been a while this question has been asked but I found it while searching for something else and thought I could suggest a better form.

Resources