Fonctions cloud appelables retournant Null dans Flutter
Mon but:
Je souhaite appeler une fonction cloud dans mon application Flutter qui récupère un objet JSON d'un serveur Python-FastAPI et affiche le contenu dans Alert Dialogue .
L'erreur:
Le service de fonction appelable dans mon application Flutter reçoit null. Ma boîte de dialogue d'alerte affiche "Erreur de valeur nulle" déclenchée par mon code.
Opérations côté cloud:
My Cloud Function fonctionne en 2 parties:
- Recevoir les données du client (Flutter APP) dans la fonction Http appelable
- Appeler l'API Python => revenir à la fonction Cloud qui revient au client
Fonction Cloud:
exports.getRandomPassword = functions.https.onCall(async (data, context) => {
const useSymbols = data.useSymbols;
const pwLength= data.pwLength;
const debug ={
received_data_type: typeof data,
received_data:data,
pwLen_type: typeof pwLength,
pwLength,
useSymbols_type:typeof useSymbols,
useSymbols,
}
console.log(debug);
await callAPI(pwLength,useSymbols).then((res:any) =>{
console.log(`Resulting Payload: ${res}`);
return res});
});
Appel Python-FastAPI:
async function callAPI(pwLength: any, useSymbols: any) {
// BUILD URL STRING WITH PARAMS
const ROOT_URL = `http://[IP_Address]/password?pwd_length=${pwLength}&use_symbols=${useSymbols}`;
let res;
// let password: any; // password to be received
await http.get(ROOT_URL)
.then((response: any) => {
console.log("TO APP "+JSON.stringify(response));
console.log(response.getBody());
res = response.getBody() as Map<any, any>;
})
.catch((err: any) => {
console.log(err);
res= err;
});
return res;
}
La charge utile résultante fonctionne correctement, comme indiqué dans mes journaux:
Opérations côté client:
Cliquez sur le bouton dans Flutter:
onPressed: () async {
// call cloud function & use set state to store pw
await getPassword().then((String result) {
setState(() {
password = result;
});
});
showDialog(
context: context,
builder: (context) => DisplayPassword());
},
Ma fonction Flutter getPassword ():
Future<String> getPassword() async {
var pw;
final HttpsCallable callable = new CloudFunctions()
.getHttpsCallable(functionName: 'getRandomPassword')
..timeout = const Duration(seconds: 30);
try {
await callable.call(
<String, dynamic>{
'pwLength': 10,
'useSymbols': true,
},
).then((value) {
print(value.data);
print(value.data.runtimeType);
pw = value.data;
return pw;
});
} on CloudFunctionsException catch (e) {
print('caught firebase functions exception');
print('Code: ${e.code}\nmessage: ${e.message}\ndetails: ${e.details}');
return '${e.details}'; } catch (e) { print('caught generic exception'); print(e); return 'caught generic exception\n$e';
}
}
Ma fonction de mot de passe d'affichage:
class DisplayPassword extends StatelessWidget {
final String pw = (_MyPasswordGenPageState().password == null)
? 'null value error'
: _MyPasswordGenPageState().password;
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(pw),
);
}
}
REMARQUE * Je souhaite conserver la récupération du mot de passe en tant que fonction cloud, afin qu'elle puisse être utilisée sur les applications Web ainsi que sur les mobiles. Je suis cependant ouvert à la refactorisation de l'ensemble de l'opération si une meilleure solution se présente.
Réponses
Solution:
- Utiliser les constructeurs pour les widgets sans état
- éviter
.then()- Cloud Functions doit revenir
Promise.resolve()lorsqu'il est appelé enasyncfonction.
Sur le bouton clic:
dynamic result =
await callable.call(<String, dynamic>{
'pwLength': 10,
'useSymbols': true,
//await the result before continuing
});
setState(() {
// convert hashmap to list and get first val
password = result.data.values.toList()[0];
isLoading = false; // remove loading indicator
});
showDialog(
context: context,
builder: (context) =>
DisplayPassword(password));
Pour faciliter la lecture, j'ai supprimé la gestion des exceptions dans le code ci-dessus
Affichage de la boîte de dialogue d'alerte:
class DisplayPassword extends StatelessWidget {
var password; // check if null
DisplayPassword(this.password); // constructor
@override
Widget build(BuildContext context) {
// make sure null value isn't being passed to alert dialog widget
if (password == null) {
password = 'null value error';
}
return AlertDialog(
title: Text(password),
);
}
}
Fonction Cloud:
exports.getRandomPassword = functions.https.onCall(async (data, context) => {
const useSymbols = data.useSymbols;
const pwLength= data.pwLength;
let password;
password = await callAPI(pwLength,useSymbols);
const debug ={
received_data_type: typeof data,
received_data:data,
pwLen_type: typeof pwLength,
pwLength,
useSymbols_type:typeof useSymbols,
useSymbols,
ResultingPayloadType: typeof password,
ResultingPayload: password,
}
console.log(debug);
return Promise.resolve(password)
});
async function callAPI(pwLength: any, useSymbols: any) {
// BUILD URL STRING WITH PARAMS
const ROOT_URL = `http://[My_Api_IP_Address]/password?pwd_length=${pwLength}&use_symbols=${useSymbols}`;
const res = await http.get(ROOT_URL);
console.log(res.getBody());
return res.getBody() as Map<String , any>;
}