Closed kamami closed 2 weeks ago
Hi @kamami,
Typically, we recommend using Amplify.isConfigured
to ensure Amplify.configure
only runs when the app is in an unconfigured state. Is something like this viable for you?
@pragma('vm:entry-point')
void backgroundCallbackDispatcher() async {
WidgetsFlutterBinding.ensureInitialized();
print(Amplify.isConfigured);
// conditionally configure
if (!Amplify.isConfigured) {
await _configureAmplify();
}
final apiRepository = APIRepository();
final authRepository = AuthRepository();
//track users location
}
If not, can you further explain your use case for calling Amplify.configure
multiple times?
Hi @Equartey ,
thanks for the quick response. I already tried Amplify.isConfigured
, but it is always false.
The backgroundCallbackDispatcher
is another entry-point for my app. This part of the app is meant to run in the background even if the app is detached. The location tracking is done by an SDK which uses iOS microtasks to invoke and run the backgroundCallbackDispatcher directly. Since iOS microtasks are killed by the OS after 20-30 secs, the backgroundCallbackDispatcher is invoked again, which triggers a new Amplify configuration. The configuration is not the problem, but adding the plugins before configuration causes the increased memory usage.
I think using Amplify in an additional entry point (@pragma('vm:entry-point')
) is a valid use case, but that also means multiple configurations need to be handled correctly.
BTW. using the same approach with Firebase/Firestore is working perfectly fine. The multiple initializations of Firebase are not causing any problems.
@pragma('vm:entry-point')
void backgroundCallbackDispatcher() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
await FirebaseAuth.instance.signInAnonymously();
const uuid = Uuid();
SafeDrivePodSDK.podData.stream().listen((event) async {
if (event is PodDateTimeRead) {
// Wait 15 seconds to ensure the connection is stable
if (Platform.isIOS) {
await Future<void>.delayed(
const Duration(seconds: 15),
);
}
SafeDrivePodSDK.podData.downloadTrips(
fromDate: DateTime.now().subtract(
const Duration(days: 365 * 10),
),
);
}
final userId = FirebaseAuth.instance.currentUser?.uid ?? 'anonymous';
final date = DateTime.now();
final day = DateFormat('yyyy-MM-dd').format(date);
final key = '${DateFormat('HH:mm:ss:SS').format(date)}_[${uuid.v4()}]';
final path = 'users/$userId/data/$day/events/$key';
FirebaseFirestore.instance.doc(path).set(
event.toMap(),
);
});
}
Thank you for the context, that's really helpful.
Couple comments and questions:
Amplify.configure
are you also getting multiple AmplifyAlreadyConfiguredException
s? Amplify.reset()
that you can try to call before calling _configureAmplify()
. This is more of a short term solution, but I would be curious to know if this changes the observed behavior. @pragma('vm:entry-point')
? AmplifyAlreadyConfiguredExceptions
.await Amplify.reset()
did not help, I still get an increased memory usage of ca. 100mb each time when adding the AWS plugins before configuration. @pragma('vm:entry-point')
is a dart annotation: https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.mdThe red errors show the time when await Amplify.addPlugins([authPlugin, apiPlugin]);
was invoked. I would not mind the increase of memory, but it never drops down again. Each configuration (only when adding plugins before) is increasing the level permanently.
Hi @kamami, we're working on reproducing this to validate some assumptions.
In the meantime, we suspect that each call to backgroundCallbackDispatcher
spawns a new isolate without context of previous configure calls. This would explain why Amplify.isConfigured
is returning false.
If this hypothesis is correct, calling Amplify.reset()
after your business logic may help by cleaning up memory to avoid additional calls consuming more. Can you give that a try and report your findings?
The point when my business logic is done, is not always clear. The backgroundCallbackDispatcher
is not only called again, when the app is killed. In this case I could observe the lifecycle of the app. Instead it is also called sometimes a second and third time, while the app is running. But this should not harm the performance as it unfortunately currently does. Here is my code for now. As you can see I reset Amplify before I do another configuration:
Future<void> _configureAmplify() async {
try {
await Amplify.reset();
const String env = String.fromEnvironment('ENV');
String configFile = 'assets/config/config.$env.json';
String s = await rootBundle.loadString(configFile);
Map<String, dynamic> config = jsonDecode(s);
AmplifyAuthCognito authPlugin = AmplifyAuthCognito();
AmplifyAPI apiPlugin = AmplifyAPI();
await Amplify.addPlugins([authPlugin, apiPlugin]);
await Amplify.configure(jsonEncode(config['amplifyConfig']));
} on AmplifyAlreadyConfiguredException {
print('Tried to reconfigure Amplify; this can occur when your app restarts on Android.');
} catch (e) {
print('Failed to configure Amplify: $e');
}
}
@pragma('vm:entry-point')
void backgroundCallbackDispatcher() async {
WidgetsFlutterBinding.ensureInitialized();
await _configureAmplify();
final apiRepository = APIRepository();
final authRepository = AuthRepository();
try {
SafeDrivePodSDK.podData.stream().listen((PodDataEvent event) async {
await _handlePodEvent(event, apiRepository, authRepository);
}, onError: (error) {
safePrint('Stream error: $error');
}, onDone: () {
safePrint('Stream closed');
}, cancelOnError: true);
} catch (e) {
safePrint('Error in background callback dispatcher: $e');
}
}
@kamami I've been unsuccessful with my attempts to observe a consistent memory increase like the graph you shared. I've seen memory usage spike, but it returns to base line after a few seconds. Given the challenge of recreating this environment, offering more specific steps or a sample app could be beneficial.
Ultimately, until we have official support for making multiple calls to Amplify.configure()
, potential workarounds are:
backgroundCallbackDispatcher
, ie through app life cyclebackgroundCallbackDispatcher
From offline discussions, it sounds like the issue here is caused by configuring Amplify from multiple isolates. This is not something we officially support. We use Amplify.reset()
internally to release resources in tests, but it does not appear that this frees up all (or even most) of the memory. Even if the memory issue were resolved, there could be other issues you find from running multiple amplify instances in multiple isolates. I am not sure that all the interactions with device storage would be safe if executed from multiple isolates in parallel. We could create a feature request to support this. I am not certain of the level of effort for this though.
The best solution here is probably to find a way to only configure and call Amplify on a single isolate (ideally the main isolate). I think you could could use a receive port and send port to send messages between the main isolate and the new isolate that is spun up each time. Please see https://dart.dev/language/isolates#sending-multiple-messages-between-isolates-with-ports.
As for the differences between Firebase and Amplify - I cannot speak in detail about Firebase, but at a high level Firebase in Flutter is just a wrapper around a set of native iOS/Android/JS Firebase libraries. It likely doesn't matter how many Dart isolates you spin up since there is likely very little resources consumed on the dart side. Amplify Flutter is written primarily in Dart. Native code is only invoked when needed to interact with native APIs. We favor a dart first approach since it allows us to support all platforms that Flutter supports, ensures consistent behavior across those platforms, is the language that most of our customers are most familiar with, and it makes debugging much simpler.
Let us know if you have any questions.
@kamami - Let me know if you have any further questions, or if you are interested in opening a feature request to track official support for using Amplify from multiple isolates.
@kamami I am going to close this issue. We have opened https://github.com/aws-amplify/amplify-flutter/issues/5302 to track interest in using isolates
Description
I am configuring amplify in a function, which is called multiple times by native code while the app is running. It is designed to track the users location even in the background. I also want to access my API Gateway and Authentication Services there to send the GPS data to my AWS backend.
I am observing following:
When calling
_configureAmplify
I get a permanent increased memory usage on my iPhone 15 pro. When call_configureAmplify
, but without the linesawait Amplify.addPlugins([authPlugin, apiPlugin]);
I do not get an increased memory usage. But this is not an option, because I need those plugins.Categories
Steps to Reproduce
Run
_configureAmplify
with and withoutawait Amplify.addPlugins([authPlugin, apiPlugin]);
Screenshots
This is the chart, when adding both plugins before configuration. Every step is one invocation of
backgroundCallbackDispatcher
.This is the chart, when NOT adding both plugins before configuration.
Platforms
Flutter Version
3.22.2
Amplify Flutter Version
2.2.0
Deployment Method
Amplify CLI
Schema
No response