Fonctions cloud appelables retournant Null dans Flutter

Oct 09 2020

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

RafaelZasas Oct 12 2020 at 21:55

Solution:

  • Utiliser les constructeurs pour les widgets sans état
  • éviter .then()
  • Cloud Functions doit revenir Promise.resolve()lorsqu'il est appelé en asyncfonction.

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>;

}