So I have this code in my flutter app - here the function refreshState is being called by the method foo which is passing in a lambda.However during debugging it says the callback is null. Any ideas why this is happening because of this my callback code is not being executed.
void refreshState(Function callback)
{
if(isAlive) {
setState(() {
if (callback != null) {
callback;
}
});
}
}
at one point in my code I am doing this
void didPush() {
foo();
}
void foo()
{
refreshState(() { //<------------------This lambda is showing up as null in the paramter of refreshState
isBusy = true;
});
}
Any ideas of why this lamda is showing up as null in the refreshState function parameter ?
You misunderstand the debug view here. It is a function () returning (=>) null. You just do not execute it.
() => ...
This is just a shortcut for:
() {
return ...
}
To execute your callback you need to add parantheses though. That would be:
setState(() {
if (callback != null)
callback();
});
Related
I'm trying to implement an event callback directly in the constructor, but for some reason it does not compile and I do not understand what's the issue with my code.
abstract class Base {
final Future<bool>? onMagic;
Base({
this.onMagic
});
Future<void> doSomething() async {
if(onMagic != null) {
// does not work... why?
// final useMagic = await onMagic!();
onMagic?.then((magic) {
if(magic) print("TODO make something magical");
});
}
}
}
class RealMagic extends Base {
RealMagic() : super(
// Error: The argument type 'Future<bool> Function()' can't be assigned to the parameter type 'Future<bool>?'.
onMagic: () async => await _magic();
);
Future<bool> _magic() async {
return true;
}
}
I inlined the error above. If that's not possible which alternatives do I have to handle the optional callback?
The problem is with the type of the onMagic. It's not a Future<bool>, it should be a Future<bool> Function()?
abstract class Base {
final Future<bool> Function()? onMagic;
Base({this.onMagic});
Future<void> doSomething() async {
onMagic?.call().then((magic) {
if (magic) print("TODO make something magical");
});
}
}
class RealMagic extends Base {
RealMagic()
: super(
onMagic: () async => await _magic(),
);
static Future<bool> _magic() async { // Made this static so it can be accessed in the constructor
return true;
}
}
Here's the simple Dart code which works, but generate a compiler warning:
Future<String> fetchUserOrder() {
return Future.delayed(Duration(seconds: 2), () {
throw UnsupportedError('out of coffee');
// return 'ristretto';
});
}
void main() {
print('main: start');
fetchUserOrder()
.then((value) {
print(value);
})
.catchError((error) => print(error.message)) // DISPLAYED WARNING: A value of type 'void' can t be returned by the 'onError' handler because it must be assignable to 'FutureOr<Null>'.
.whenComplete(() => print('complete'));
}
How is it possible to avoid the compiler warning "A value of type 'void' can t be returned by the 'onError' handler because it must be assignable to 'FutureOr'." displayed on the catchError line ?
The direct way is to use an async function:
void main() async {
print('main: start');
try {
var value = fetchUserOrder();
print(value);
} catch (error) {
print((error as dynamic).message);
} finally {
print('complete'));
}
}
If you want to use Future methods instead, you can make the then call return a Future<void> as:
void main() {
print('main: start');
fetchUserOrder()
.then<void>((value) {
print(value);
})
.catchError((error) => print(error.message))
.whenComplete(() => print('complete'));
}
I'd probably personally go with including the catchError in the then call:
void main() {
print('main: start');
fetchUserOrder().then<void>((value) {
print(value);
}, onError: (dynamic error) {
print(error.message);
}).whenComplete(() => print('complete'));
}
Using then allows you to set the required return type of the onError callback, and it can be used even when there is no value callback, as:
someFuture.then<void>((_){}, onError: (e, s) {
do something without returning a value;
});
What I try to achieve is for the TextInputField to be autovalidated once there is more than 1 character entered.
Here's my initState (simplified):
#override
void initState() {
autoValidateList.addAll([
_autoValidateEmail,
_autoValidateCompanyName,
_autoValidatePhoneNo,
_autoValidateName,
_autoValidateSurname
]);
textEditingControllersList.addAll([
_emailController,
_companyNameController,
_phoneNoController,
_nameController,
_surnameController
]);
for (int i = 0; i < textEditingControllersList.length; i++) {
TextEditingController controller = textEditingControllersList[i];
controller.addListener(() => () {
print(
'Listener entered. companyName? ${controller == _companyNameController}');
if (controller.text.length > 0) {
print('=> true');
setState(() => autoValidateList[i] = true);
} else {
print('=> false');
setState(() => autoValidateList[i] = false);
}
});
}
_emailController.text = widget.loginData.email;
super.initState();
}
If I add the listeners not in a loop, for example:
_emailController.addListener(() => setState(() {
if (_emailController.text.length > 0) {
_autoValidateEmail = true;
} else {
_autoValidateEmail = false;
}
}));
It works fine.
None of the print statements get executed. What am I missing here?
There's a very insidious error here. Notice that in your addListener, you're passing a function that returns a function. What you want to execute is the function that is being returned, but you're actually executing the function that you're passing.
In a more clear syntax, you're doing this:
controller.addListener(() {
return () {
// Your code
};
});
So, what is happening is:
controller.addListener(() {
print('This is going to be executed');
return () {
print('This is NOT going to be executed. Your code used to be here.');
};
});
Instead of:
controller.addListener(() => () {
...
});
You should be doing:
controller.addListener(() {
...
});
Also, this is not related, but you should be calling super at the beginning of initState, not at the end.
I want setState in AsyncStorage block but there is a error: undefined is not an object(evaluating 'this.setState').
constructor(props){
super(props);
this.state={
activeID:this.props.data.channel[0].id
};
}
componentDidMount() {
AsyncStorage.getItem(this.props.data.type,function(errs,result){
if (!errs) {
if (result !== null) {
this.setState({activeID:result});
}
}
});
}
_buttonPressed = (id,name,index) => {
this.setState({activeID:id});
AsyncStorage.setItem(this.props.data.type,id,function(errs){
if (errs) {
console.log('error');
}
if (!errs) {
console.log('succeed');
}
});
}
Any help will be appreciate, thanks.
This is a binding issue. Try the following:
componentDidMount() {
AsyncStorage.getItem(this.props.data.type, (errs,result) => {
if (!errs) {
if (result !== null) {
this.setState({activeID:result});
}
}
})
}
The reason for this is that functions declared with the function specifier create a context of their own, that is, the value of this is not the instance of your component. But "fat arrow" functions do not create a new context, and so you can use all methods inside. You could as well bind the function in order to keep the context, but in this case I think that this solution is much cleaner.
I find another solution, but martinarroyo's answer is much more cleaner.
componentDidMount() {
this._loadInitialState().done();
}
async _loadInitialState(){
try{
var value=await AsyncStorage.getItem(this.props.data.type);
if(value!=null){
this._saveActiveID(value);
}
} catch(error){
}
}
_saveActiveID(id) {
this.setState({activeID: id});
}
SOLVED! It was a Knockout issue (wrong binding). But maybe someone likes to argue or comment about the code in general (dataservice, viewmodel, etc).
I tried to build a Breeze sample, where I get one database record (with fetchEntityByKey), display it for updating, then with a save button, write the changes back to the database. I could not figure out how to get it to work.
I was trying to have a dataservice ('class') and a viewmodel ('class'), binding the viewmodel with Knockout to the view.
I very much appreciated if someone could provide a sample or provide some hints.
Thankx, Harry
var dataservice = (function () {
var serviceName = "/api/amms/";
breeze.NamingConvention.camelCase.setAsDefault();
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName,
entityManager: entityManager,
init: init,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function init() {
return getMetadataStore();
}
function getMetadataStore() {
return entityManager.fetchMetadata()
.then(function (result) { return dataservice; })
.fail(function () { window.alert("fetchMetadata:fail"); })
.fin(function () { });
}
function saveChanges() {
return entityManager.saveChanges()
.then(function (result) { return result; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
})();
var viewmodel = (function () {
var viewmodel = {
location: null,
error: ko.observable(""),
init: init,
saveChanges: null
};
return viewmodel;
function init() {
return dataservice.init().then(function () {
viewmodel.saveChanges = dataservice.saveChanges;
return getLocation();
})
}
function getLocation() {
return dataservice.getLocation().then(function (result) {
return viewmodel.location = result;
})
}
})();
viewmodel.init().then(function () {
ko.applyBindings(viewmodel);
});
Glad you solved it. Can't help noticing that you added a great number of do-nothing callbacks. I can't think of a reason to do that. You also asked for metadata explicitly. But your call to fetchEntityByKey will do that implicitly for you because, as you called it, it will always go to the server.
Also, it is a good idea to re-throw the error in the fail callback within a dataservice so that a caller (e.g., the ViewModel) can add its own fail handler. Without re-throw, the caller's fail callback would not hear it (Q promise machinery acts as if the first fail handler "solved" the problem).
Therefore, your dataservice could be reduced to:
var dataservice = (function () {
breeze.NamingConvention.camelCase.setAsDefault();
var serviceName = "/api/amms/";
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName, // why are you exporting this?
entityManager: entityManager,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function saveChanges() {
return entityManager.saveChanges()
.fail(function () {
window.alert("saveChanges failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () {
window.alert("fetchEntityByKey failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
})();
I don't want to make too much of this. Maybe you're giving us the stripped down version of something more substantial. But, in case you (or a reader) think those methods are always necessary, I wanted to make clear that they are not.