FlutterFlow / flutterflow-issues

A community issue tracker for FlutterFlow.
105 stars 18 forks source link

API Interceptor Exception "Cannot modify unmodifiable map" - immutable options.headers #3238

Open Coldzer0 opened 1 week ago

Coldzer0 commented 1 week ago

Can we access your project?

Current Behavior

I have an interceptor in my project. It's straightforward, but the requests never hit the backend server.

The interceptor code is

// Automatic FlutterFlow imports
// Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import '/backend/api_requests/api_interceptor.dart';
import 'package:telegram_web_app/src/telegram/internal.dart' as tg;

class AuthorizationInterceptor extends FFApiInterceptor {
  @override
  Future onRequest({
    required ApiCallOptions options,
  }) async {
    // Perform any necessary calls or modifications to the [options] before
    // the API call is made.
    String telegramAuthToken = base64Encode(tg.initData);
    options.headers['Authorization'] = 'Bearer $telegramAuthToken';
    return options;  
  }

  @override
  Future onResponse({
    required ApiCallResponse response,
    required Future Function() retryFn,
  }) async {
    // Perform any necessary calls or modifications to the [response] prior
    // to returning it.
    return response;
  }
}

When the code at options.headers['Authorization'] = 'Bearer $telegramAuthToken'; runs it throw an exception Cannot modify unmodifiable map

Expected Behavior

headers to be modifiable ^_^

Steps to Reproduce

// Automatic FlutterFlow imports
// Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import '/backend/api_requests/api_interceptor.dart';
import 'package:telegram_web_app/src/telegram/internal.dart' as tg;

class AuthorizationInterceptor extends FFApiInterceptor {
  @override
  Future<ApiCallOptions> onRequest({
    required ApiCallOptions options,
  }) async {
    // Perform any necessary calls or modifications to the [options] before
    // the API call is made.
    options.headers['Authorization'] = 'Bearer test';
    return options;
  }

  @override
  Future<ApiCallResponse> onResponse({
    required ApiCallResponse response,
    required Future<ApiCallResponse> Function() retryFn,
  }) async {
    // Perform any necessary calls or modifications to the [response] prior
    // to returning it.
    return response;
  }
}

Reproducible from Blank

Bug Report Code (Required)

IT4oz/Hqx89grs9J1s/IdfoxnSQWC0d9UIEVi8pBZywgfZzzBLN/fPfQP1NCTcO/YFZ2f06cp14K+d7WvuHTG/VdA0utfpxEyatcZzvvXT6lbpOTF7meS3FTN+JYN3az4aaJvyZ+IvZudFpg7DuLN96vYCbUJpCwPGo4Sq/LZO4=

Visual documentation

image

Environment

- FlutterFlow version: v4.1.63+ released June 26, 2024
- FlutterFlow CLI Version: 0.0.18
- Platform: Web
- Browser name and version: Version 126.0.6478.115 (Official Build) (64-bit)
- Operating system and version affected: Windows 11 Pro 23H2 Build 22631.3737

Additional Information

No response

Coldzer0 commented 1 week ago

Same here this problem happens when I use --fix in FlutterFlow CLI it auto convert headers: {} to headers: const {}

Coldzer0 commented 1 week ago

using prefer_const_declarations: false in analysis_options.yaml fixes it

Is there any way we can edit the analysis_options.yaml before download or maybe pass some arg to flutterflow CLI to control it

ignalauret commented 1 week ago

Hey @Coldzer0 thanks for your report. What you are describing makes perfect sense, but I'm not able to find the line where the const is added by --fix. I can see on the definition of the calls, it's staying as headers: {} when I download the code.
Could you help me find where the line that changes is?
Also just FYI if you want you can disable this --fix from Settings > AppDetails > Download Settings > Run "dart fix".

Coldzer0 commented 1 week ago
lib\backend\api_requests\api_calls.dart
  prefer_const_literals_to_create_immutables • 6 fixes
  unnecessary_brace_in_string_interps • 5 fixes
  unused_import • 2 fixes
Alezanello commented 3 days ago

Hello!

The issue stems from the Flutter option dart --fix converting headers: {} to headers: const {}, making the map immutable.

To work around this issue, you can avoid directly modifying the headers map and instead build a new map.

Here is an approach to ensure the headers remain mutable:

// Automatic FlutterFlow imports
// Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import '/backend/api_requests/api_interceptor.dart';
import 'package:telegram_web_app/src/telegram/internal.dart' as tg;

class AuthorizationInterceptor extends FFApiInterceptor {
  @override
  Future onRequest({
    required ApiCallOptions options,
  }) async {
    // Create a mutable copy of the headers
    final headers = Map<String, String>.from(options.headers);

    // Perform any necessary calls or modifications to the [options] before
    // the API call is made.
    String telegramAuthToken = base64Encode(tg.initData);
    headers['Authorization'] = 'Bearer $telegramAuthToken';

    // Create a new options object with the modified headers
    final newOptions = options.copyWith(headers: headers);

    return newOptions;
  }

  @override
  Future onResponse({
    required ApiCallResponse response,
    required Future Function() retryFn,
  }) async {
    // Perform any necessary calls or modifications to the [response] prior
    // to returning it.
    return response;
  }
}

In this approach:

  1. A mutable copy of the headers is created using Map<String, String>.from(options.headers).
  2. The mutable copy is modified.
  3. A new ApiCallOptions object is created with the modified headers using options.copyWith(headers: headers).
  4. The modified ApiCallOptions object is returned.

This way, you avoid the issue caused by immutable maps and ensure the headers can be modified as needed.

I'm not entirely sure if this will work, as I haven't tested it myself yet. Please let me know if it does, and if not, we can find another workaround together!

Best regards,
Azanello

Coldzer0 commented 3 days ago

options copyWith is not implemented in the ApiCallOptions class.

Alezanello commented 3 days ago

True, sorry for this misleading workaround

Try to create a new option using object with modified headers:

final newOptions = ApiCallOptions(
      callName: options.callName,
      callType: options.callType,
      apiUrl: options.apiUrl,
      headers: headers,
      params: options.params,
      bodyType: options.bodyType,
      body: options.body,
      returnBody: options.returnBody,
      encodeBodyUtf8: options.encodeBodyUtf8,
      decodeUtf8: options.decodeUtf8,
      alwaysAllowBody: options.alwaysAllowBody,
      cache: options.cache,
    );

Let me know if this works for you

Coldzer0 commented 3 days ago

Yep, this workaround works fine. So, every time I need to do an interception, I need to use this, or will there be a way to fix it or something?

For now, I'm using the CLI tool without --fix command