writeHostApi method

  1. @override
void writeHostApi(
  1. CppOptions generatorOptions,
  2. Root root,
  3. Indent indent,
  4. Api api, {
  5. required String dartPackageName,
})
override

Writes a single Host Api to indent.

Implementation

@override
void writeHostApi(
  CppOptions generatorOptions,
  Root root,
  Indent indent,
  Api api, {
  required String dartPackageName,
}) {
  assert(api.location == ApiLocation.host);
  if (getCodecClasses(api, root).isNotEmpty) {
    _writeCodec(generatorOptions, root, indent, api);
  }

  final String codeSerializerName = getCodecClasses(api, root).isNotEmpty
      ? _getCodecSerializerName(api)
      : _defaultCodecSerializer;
  indent.writeln('/// The codec used by ${api.name}.');
  _writeFunctionDefinition(indent, 'GetCodec',
      scope: api.name,
      returnType: 'const flutter::StandardMessageCodec&', body: () {
    indent.writeln(
        'return flutter::StandardMessageCodec::GetInstance(&$codeSerializerName::GetInstance());');
  });
  indent.writeln(
      '$_commentPrefix Sets up an instance of `${api.name}` to handle messages through the `binary_messenger`.');
  _writeFunctionDefinition(indent, 'SetUp',
      scope: api.name,
      returnType: _voidType,
      parameters: <String>[
        'flutter::BinaryMessenger* binary_messenger',
        '${api.name}* api'
      ], body: () {
    for (final Method method in api.methods) {
      final String channelName =
          makeChannelName(api, method, dartPackageName);
      indent.writeScoped('{', '}', () {
        indent.writeln(
            'auto channel = std::make_unique<BasicMessageChannel<>>(binary_messenger, '
            '"$channelName", &GetCodec());');
        indent.writeScoped('if (api != nullptr) {', '} else {', () {
          indent.write(
              'channel->SetMessageHandler([api](const EncodableValue& message, const flutter::MessageReply<EncodableValue>& reply) ');
          indent.addScoped('{', '});', () {
            indent.writeScoped('try {', '}', () {
              final List<String> methodArgument = <String>[];
              if (method.arguments.isNotEmpty) {
                indent.writeln(
                    'const auto& args = std::get<EncodableList>(message);');

                enumerate(method.arguments, (int index, NamedType arg) {
                  final HostDatatype hostType = getHostDatatype(
                      arg.type,
                      root.classes,
                      root.enums,
                      (TypeDeclaration x) =>
                          _shortBaseCppTypeForBuiltinDartType(x));
                  final String argName = _getSafeArgumentName(index, arg);

                  final String encodableArgName =
                      '${_encodablePrefix}_$argName';
                  indent.writeln(
                      'const auto& $encodableArgName = args.at($index);');
                  if (!arg.type.isNullable) {
                    indent.writeScoped(
                        'if ($encodableArgName.IsNull()) {', '}', () {
                      indent.writeln(
                          'reply(WrapError("$argName unexpectedly null."));');
                      indent.writeln('return;');
                    });
                  }
                  _writeEncodableValueArgumentUnwrapping(
                    indent,
                    root,
                    hostType,
                    argName: argName,
                    encodableArgName: encodableArgName,
                    apiType: ApiType.host,
                  );
                  final String unwrapEnum =
                      isEnum(root, arg.type) && arg.type.isNullable
                          ? ' ? &(*$argName) : nullptr'
                          : '';
                  methodArgument.add('$argName$unwrapEnum');
                });
              }

              final HostDatatype returnType = getHostDatatype(
                  method.returnType,
                  root.classes,
                  root.enums,
                  _shortBaseCppTypeForBuiltinDartType);
              final String returnTypeName = _hostApiReturnType(returnType);
              if (method.isAsynchronous) {
                methodArgument.add(
                  '[reply]($returnTypeName&& output) {${indent.newline}'
                  '${_wrapResponse(indent, root, method.returnType, prefix: '\t')}${indent.newline}'
                  '}',
                );
              }
              final String call =
                  'api->${_makeMethodName(method)}(${methodArgument.join(', ')})';
              if (method.isAsynchronous) {
                indent.format('$call;');
              } else {
                indent.writeln('$returnTypeName output = $call;');
                indent.format(_wrapResponse(indent, root, method.returnType));
              }
            }, addTrailingNewline: false);
            indent.add(' catch (const std::exception& exception) ');
            indent.addScoped('{', '}', () {
              // There is a potential here for `reply` to be called twice, which
              // is a violation of the API contract, because there's no way of
              // knowing whether or not the plugin code called `reply` before
              // throwing. Since use of `@async` suggests that the reply is
              // probably not sent within the scope of the stack, err on the
              // side of potential double-call rather than no call (which is
              // also an API violation) so that unexpected errors have a better
              // chance of being caught and handled in a useful way.
              indent.writeln('reply(WrapError(exception.what()));');
            });
          });
        });
        indent.addScoped(null, '}', () {
          indent.writeln('channel->SetMessageHandler(nullptr);');
        });
      });
    }
  });

  _writeFunctionDefinition(indent, 'WrapError',
      scope: api.name,
      returnType: 'EncodableValue',
      parameters: <String>['std::string_view error_message'], body: () {
    indent.format('''
return EncodableValue(EncodableList{
\tEncodableValue(std::string(error_message)),
\tEncodableValue("Error"),
\tEncodableValue()
});''');
  });
  _writeFunctionDefinition(indent, 'WrapError',
      scope: api.name,
      returnType: 'EncodableValue',
      parameters: <String>['const FlutterError& error'], body: () {
    indent.format('''
return EncodableValue(EncodableList{
\tEncodableValue(error.code()),
\tEncodableValue(error.message()),
\terror.details()
});''');
  });
}