Funciones de nube invocables que devuelven nulos en Flutter

Oct 09 2020

Mi meta:

Deseo llamar a una función en la nube en mi aplicación Flutter que recupera un objeto JSON de un servidor Python-FastAPI y muestra contenido en Alert Dialogue .

El error:

El servicio de función invocable dentro de mi aplicación flutter recibe un valor nulo. Mi cuadro de diálogo de alerta muestra "Error de valor nulo" según lo desencadena mi código.

Operaciones del lado de la nube:

My Cloud Function funciona en 2 partes:

  • Reciba datos del cliente (aplicación Flutter) en la función Http invocable
  • Llamar a la API de Python => volver a la función de nube que vuelve al cliente

Función de nube:

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

});

Llamada 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 carga útil resultante funciona correctamente como se ve en mis registros:

Operaciones del lado del cliente:

Al hacer clic en el botón en 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());
                        },

Función getPassword () de mi Flutter:

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

Función de contraseña de mi pantalla:

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),
    );
  }
}

NOTA * Me gustaría mantener la recuperación de la contraseña como una función en la nube, por lo que se puede utilizar en aplicaciones web y móviles. Sin embargo, estoy dispuesto a refactorizar toda la operación si se presenta una solución mejor.

Respuestas

RafaelZasas Oct 12 2020 at 21:55

Solución:

  • Usar constructores para widgets sin estado
  • evitar .then()
  • Cloud Functions debería regresar Promise.resolve()cuando se invoca una asyncfunción.

Al hacer clic en el botón:


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


Para facilitar la lectura, eliminé el manejo de excepciones en el código anterior

Visualización del cuadro de diálogo de alerta:


    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),
        );
      }
    }

Función de nube:


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

}