postJSON method

Future<BaseResponse?> postJSON(
  1. RequestData rd
)

post_JSON to our backend as the user

Implementation

Future<http.BaseResponse?> postJSON(RequestData rd) async {
  var headers = {HttpHeaders.contentTypeHeader: 'application/json'};
  rd.json ??= {};
  rd.json!['user'] = _user?.toJson();
  try {
    if (rd.multipartFiles.isNotEmpty) {
      http.MultipartRequest? mreq;
      try {
        var fullURL = Uri.parse(_baseurl + rd.route);
        mreq = http.MultipartRequest('POST', fullURL)
          ..headers.addAll({HttpHeaders.authorizationHeader: _api_key})
          ..fields.addAll(
            /*flatten()*/ rd.json!.map<String, String>((key, value) {
              if (value is Map || value is List) {
                return MapEntry(key, jsonEncode(value));
              }
              return MapEntry(key, value.toString());
            }),
          ); // send structured fields as JSON so the backend can parse them reliably

        // Build multipart files sequentially to keep peak memory lower.
        final usedUploadImageNames = <String>{};
        for (final fxfile in rd.multipartFiles) {
          final xfile = await fxfile;
          String name = xfile.name;
          if (rd.route == _uploadImage_r) {
            DateTime ts = parseTimestampImageFilename(name) ??
                parseTimestampImageFilename(
                    canonicalTimestampFilenameForXFile(xfile)) ??
                DateTime.now();
            name = formatTimestampImageFilename(ts);
            while (usedUploadImageNames.contains(name)) {
              ts = ts.add(const Duration(seconds: 1));
              name = formatTimestampImageFilename(ts);
            }
            usedUploadImageNames.add(name);
          }
          final creation = FileStat.statSync(xfile.path)
              .changed
              .toUtc()
              .millisecondsSinceEpoch;
          final path = xfile.path;
          mreq.files.add(await http.MultipartFile.fromPath(
            creation.toString(),
            path,
            filename: name,
          ));
        }
        // Avoid logging full fields (can be huge and contains credentials) and reduces memory churn.
        debugPrint(
            'sending multipart request ${rd.route} (files: ${mreq.files.length})');
        var res = (rd.timeout == null)
            ? await _client.send(mreq)
            : await _client.send(mreq).timeout(rd.timeout!);
        BackendReachability.instance.clearFailure();
        return res;
      } on SocketException catch (e) {
        if (!_logReachabilityFailureOnce(e)) {
          debugPrint('Socket-Fehler bei Multipart-Request: ${e.message}');
        }
        if (e.message.contains('Write failed') ||
            e.message.contains('connection abort') ||
            e.message.contains('Connection refused') ||
            e.message.contains('Software caused connection abort')) {
          debugPrint(
              'Hintergrund-Socket-Fehler erkannt, App möglicherweise im Hintergrund');
        }
        rethrow;
      } on Exception catch (e) {
        debugPrint('multipartRequest, failed: $e');
        rethrow;
      }
    } else {
      final req =
          makepost(rd.route, headers: headers, body: jsonEncode(rd.json));
      try {
        // Erhöhe die Robustheit des Requests speziell für Hintergrundausführung
        final response = await send(
          req,
          timeout:
              rd.timeout ?? Duration(minutes: 2), // Längerer Standard-Timeout
          returnsBinary: rd.returnsBinary,
        );

        return response;
      } on SocketException catch (e) {
        if (!_logReachabilityFailureOnce(e)) {
          debugPrint('Socket-Fehler bei HTTP-Request: ${e.message}');
        }
        if (e.message.contains('Write failed') ||
            e.message.contains('connection abort') ||
            e.message.contains('Connection refused') ||
            e.message.contains('Software caused connection abort')) {
          debugPrint(
              'Hintergrund-Socket-Fehler erkannt, App möglicherweise im Hintergrund');
        }
        rethrow;
      } catch (e) {
        if (!_logReachabilityFailureOnce(e)) {
          debugPrint('request failed: $e');
        }
        rethrow;
      }
    }
  } catch (e) {
    // Don't swallow OOM: returning null will trigger retries and worsen memory pressure.
    if (e is OutOfMemoryError) rethrow;
    final msg = e.toString();
    if (msg.contains('Out of Memory') || msg.contains('Exhausted heap')) {
      rethrow;
    }
    if (!_logReachabilityFailureOnce(e)) {
      debugPrint("request failed, cause : $e");
    }
    return null;
  }
}